core: gsp: General improvements to GSP service accuracy (#2273)

This commit is contained in:
PabloMK7 2026-07-09 14:19:36 +02:00 committed by GitHub
parent c2dd5f3bea
commit bda4671be6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 343 additions and 102 deletions

View File

@ -39,10 +39,13 @@ namespace ErrCodes {
enum { enum {
// TODO(purpasmart): Check if this name fits its actual usage // TODO(purpasmart): Check if this name fits its actual usage
OutofRangeOrMisalignedAddress = 513, OutofRangeOrMisalignedAddress = 513,
NoSaveVRAMSysAreaPerm = 518,
FirstInitialization = 519, FirstInitialization = 519,
}; };
} }
constexpr Result ResultNoSaveVRAMSysAreaPerm(ErrCodes::NoSaveVRAMSysAreaPerm, ErrorModule::GX,
ErrorSummary::NothingHappened, ErrorLevel::Permanent);
constexpr Result ResultFirstInitialization(ErrCodes::FirstInitialization, ErrorModule::GX, constexpr Result ResultFirstInitialization(ErrCodes::FirstInitialization, ErrorModule::GX,
ErrorSummary::Success, ErrorLevel::Success); ErrorSummary::Success, ErrorLevel::Success);
constexpr Result ResultRegsOutOfRangeOrMisaligned(ErrCodes::OutofRangeOrMisalignedAddress, constexpr Result ResultRegsOutOfRangeOrMisaligned(ErrCodes::OutofRangeOrMisalignedAddress,
@ -71,13 +74,34 @@ CommandBuffer* GSP_GPU::GetCommandBuffer(u32 thread_id) {
return reinterpret_cast<CommandBuffer*>(ptr); return reinterpret_cast<CommandBuffer*>(ptr);
} }
FrameBufferUpdate* GSP_GPU::GetFrameBufferInfo(u32 thread_id, u32 screen_index) { std::pair<bool, FrameBufferInfo*> GSP_GPU::GetFrameBufferInfo(u32 thread_id, u32 screen_index,
bool force_update) {
DEBUG_ASSERT_MSG(screen_index < 2, "Invalid screen index"); DEBUG_ASSERT_MSG(screen_index < 2, "Invalid screen index");
bool is_dirty = false;
// Matches real HW behaviour of a cached copy of
// the framebuffer info being maintained that
// is only updated with the dirty flag every time
// the FB info is accessed.
//
// If the thread ID is invalid, the last cached
// data is used.
if (thread_id != std::numeric_limits<u32>::max()) {
// For each thread there are two FrameBufferUpdate fields // For each thread there are two FrameBufferUpdate fields
const u32 offset = 0x200 + (2 * thread_id + screen_index) * sizeof(FrameBufferUpdate); const u32 offset = 0x200 + (2 * thread_id + screen_index) * sizeof(FrameBufferUpdate);
u8* ptr = shared_memory->GetPointer(offset); FrameBufferUpdate* screen =
return reinterpret_cast<FrameBufferUpdate*>(ptr); reinterpret_cast<FrameBufferUpdate*>(shared_memory->GetPointer(offset));
if (screen->is_dirty || force_update) {
cached_framebuffer_infos[screen_index] = screen->framebuffer_info[screen->index];
screen->is_dirty.Assign(false);
is_dirty = true;
}
}
return {is_dirty, &cached_framebuffer_infos[screen_index]};
} }
InterruptRelayQueue* GSP_GPU::GetInterruptRelayQueue(u32 thread_id) { InterruptRelayQueue* GSP_GPU::GetInterruptRelayQueue(u32 thread_id) {
@ -86,10 +110,15 @@ InterruptRelayQueue* GSP_GPU::GetInterruptRelayQueue(u32 thread_id) {
} }
void GSP_GPU::ClientDisconnected(std::shared_ptr<Kernel::ServerSession> server_session) { void GSP_GPU::ClientDisconnected(std::shared_ptr<Kernel::ServerSession> server_session) {
const SessionData* session_data = GetSessionData(server_session); SessionData* session_data = GetSessionData(server_session);
if (active_thread_id == session_data->thread_id) { session_data->interrupt_event = nullptr;
session_data->registered = false;
if (thread_id_with_rights == session_data->thread_id) {
ReleaseRight(session_data); ReleaseRight(session_data);
} }
if (True(session_data->relay_queue_flags & RelayEventQueueFlags::AllowSaveVramSysArea)) {
vram_backup_handler.ResetState();
}
SessionRequestHandler::ClientDisconnected(server_session); SessionRequestHandler::ClientDisconnected(server_session);
} }
@ -190,6 +219,41 @@ static Result WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, std::span
return ResultSuccess; return ResultSuccess;
} }
class GSP_GPU::ThreadCallback : public Kernel::HLERequestContext::WakeupCallback {
public:
enum class Type : u32 {
SaveVRAMSysArea,
RestoreVRAMSysArea,
};
explicit ThreadCallback(Core::System& system_, std::string name_, Type type_)
: system(system_), name(name_), type(type_) {}
void WakeUp(std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
Kernel::ThreadWakeupReason reason) {
if (type == Type::SaveVRAMSysArea || type == Type::RestoreVRAMSysArea) {
IPC::RequestBuilder rb = IPC::RequestBuilder(ctx, 1, 0);
rb.Push(ResultSuccess);
}
}
private:
Core::System& system;
std::string name;
Type type;
ThreadCallback() : system(Core::Global<Core::System>()) {}
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Kernel::HLERequestContext::WakeupCallback>(*this);
ar & name;
ar & type;
}
friend class boost::serialization::access;
};
void GSP_GPU::WriteHWRegs(Kernel::HLERequestContext& ctx) { void GSP_GPU::WriteHWRegs(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const u32 reg_addr = rp.Pop<u32>(); const u32 reg_addr = rp.Pop<u32>();
@ -315,7 +379,8 @@ void GSP_GPU::GetPerfLog(Kernel::HLERequestContext& ctx) {
void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
u32 flags = rp.Pop<u32>();
auto relay_queue_flags = static_cast<RelayEventQueueFlags>(rp.Pop<u32>());
auto interrupt_event = rp.PopObject<Kernel::Event>(); auto interrupt_event = rp.PopObject<Kernel::Event>();
ASSERT_MSG(interrupt_event, "handle is not valid!"); ASSERT_MSG(interrupt_event, "handle is not valid!");
@ -325,6 +390,7 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
SessionData* session_data = GetSessionData(ctx.Session()); SessionData* session_data = GetSessionData(ctx.Session());
session_data->interrupt_event = std::move(interrupt_event); session_data->interrupt_event = std::move(interrupt_event);
session_data->registered = true; session_data->registered = true;
session_data->relay_queue_flags = relay_queue_flags;
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
@ -339,7 +405,7 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
rb.Push(session_data->thread_id); rb.Push(session_data->thread_id);
rb.PushCopyObjects(shared_memory); rb.PushCopyObjects(shared_memory);
LOG_DEBUG(Service_GSP, "called, flags=0x{:08X}", flags); LOG_DEBUG(Service_GSP, "called, relay_queue_flags=0x{:08X}", relay_queue_flags);
} }
void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
@ -521,10 +587,17 @@ void Service::GSP::GSP_GPU::ProcessPendingInterruptImpl(InterruptId interrupt_id
// Update framebuffer information if requested // Update framebuffer information if requested
const s32 screen_id = (interrupt_id == InterruptId::PDC0) ? 0 : 1; const s32 screen_id = (interrupt_id == InterruptId::PDC0) ? 0 : 1;
auto* info = GetFrameBufferInfo(thread_id, screen_id); auto info = GetFrameBufferInfo(thread_id, screen_id);
if (info->is_dirty) { if (info.first || force_buffer_swap[screen_id]) {
system.GPU().SetBufferSwap(screen_id, info->framebuffer_info[info->index]); force_buffer_swap[screen_id] = false;
info->is_dirty.Assign(false); system.GPU().SetBufferSwap(screen_id, *info.second);
}
// If it's the bottom screen interrupt and a HLE event is pending,
// signal it and reset.
if (interrupt_id == InterruptId::PDC1 && session_data->on_vblank_event) {
session_data->on_vblank_event->Signal();
session_data->on_vblank_event = nullptr;
} }
} else { } else {
@ -549,11 +622,11 @@ void GSP_GPU::SignalInterrupt(InterruptId interrupt_id, u64 wait_delay_ns) {
} }
// For normal interrupts, don't do anything if no process has acquired the GPU right. // For normal interrupts, don't do anything if no process has acquired the GPU right.
if (active_thread_id == std::numeric_limits<u32>::max()) { if (thread_id_with_rights == std::numeric_limits<u32>::max()) {
return; return;
} }
SignalInterruptForThread(interrupt_id, active_thread_id, wait_delay_ns); SignalInterruptForThread(interrupt_id, thread_id_with_rights, wait_delay_ns);
} }
void GSP_GPU::SetLcdForceBlack(Kernel::HLERequestContext& ctx) { void GSP_GPU::SetLcdForceBlack(Kernel::HLERequestContext& ctx) {
@ -571,7 +644,14 @@ void GSP_GPU::SetLcdForceBlack(Kernel::HLERequestContext& ctx) {
void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) { void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
auto* command_buffer = GetCommandBuffer(active_thread_id); if (thread_id_with_rights == std::numeric_limits<u32>::max()) {
// Even if the active thread ID is not set,
// the function always succeeds.
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
auto* command_buffer = GetCommandBuffer(thread_id_with_rights);
auto& gpu = system.GPU(); auto& gpu = system.GPU();
bool requires_delay = false; bool requires_delay = false;
@ -628,17 +708,18 @@ void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_GSP, "called"); LOG_DEBUG(Service_GSP, "called");
if (active_thread_id == std::numeric_limits<u32>::max()) { auto top_screen_info = GetFrameBufferInfo(thread_id_with_rights, 0);
LOG_WARNING(Service_GSP, "Called without an active thread."); auto bottom_screen_info = GetFrameBufferInfo(thread_id_with_rights, 1);
// TODO: Find the right error code. if (top_screen_info.first) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); force_buffer_swap[0] = true;
rb.Push(-1); }
return; if (bottom_screen_info.first) {
force_buffer_swap[1] = true;
} }
FrameBufferUpdate* top_screen = GetFrameBufferInfo(active_thread_id, 0); auto top_screen = top_screen_info.second;
FrameBufferUpdate* bottom_screen = GetFrameBufferInfo(active_thread_id, 1); auto bottom_screen = bottom_screen_info.second;
struct CaptureInfoEntry { struct CaptureInfoEntry {
u32_le address_left; u32_le address_left;
@ -649,16 +730,15 @@ void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) {
CaptureInfoEntry top_entry, bottom_entry; CaptureInfoEntry top_entry, bottom_entry;
// Top Screen // Top Screen
top_entry.address_left = top_screen->framebuffer_info[top_screen->index].address_left; top_entry.address_left = top_screen->address_left;
top_entry.address_right = top_screen->framebuffer_info[top_screen->index].address_right; top_entry.address_right = top_screen->address_right;
top_entry.format = top_screen->framebuffer_info[top_screen->index].format; top_entry.format = top_screen->format;
top_entry.stride = top_screen->framebuffer_info[top_screen->index].stride; top_entry.stride = top_screen->stride;
// Bottom Screen // Bottom Screen
bottom_entry.address_left = bottom_screen->framebuffer_info[bottom_screen->index].address_left; bottom_entry.address_left = bottom_screen->address_left;
bottom_entry.address_right = bottom_entry.address_right = bottom_screen->address_right;
bottom_screen->framebuffer_info[bottom_screen->index].address_right; bottom_entry.format = bottom_screen->format;
bottom_entry.format = bottom_screen->framebuffer_info[bottom_screen->index].format; bottom_entry.stride = bottom_screen->stride;
bottom_entry.stride = bottom_screen->framebuffer_info[bottom_screen->index].stride;
IPC::RequestBuilder rb = rp.MakeBuilder(9, 0); IPC::RequestBuilder rb = rp.MakeBuilder(9, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@ -722,26 +802,19 @@ void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) {
// Invalid values // Invalid values
0, 0, 0}; 0, 0, 0};
if (active_thread_id == std::numeric_limits<u32>::max()) { const auto session_data = GetSessionData(ctx.Session());
LOG_WARNING(Service_GSP, "Called without an active thread."); if (False(session_data->relay_queue_flags & RelayEventQueueFlags::AllowSaveVramSysArea)) {
// TODO: Find the right error code.
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(-1); rb.Push(ResultNoSaveVRAMSysAreaPerm);
return; return;
} }
system.Memory().RasterizerFlushVirtualRegion(Memory::VRAM_VADDR, Memory::VRAM_SIZE, vram_backup_handler.SaveVRAMWithState(VramBackupHandler::State::STATE_0);
Memory::FlushMode::Flush);
const auto vram = system.Memory().GetPointer(Memory::VRAM_VADDR);
saved_vram.emplace(std::vector<u8>(Memory::VRAM_SIZE));
std::memcpy(saved_vram.get().data(), vram, Memory::VRAM_SIZE);
auto top_screen = GetFrameBufferInfo(active_thread_id, 0); auto top_screen = GetFrameBufferInfo(thread_id_with_rights, 0).second;
if (top_screen) { if (top_screen) {
u8 bytes_per_pixel = u8 bytes_per_pixel = bpp_per_format[top_screen->GetPixelFormat()];
bpp_per_format[top_screen->framebuffer_info[top_screen->index].GetPixelFormat()]; const auto& top_fb = *top_screen;
const auto top_fb = top_screen->framebuffer_info[top_screen->index];
if (top_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) { if (top_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) {
CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_LEFT, top_fb.address_left, CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_LEFT, top_fb.address_left,
FRAMEBUFFER_WIDTH * bytes_per_pixel, top_fb.stride, FRAMEBUFFER_WIDTH * bytes_per_pixel, top_fb.stride,
@ -761,21 +834,18 @@ void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) {
FRAMEBUFFER_WIDTH * bytes_per_pixel, TOP_FRAMEBUFFER_HEIGHT); FRAMEBUFFER_WIDTH * bytes_per_pixel, TOP_FRAMEBUFFER_HEIGHT);
} }
FrameBufferInfo fb_info = top_screen->framebuffer_info[top_screen->index]; top_screen->address_left = FRAMEBUFFER_SAVE_AREA_TOP_LEFT;
top_screen->address_right = FRAMEBUFFER_SAVE_AREA_TOP_RIGHT;
fb_info.address_left = FRAMEBUFFER_SAVE_AREA_TOP_LEFT; top_screen->stride = FRAMEBUFFER_WIDTH * bytes_per_pixel;
fb_info.address_right = FRAMEBUFFER_SAVE_AREA_TOP_RIGHT; force_buffer_swap[0] = true;
fb_info.stride = FRAMEBUFFER_WIDTH * bytes_per_pixel;
system.GPU().SetBufferSwap(0, fb_info);
} else { } else {
LOG_WARNING(Service_GSP, "No top screen bound, skipping capture."); LOG_WARNING(Service_GSP, "No top screen bound, skipping capture.");
} }
auto bottom_screen = GetFrameBufferInfo(active_thread_id, 1); auto bottom_screen = GetFrameBufferInfo(thread_id_with_rights, 1).second;
if (bottom_screen) { if (bottom_screen) {
u8 bytes_per_pixel = u8 bytes_per_pixel = bpp_per_format[bottom_screen->GetPixelFormat()];
bpp_per_format[bottom_screen->framebuffer_info[bottom_screen->index].GetPixelFormat()]; const auto& bottom_fb = *bottom_screen;
const auto bottom_fb = bottom_screen->framebuffer_info[bottom_screen->index];
if (bottom_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) { if (bottom_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) {
CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_BOTTOM, bottom_fb.address_left, CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_BOTTOM, bottom_fb.address_left,
FRAMEBUFFER_WIDTH * bytes_per_pixel, bottom_fb.stride, FRAMEBUFFER_WIDTH * bytes_per_pixel, bottom_fb.stride,
@ -785,19 +855,21 @@ void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) {
ClearFramebuffer(system, FRAMEBUFFER_SAVE_AREA_BOTTOM, ClearFramebuffer(system, FRAMEBUFFER_SAVE_AREA_BOTTOM,
FRAMEBUFFER_WIDTH * bytes_per_pixel, BOTTOM_FRAMEBUFFER_HEIGHT); FRAMEBUFFER_WIDTH * bytes_per_pixel, BOTTOM_FRAMEBUFFER_HEIGHT);
} }
FrameBufferInfo fb_info = bottom_screen->framebuffer_info[bottom_screen->index];
fb_info.address_left = FRAMEBUFFER_SAVE_AREA_BOTTOM; bottom_screen->address_left = FRAMEBUFFER_SAVE_AREA_BOTTOM;
fb_info.stride = FRAMEBUFFER_WIDTH * bytes_per_pixel; bottom_screen->stride = FRAMEBUFFER_WIDTH * bytes_per_pixel;
system.GPU().SetBufferSwap(1, fb_info); force_buffer_swap[1] = true;
} else { } else {
LOG_WARNING(Service_GSP, "No bottom screen bound, skipping capture."); LOG_WARNING(Service_GSP, "No bottom screen bound, skipping capture.");
} }
// Real GSP waits for VBlank here, but we don't need it (?). vram_backup_handler.SaveVRAMWithState(VramBackupHandler::State::STATE_1);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); // Wait for VBlank
rb.Push(ResultSuccess); auto vblank_callback = std::make_shared<ThreadCallback>(system, "GSP_GPU::SaveVRAMSysArea",
ThreadCallback::Type::SaveVRAMSysArea);
session_data->on_vblank_event = ctx.SleepClientThread(
"GSP_GPU::SaveVRAMSysArea", std::chrono::nanoseconds(-1), vblank_callback);
} }
void GSP_GPU::RestoreVramSysArea(Kernel::HLERequestContext& ctx) { void GSP_GPU::RestoreVramSysArea(Kernel::HLERequestContext& ctx) {
@ -805,31 +877,35 @@ void GSP_GPU::RestoreVramSysArea(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_GSP, "called"); LOG_DEBUG(Service_GSP, "called");
if (saved_vram) { const auto session_data = GetSessionData(ctx.Session());
auto vram = system.Memory().GetPointer(Memory::VRAM_VADDR); if (False(session_data->relay_queue_flags & RelayEventQueueFlags::AllowSaveVramSysArea)) {
std::memcpy(vram, saved_vram.get().data(), Memory::VRAM_SIZE);
system.Memory().RasterizerFlushVirtualRegion(Memory::VRAM_VADDR, Memory::VRAM_SIZE,
Memory::FlushMode::Invalidate);
}
auto top_screen = GetFrameBufferInfo(active_thread_id, 0);
if (top_screen) {
system.GPU().SetBufferSwap(0, top_screen->framebuffer_info[top_screen->index]);
} else {
LOG_WARNING(Service_GSP, "No top screen bound, skipping restore.");
}
auto bottom_screen = GetFrameBufferInfo(active_thread_id, 1);
if (bottom_screen) {
system.GPU().SetBufferSwap(1, bottom_screen->framebuffer_info[top_screen->index]);
} else {
LOG_WARNING(Service_GSP, "No bottom screen bound, skipping restore.");
}
// Real GSP waits for VBlank here, but we don't need it (?).
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess); rb.Push(ResultNoSaveVRAMSysAreaPerm);
return;
}
vram_backup_handler.RestoreVRAMWithState(VramBackupHandler::State::STATE_1);
auto top_screen = GetFrameBufferInfo(thread_id_with_rights, 0, true).second;
if (!top_screen) {
LOG_WARNING(Service_GSP, "No top screen bound, skipping restore.");
} else {
force_buffer_swap[0] = true;
}
auto bottom_screen = GetFrameBufferInfo(thread_id_with_rights, 1, true).second;
if (!bottom_screen) {
LOG_WARNING(Service_GSP, "No bottom screen bound, skipping restore.");
} else {
force_buffer_swap[1] = true;
}
vram_backup_handler.RestoreVRAMWithState(VramBackupHandler::State::STATE_0);
auto vblank_callback = std::make_shared<ThreadCallback>(
system, "GSP_GPU::RestoreVramSysArea", ThreadCallback::Type::RestoreVRAMSysArea);
session_data->on_vblank_event = ctx.SleepClientThread(
"GSP_GPU::RestoreVramSysArea", std::chrono::nanoseconds(-1), vblank_callback);
} }
Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx, Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx,
@ -861,7 +937,7 @@ Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx,
gpu.PicaCore().vs_setup.requires_fixup = requires_shader_fixup; gpu.PicaCore().vs_setup.requires_fixup = requires_shader_fixup;
gpu.PicaCore().gs_setup.requires_fixup = requires_shader_fixup; gpu.PicaCore().gs_setup.requires_fixup = requires_shader_fixup;
if (active_thread_id == session_data->thread_id) { if (thread_id_with_rights == session_data->thread_id) {
return {ErrorDescription::AlreadyDone, ErrorModule::GX, ErrorSummary::Success, return {ErrorDescription::AlreadyDone, ErrorModule::GX, ErrorSummary::Success,
ErrorLevel::Success}; ErrorLevel::Success};
} }
@ -870,14 +946,14 @@ Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx,
if (blocking) { if (blocking) {
// TODO: The thread should be put to sleep until acquired. // TODO: The thread should be put to sleep until acquired.
ASSERT_MSG(active_thread_id == std::numeric_limits<u32>::max(), ASSERT_MSG(thread_id_with_rights == std::numeric_limits<u32>::max(),
"Sleeping for GPU right is not yet supported."); "Sleeping for GPU right is not yet supported.");
} else if (active_thread_id != std::numeric_limits<u32>::max()) { } else if (thread_id_with_rights != std::numeric_limits<u32>::max()) {
return {ErrorDescription::Busy, ErrorModule::GX, ErrorSummary::WouldBlock, return {ErrorDescription::Busy, ErrorModule::GX, ErrorSummary::WouldBlock,
ErrorLevel::Status}; ErrorLevel::Status};
} }
active_thread_id = session_data->thread_id; thread_id_with_rights = session_data->thread_id;
active_client_thread_id = ctx.ClientThread()->thread_id; active_client_thread_id = ctx.ClientThread()->thread_id;
return ResultSuccess; return ResultSuccess;
} }
@ -904,16 +980,17 @@ void GSP_GPU::AcquireRight(Kernel::HLERequestContext& ctx) {
} }
void GSP_GPU::ReleaseRight(const SessionData* session_data) { void GSP_GPU::ReleaseRight(const SessionData* session_data) {
ASSERT_MSG(active_thread_id == session_data->thread_id, if (thread_id_with_rights == session_data->thread_id) {
"Wrong thread tried to release GPU right"); thread_id_with_rights = std::numeric_limits<u32>::max();
active_thread_id = std::numeric_limits<u32>::max();
active_client_thread_id = std::numeric_limits<u32>::max(); active_client_thread_id = std::numeric_limits<u32>::max();
}
} }
void GSP_GPU::ReleaseRight(Kernel::HLERequestContext& ctx) { void GSP_GPU::ReleaseRight(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const SessionData* session_data = GetSessionData(ctx.Session()); const SessionData* session_data = GetSessionData(ctx.Session());
// Success even if wrong thread calls this function.
ReleaseRight(session_data); ReleaseRight(session_data);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@ -976,14 +1053,16 @@ void GSP_GPU::serialize(Archive& ar, const unsigned int) {
DEBUG_SERIALIZATION_POINT; DEBUG_SERIALIZATION_POINT;
ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this); ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
ar & shared_memory; ar & shared_memory;
ar & active_thread_id; ar & thread_id_with_rights;
ar & active_client_thread_id; ar & active_client_thread_id;
ar & first_initialization; ar & first_initialization;
ar & used_thread_ids; ar & used_thread_ids;
ar & saved_vram;
ar & delay_texture_copy_completion; ar & delay_texture_copy_completion;
ar & pending_interrupts; ar & pending_interrupts;
ar & perf_recorder; ar & perf_recorder;
ar & cached_framebuffer_infos;
ar & force_buffer_swap;
ar & vram_backup_handler;
} }
SERIALIZE_IMPL(GSP_GPU) SERIALIZE_IMPL(GSP_GPU)
@ -1051,6 +1130,8 @@ void SessionData::serialize(Archive& ar, const unsigned int) {
ar & interrupt_event; ar & interrupt_event;
ar & thread_id; ar & thread_id;
ar & registered; ar & registered;
ar & relay_queue_flags;
ar & on_vblank_event;
} }
SERIALIZE_IMPL(SessionData) SERIALIZE_IMPL(SessionData)
@ -1067,4 +1148,64 @@ SessionData::~SessionData() {
gsp->used_thread_ids[thread_id] = false; gsp->used_thread_ids[thread_id] = false;
} }
void VramBackupHandler::SaveVRAMWithState(State state) {
VRAMBankBackupMask mask{};
if (state == State::STATE_0 && curr_state == State::STATE_0) {
mask = VRAMBankBackupMask::MASK_1;
curr_state = State::STATE_1;
} else if (state == State::STATE_1 && curr_state == State::STATE_1) {
mask = VRAMBankBackupMask::MASK_2;
curr_state = State::STATE_2;
}
for (size_t i = 0; i < BANK_COUNT; i++) {
if (True(mask & mask_per_bank[i])) {
SaveVRAMBank(i);
}
}
}
void VramBackupHandler::RestoreVRAMWithState(State state) {
VRAMBankBackupMask mask{};
if (state == State::STATE_0 && curr_state == State::STATE_1) {
mask = VRAMBankBackupMask::MASK_1;
curr_state = State::STATE_0;
} else if (state == State::STATE_1 && curr_state == State::STATE_2) {
mask = VRAMBankBackupMask::MASK_2;
curr_state = State::STATE_1;
}
for (size_t i = 0; i < BANK_COUNT; i++) {
if (True(mask & mask_per_bank[i])) {
RestoreVRAMBank(i);
}
}
}
void VramBackupHandler::SaveVRAMBank(size_t bank_id) {
ASSERT(bank_id < BANK_COUNT);
VAddr bank_vaddr = Memory::VRAM_VADDR + bank_id * VRAM_BACKUP_BANK_SIZE;
system.Memory().RasterizerFlushVirtualRegion(bank_vaddr, VRAM_BACKUP_BANK_SIZE,
Memory::FlushMode::Flush);
u8* data = system.Memory().GetPointer(bank_vaddr);
memcpy(vram_backup[bank_id].data(), data, VRAM_BACKUP_BANK_SIZE);
}
void VramBackupHandler::RestoreVRAMBank(size_t bank_id) {
ASSERT(bank_id < BANK_COUNT);
VAddr bank_vaddr = Memory::VRAM_VADDR + bank_id * VRAM_BACKUP_BANK_SIZE;
u8* data = system.Memory().GetPointer(bank_vaddr);
memcpy(data, vram_backup[bank_id].data(), VRAM_BACKUP_BANK_SIZE);
system.Memory().RasterizerFlushVirtualRegion(bank_vaddr, VRAM_BACKUP_BANK_SIZE,
Memory::FlushMode::Invalidate);
}
} // namespace Service::GSP } // namespace Service::GSP
SERIALIZE_EXPORT_IMPL(Service::GSP::GSP_GPU::ThreadCallback)

View File

@ -18,6 +18,7 @@
#include "core/hle/service/gsp/gsp_command.h" #include "core/hle/service/gsp/gsp_command.h"
#include "core/hle/service/gsp/gsp_interrupt.h" #include "core/hle/service/gsp/gsp_interrupt.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
#include "core/memory.h"
namespace Core { namespace Core {
class System; class System;
@ -45,6 +46,17 @@ struct FrameBufferInfo {
u32 GetPixelFormat() { u32 GetPixelFormat() {
return format & PIXEL_FORMAT_MASK; return format & PIXEL_FORMAT_MASK;
} }
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar & active_fb;
ar & address_left;
ar & address_right;
ar & stride;
ar & format;
ar & shown_fb;
ar & unknown;
}
}; };
static_assert(sizeof(FrameBufferInfo) == 0x1c, "Struct has incorrect size"); static_assert(sizeof(FrameBufferInfo) == 0x1c, "Struct has incorrect size");
@ -61,6 +73,20 @@ static_assert(sizeof(FrameBufferUpdate) == 0x40, "Struct has incorrect size");
static_assert(offsetof(FrameBufferUpdate, framebuffer_info[1]) == 0x20, static_assert(offsetof(FrameBufferUpdate, framebuffer_info[1]) == 0x20,
"FrameBufferInfo element has incorrect alignment"); "FrameBufferInfo element has incorrect alignment");
enum RelayEventQueueFlags : u32 {
AllowSaveVramSysArea = (1 << 0),
};
DECLARE_ENUM_FLAG_OPERATORS(RelayEventQueueFlags)
// TODO(PabloMK7): Fill this in when the purpose of
// each bit is determined.
enum class VRAMBankBackupMask : u8 {
MASK_0 = (1 << 0),
MASK_1 = (1 << 1),
MASK_2 = (1 << 2),
};
DECLARE_ENUM_FLAG_OPERATORS(VRAMBankBackupMask)
constexpr u32 FRAMEBUFFER_WIDTH = 240; constexpr u32 FRAMEBUFFER_WIDTH = 240;
constexpr u32 FRAMEBUFFER_WIDTH_POW2 = 256; constexpr u32 FRAMEBUFFER_WIDTH_POW2 = 256;
constexpr u32 TOP_FRAMEBUFFER_HEIGHT = 400; constexpr u32 TOP_FRAMEBUFFER_HEIGHT = 400;
@ -72,6 +98,8 @@ constexpr VAddr FRAMEBUFFER_SAVE_AREA_TOP_LEFT = Memory::VRAM_VADDR + 0x273000;
constexpr VAddr FRAMEBUFFER_SAVE_AREA_TOP_RIGHT = Memory::VRAM_VADDR + 0x2B9800; constexpr VAddr FRAMEBUFFER_SAVE_AREA_TOP_RIGHT = Memory::VRAM_VADDR + 0x2B9800;
constexpr VAddr FRAMEBUFFER_SAVE_AREA_BOTTOM = Memory::VRAM_VADDR + 0x4C7800; constexpr VAddr FRAMEBUFFER_SAVE_AREA_BOTTOM = Memory::VRAM_VADDR + 0x4C7800;
constexpr size_t VRAM_BACKUP_BANK_SIZE = 0x80000;
class GSP_GPU; class GSP_GPU;
class SessionData : public Kernel::SessionRequestHandler::SessionDataBase { class SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
@ -88,6 +116,10 @@ public:
u32 thread_id; u32 thread_id;
/// Whether RegisterInterruptRelayQueue was called for this session /// Whether RegisterInterruptRelayQueue was called for this session
bool registered = false; bool registered = false;
/// Current relay queue flags
RelayEventQueueFlags relay_queue_flags{};
/// Event to be signaled on VBlank
std::shared_ptr<Kernel::Event> on_vblank_event{};
private: private:
template <class Archive> template <class Archive>
@ -95,6 +127,68 @@ private:
friend class boost::serialization::access; friend class boost::serialization::access;
}; };
class VramBackupHandler {
public:
VramBackupHandler() : system(Core::Global<Core::System>()) {
for (auto& v : vram_backup) {
v.resize(VRAM_BACKUP_BANK_SIZE);
}
}
// TODO(PabloMK7): Figure out what each state means
enum class State : u32 { STATE_0, STATE_1, STATE_2 };
void SaveVRAMWithState(State state);
void RestoreVRAMWithState(State state);
void ResetState() {
curr_state = State::STATE_0;
}
private:
Core::System& system;
void SaveVRAMBank(size_t bank_id);
void RestoreVRAMBank(size_t bank_id);
static constexpr u32 BANK_COUNT = Memory::VRAM_SIZE / VRAM_BACKUP_BANK_SIZE;
// A vector is needed because otherwise a stack overflow happens
// in the boost serialization code.
std::array<std::vector<u8>, BANK_COUNT> vram_backup{};
// TODO(PabloMK7): Figure out what this means, it's taken from
// GSP decompilation.
static constexpr std::array<VRAMBankBackupMask, BANK_COUNT> mask_per_bank = {
VRAMBankBackupMask::MASK_0, // 0x1F000000 - 0x1F07FFFF
VRAMBankBackupMask::MASK_0, // 0x1F080000 - 0x1F0FFFFF
VRAMBankBackupMask::MASK_0, // 0x1F100000 - 0x1F17FFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_1, // 0x1F180000 - 0x1F1FFFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_1, // 0x1F200000 - 0x1F27FFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_1, // 0x1F280000 - 0x1F2FFFFF
VRAMBankBackupMask::MASK_0, // 0x1F300000 - 0x1F37FFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_2, // 0x1F380000 - 0x1F3FFFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_2, // 0x1F400000 - 0x1F47FFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_1, // 0x1F480000 - 0x1F4FFFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_1, // 0x1F500000 - 0x1F57FFFF
VRAMBankBackupMask::MASK_0 | VRAMBankBackupMask::MASK_1, // 0x1F580000 - 0x1F5FFFFF
};
State curr_state = State::STATE_0;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
for (size_t i = 0; i < BANK_COUNT; i++) {
ar& vram_backup[i];
}
ar & curr_state;
}
friend class boost::serialization::access;
};
class GSP_GPU final : public ServiceFramework<GSP_GPU, SessionData> { class GSP_GPU final : public ServiceFramework<GSP_GPU, SessionData> {
public: public:
explicit GSP_GPU(Core::System& system); explicit GSP_GPU(Core::System& system);
@ -114,9 +208,10 @@ public:
* @param thread_id GSP thread id of the process that accesses the structure that we are * @param thread_id GSP thread id of the process that accesses the structure that we are
* requesting. * requesting.
* @param screen_index Index of the screen we are requesting (Top = 0, Bottom = 1). * @param screen_index Index of the screen we are requesting (Top = 0, Bottom = 1).
* @returns FramebufferUpdate Information about the specified framebuffer. * @returns A pair containing whether the FB info is new and the framebuffer info itself.
*/ */
FrameBufferUpdate* GetFrameBufferInfo(u32 thread_id, u32 screen_index); std::pair<bool, FrameBufferInfo*> GetFrameBufferInfo(u32 thread_id, u32 screen_index,
bool force_update = false);
/// Gets a pointer to a thread command buffer in GSP shared memory /// Gets a pointer to a thread command buffer in GSP shared memory
CommandBuffer* GetCommandBuffer(u32 thread_id); CommandBuffer* GetCommandBuffer(u32 thread_id);
@ -128,7 +223,7 @@ public:
* Retreives the ID of the thread with GPU rights. * Retreives the ID of the thread with GPU rights.
*/ */
u32 GetActiveThreadId() { u32 GetActiveThreadId() {
return active_thread_id; return thread_id_with_rights;
} }
/** /**
@ -138,6 +233,8 @@ public:
return active_client_thread_id; return active_client_thread_id;
} }
class ThreadCallback;
private: private:
/** /**
* Signals that the specified interrupt type has occurred to userland code for the specified GSP * Signals that the specified interrupt type has occurred to userland code for the specified GSP
@ -399,16 +496,13 @@ private:
std::shared_ptr<Kernel::SharedMemory> shared_memory; std::shared_ptr<Kernel::SharedMemory> shared_memory;
/// Thread id that currently has GPU rights or std::numeric_limits<u32>::max() if none. /// Thread id that currently has GPU rights or std::numeric_limits<u32>::max() if none.
u32 active_thread_id = std::numeric_limits<u32>::max(); u32 thread_id_with_rights = std::numeric_limits<u32>::max();
/// Thread id of the client thread that has GPU rights /// Thread id of the client thread that has GPU rights
u32 active_client_thread_id = std::numeric_limits<u32>::max(); u32 active_client_thread_id = std::numeric_limits<u32>::max();
bool first_initialization = true; bool first_initialization = true;
/// VRAM copy saved using SaveVramSysArea.
boost::optional<std::vector<u8>> saved_vram;
/// Maximum number of threads that can be registered at the same time in the GSP module. /// Maximum number of threads that can be registered at the same time in the GSP module.
static constexpr u32 MaxGSPThreads = 4; static constexpr u32 MaxGSPThreads = 4;
@ -527,6 +621,11 @@ private:
Core::TimingEventType* SignalInterruptEventType = nullptr; Core::TimingEventType* SignalInterruptEventType = nullptr;
std::array<FrameBufferInfo, 2> cached_framebuffer_infos;
std::array<bool, 2> force_buffer_swap{};
VramBackupHandler vram_backup_handler;
friend class SessionData; friend class SessionData;
template <class Archive> template <class Archive>
@ -539,3 +638,4 @@ private:
BOOST_CLASS_EXPORT_KEY(Service::GSP::SessionData) BOOST_CLASS_EXPORT_KEY(Service::GSP::SessionData)
BOOST_CLASS_EXPORT_KEY(Service::GSP::GSP_GPU) BOOST_CLASS_EXPORT_KEY(Service::GSP::GSP_GPU)
SERVICE_CONSTRUCT(Service::GSP::GSP_GPU) SERVICE_CONSTRUCT(Service::GSP::GSP_GPU)
BOOST_CLASS_EXPORT_KEY(Service::GSP::GSP_GPU::ThreadCallback)

View File

@ -486,13 +486,13 @@ void GPU::MemoryTransfer() {
} }
void GPU::VBlankCallback(std::uintptr_t user_data, s64 cycles_late) { void GPU::VBlankCallback(std::uintptr_t user_data, s64 cycles_late) {
// Present renderered frame.
impl->renderer->SwapBuffers();
// Signal to GSP that GPU interrupt has occurred // Signal to GSP that GPU interrupt has occurred
impl->signal_interrupt(Service::GSP::InterruptId::PDC0, 0); impl->signal_interrupt(Service::GSP::InterruptId::PDC0, 0);
impl->signal_interrupt(Service::GSP::InterruptId::PDC1, 0); impl->signal_interrupt(Service::GSP::InterruptId::PDC1, 0);
// Present renderered frame.
impl->renderer->SwapBuffers();
// Reschedule recurrent event // Reschedule recurrent event
impl->timing.ScheduleEvent(FRAME_TICKS - cycles_late, impl->vblank_event); impl->timing.ScheduleEvent(FRAME_TICKS - cycles_late, impl->vblank_event);
} }