From bda4671be607db03ec0b8fd1cc993807cb9f57ca Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Thu, 9 Jul 2026 14:19:36 +0200 Subject: [PATCH] core: gsp: General improvements to GSP service accuracy (#2273) --- src/core/hle/service/gsp/gsp_gpu.cpp | 325 +++++++++++++++++++-------- src/core/hle/service/gsp/gsp_gpu.h | 114 +++++++++- src/video_core/gpu.cpp | 6 +- 3 files changed, 343 insertions(+), 102 deletions(-) diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp index 853dd8d3c..49039e9b5 100644 --- a/src/core/hle/service/gsp/gsp_gpu.cpp +++ b/src/core/hle/service/gsp/gsp_gpu.cpp @@ -39,10 +39,13 @@ namespace ErrCodes { enum { // TODO(purpasmart): Check if this name fits its actual usage OutofRangeOrMisalignedAddress = 513, + NoSaveVRAMSysAreaPerm = 518, FirstInitialization = 519, }; } +constexpr Result ResultNoSaveVRAMSysAreaPerm(ErrCodes::NoSaveVRAMSysAreaPerm, ErrorModule::GX, + ErrorSummary::NothingHappened, ErrorLevel::Permanent); constexpr Result ResultFirstInitialization(ErrCodes::FirstInitialization, ErrorModule::GX, ErrorSummary::Success, ErrorLevel::Success); constexpr Result ResultRegsOutOfRangeOrMisaligned(ErrCodes::OutofRangeOrMisalignedAddress, @@ -71,13 +74,34 @@ CommandBuffer* GSP_GPU::GetCommandBuffer(u32 thread_id) { return reinterpret_cast(ptr); } -FrameBufferUpdate* GSP_GPU::GetFrameBufferInfo(u32 thread_id, u32 screen_index) { +std::pair GSP_GPU::GetFrameBufferInfo(u32 thread_id, u32 screen_index, + bool force_update) { DEBUG_ASSERT_MSG(screen_index < 2, "Invalid screen index"); - // For each thread there are two FrameBufferUpdate fields - const u32 offset = 0x200 + (2 * thread_id + screen_index) * sizeof(FrameBufferUpdate); - u8* ptr = shared_memory->GetPointer(offset); - return reinterpret_cast(ptr); + 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::max()) { + + // For each thread there are two FrameBufferUpdate fields + const u32 offset = 0x200 + (2 * thread_id + screen_index) * sizeof(FrameBufferUpdate); + FrameBufferUpdate* screen = + reinterpret_cast(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) { @@ -86,10 +110,15 @@ InterruptRelayQueue* GSP_GPU::GetInterruptRelayQueue(u32 thread_id) { } void GSP_GPU::ClientDisconnected(std::shared_ptr server_session) { - const SessionData* session_data = GetSessionData(server_session); - if (active_thread_id == session_data->thread_id) { + SessionData* session_data = GetSessionData(server_session); + session_data->interrupt_event = nullptr; + session_data->registered = false; + if (thread_id_with_rights == session_data->thread_id) { ReleaseRight(session_data); } + if (True(session_data->relay_queue_flags & RelayEventQueueFlags::AllowSaveVramSysArea)) { + vram_backup_handler.ResetState(); + } SessionRequestHandler::ClientDisconnected(server_session); } @@ -190,6 +219,41 @@ static Result WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, std::span 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 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()) {} + + template + void serialize(Archive& ar, const unsigned int) { + ar& boost::serialization::base_object(*this); + ar & name; + ar & type; + } + friend class boost::serialization::access; +}; + void GSP_GPU::WriteHWRegs(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 reg_addr = rp.Pop(); @@ -315,7 +379,8 @@ void GSP_GPU::GetPerfLog(Kernel::HLERequestContext& ctx) { void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - u32 flags = rp.Pop(); + + auto relay_queue_flags = static_cast(rp.Pop()); auto interrupt_event = rp.PopObject(); 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()); session_data->interrupt_event = std::move(interrupt_event); session_data->registered = true; + session_data->relay_queue_flags = relay_queue_flags; 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.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) { @@ -521,10 +587,17 @@ void Service::GSP::GSP_GPU::ProcessPendingInterruptImpl(InterruptId interrupt_id // Update framebuffer information if requested const s32 screen_id = (interrupt_id == InterruptId::PDC0) ? 0 : 1; - auto* info = GetFrameBufferInfo(thread_id, screen_id); - if (info->is_dirty) { - system.GPU().SetBufferSwap(screen_id, info->framebuffer_info[info->index]); - info->is_dirty.Assign(false); + auto info = GetFrameBufferInfo(thread_id, screen_id); + if (info.first || force_buffer_swap[screen_id]) { + force_buffer_swap[screen_id] = 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 { @@ -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. - if (active_thread_id == std::numeric_limits::max()) { + if (thread_id_with_rights == std::numeric_limits::max()) { 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) { @@ -571,7 +644,14 @@ void GSP_GPU::SetLcdForceBlack(Kernel::HLERequestContext& ctx) { void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - auto* command_buffer = GetCommandBuffer(active_thread_id); + if (thread_id_with_rights == std::numeric_limits::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(); bool requires_delay = false; @@ -628,17 +708,18 @@ void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_GSP, "called"); - if (active_thread_id == std::numeric_limits::max()) { - LOG_WARNING(Service_GSP, "Called without an active thread."); + auto top_screen_info = GetFrameBufferInfo(thread_id_with_rights, 0); + auto bottom_screen_info = GetFrameBufferInfo(thread_id_with_rights, 1); - // TODO: Find the right error code. - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(-1); - return; + if (top_screen_info.first) { + force_buffer_swap[0] = true; + } + if (bottom_screen_info.first) { + force_buffer_swap[1] = true; } - FrameBufferUpdate* top_screen = GetFrameBufferInfo(active_thread_id, 0); - FrameBufferUpdate* bottom_screen = GetFrameBufferInfo(active_thread_id, 1); + auto top_screen = top_screen_info.second; + auto bottom_screen = bottom_screen_info.second; struct CaptureInfoEntry { u32_le address_left; @@ -649,16 +730,15 @@ void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) { CaptureInfoEntry top_entry, bottom_entry; // Top Screen - top_entry.address_left = top_screen->framebuffer_info[top_screen->index].address_left; - top_entry.address_right = top_screen->framebuffer_info[top_screen->index].address_right; - top_entry.format = top_screen->framebuffer_info[top_screen->index].format; - top_entry.stride = top_screen->framebuffer_info[top_screen->index].stride; + top_entry.address_left = top_screen->address_left; + top_entry.address_right = top_screen->address_right; + top_entry.format = top_screen->format; + top_entry.stride = top_screen->stride; // Bottom Screen - bottom_entry.address_left = bottom_screen->framebuffer_info[bottom_screen->index].address_left; - bottom_entry.address_right = - bottom_screen->framebuffer_info[bottom_screen->index].address_right; - bottom_entry.format = bottom_screen->framebuffer_info[bottom_screen->index].format; - bottom_entry.stride = bottom_screen->framebuffer_info[bottom_screen->index].stride; + bottom_entry.address_left = bottom_screen->address_left; + bottom_entry.address_right = bottom_screen->address_right; + bottom_entry.format = bottom_screen->format; + bottom_entry.stride = bottom_screen->stride; IPC::RequestBuilder rb = rp.MakeBuilder(9, 0); rb.Push(ResultSuccess); @@ -722,26 +802,19 @@ void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) { // Invalid values 0, 0, 0}; - if (active_thread_id == std::numeric_limits::max()) { - LOG_WARNING(Service_GSP, "Called without an active thread."); - - // TODO: Find the right error code. + const auto session_data = GetSessionData(ctx.Session()); + if (False(session_data->relay_queue_flags & RelayEventQueueFlags::AllowSaveVramSysArea)) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(-1); + rb.Push(ResultNoSaveVRAMSysAreaPerm); return; } - system.Memory().RasterizerFlushVirtualRegion(Memory::VRAM_VADDR, Memory::VRAM_SIZE, - Memory::FlushMode::Flush); - const auto vram = system.Memory().GetPointer(Memory::VRAM_VADDR); - saved_vram.emplace(std::vector(Memory::VRAM_SIZE)); - std::memcpy(saved_vram.get().data(), vram, Memory::VRAM_SIZE); + vram_backup_handler.SaveVRAMWithState(VramBackupHandler::State::STATE_0); - auto top_screen = GetFrameBufferInfo(active_thread_id, 0); + auto top_screen = GetFrameBufferInfo(thread_id_with_rights, 0).second; if (top_screen) { - u8 bytes_per_pixel = - bpp_per_format[top_screen->framebuffer_info[top_screen->index].GetPixelFormat()]; - const auto top_fb = top_screen->framebuffer_info[top_screen->index]; + u8 bytes_per_pixel = bpp_per_format[top_screen->GetPixelFormat()]; + const auto& top_fb = *top_screen; if (top_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) { CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_LEFT, top_fb.address_left, 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); } - FrameBufferInfo fb_info = top_screen->framebuffer_info[top_screen->index]; - - fb_info.address_left = FRAMEBUFFER_SAVE_AREA_TOP_LEFT; - fb_info.address_right = FRAMEBUFFER_SAVE_AREA_TOP_RIGHT; - fb_info.stride = FRAMEBUFFER_WIDTH * bytes_per_pixel; - system.GPU().SetBufferSwap(0, fb_info); + top_screen->address_left = FRAMEBUFFER_SAVE_AREA_TOP_LEFT; + top_screen->address_right = FRAMEBUFFER_SAVE_AREA_TOP_RIGHT; + top_screen->stride = FRAMEBUFFER_WIDTH * bytes_per_pixel; + force_buffer_swap[0] = true; } else { 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) { - u8 bytes_per_pixel = - bpp_per_format[bottom_screen->framebuffer_info[bottom_screen->index].GetPixelFormat()]; - const auto bottom_fb = bottom_screen->framebuffer_info[bottom_screen->index]; + u8 bytes_per_pixel = bpp_per_format[bottom_screen->GetPixelFormat()]; + const auto& bottom_fb = *bottom_screen; if (bottom_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) { CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_BOTTOM, bottom_fb.address_left, 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, 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; - fb_info.stride = FRAMEBUFFER_WIDTH * bytes_per_pixel; - system.GPU().SetBufferSwap(1, fb_info); + bottom_screen->address_left = FRAMEBUFFER_SAVE_AREA_BOTTOM; + bottom_screen->stride = FRAMEBUFFER_WIDTH * bytes_per_pixel; + force_buffer_swap[1] = true; } else { 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); - rb.Push(ResultSuccess); + // Wait for VBlank + auto vblank_callback = std::make_shared(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) { @@ -805,31 +877,35 @@ void GSP_GPU::RestoreVramSysArea(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_GSP, "called"); - if (saved_vram) { - auto vram = system.Memory().GetPointer(Memory::VRAM_VADDR); - std::memcpy(vram, saved_vram.get().data(), Memory::VRAM_SIZE); - system.Memory().RasterizerFlushVirtualRegion(Memory::VRAM_VADDR, Memory::VRAM_SIZE, - Memory::FlushMode::Invalidate); + const auto session_data = GetSessionData(ctx.Session()); + if (False(session_data->relay_queue_flags & RelayEventQueueFlags::AllowSaveVramSysArea)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultNoSaveVRAMSysAreaPerm); + return; } - auto top_screen = GetFrameBufferInfo(active_thread_id, 0); - if (top_screen) { - system.GPU().SetBufferSwap(0, top_screen->framebuffer_info[top_screen->index]); - } else { + 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."); - } - - 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."); + force_buffer_swap[0] = true; } - // Real GSP waits for VBlank here, but we don't need it (?). + 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; + } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); + vram_backup_handler.RestoreVRAMWithState(VramBackupHandler::State::STATE_0); + + auto vblank_callback = std::make_shared( + 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, @@ -861,7 +937,7 @@ Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx, gpu.PicaCore().vs_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, ErrorLevel::Success}; } @@ -870,14 +946,14 @@ Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx, if (blocking) { // TODO: The thread should be put to sleep until acquired. - ASSERT_MSG(active_thread_id == std::numeric_limits::max(), + ASSERT_MSG(thread_id_with_rights == std::numeric_limits::max(), "Sleeping for GPU right is not yet supported."); - } else if (active_thread_id != std::numeric_limits::max()) { + } else if (thread_id_with_rights != std::numeric_limits::max()) { return {ErrorDescription::Busy, ErrorModule::GX, ErrorSummary::WouldBlock, 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; return ResultSuccess; } @@ -904,16 +980,17 @@ void GSP_GPU::AcquireRight(Kernel::HLERequestContext& ctx) { } void GSP_GPU::ReleaseRight(const SessionData* session_data) { - ASSERT_MSG(active_thread_id == session_data->thread_id, - "Wrong thread tried to release GPU right"); - active_thread_id = std::numeric_limits::max(); - active_client_thread_id = std::numeric_limits::max(); + if (thread_id_with_rights == session_data->thread_id) { + thread_id_with_rights = std::numeric_limits::max(); + active_client_thread_id = std::numeric_limits::max(); + } } void GSP_GPU::ReleaseRight(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const SessionData* session_data = GetSessionData(ctx.Session()); + // Success even if wrong thread calls this function. ReleaseRight(session_data); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); @@ -976,14 +1053,16 @@ void GSP_GPU::serialize(Archive& ar, const unsigned int) { DEBUG_SERIALIZATION_POINT; ar& boost::serialization::base_object(*this); ar & shared_memory; - ar & active_thread_id; + ar & thread_id_with_rights; ar & active_client_thread_id; ar & first_initialization; ar & used_thread_ids; - ar & saved_vram; ar & delay_texture_copy_completion; ar & pending_interrupts; ar & perf_recorder; + ar & cached_framebuffer_infos; + ar & force_buffer_swap; + ar & vram_backup_handler; } SERIALIZE_IMPL(GSP_GPU) @@ -1051,6 +1130,8 @@ void SessionData::serialize(Archive& ar, const unsigned int) { ar & interrupt_event; ar & thread_id; ar & registered; + ar & relay_queue_flags; + ar & on_vblank_event; } SERIALIZE_IMPL(SessionData) @@ -1067,4 +1148,64 @@ SessionData::~SessionData() { 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 + +SERIALIZE_EXPORT_IMPL(Service::GSP::GSP_GPU::ThreadCallback) diff --git a/src/core/hle/service/gsp/gsp_gpu.h b/src/core/hle/service/gsp/gsp_gpu.h index 3afbb0916..5ce90d120 100644 --- a/src/core/hle/service/gsp/gsp_gpu.h +++ b/src/core/hle/service/gsp/gsp_gpu.h @@ -18,6 +18,7 @@ #include "core/hle/service/gsp/gsp_command.h" #include "core/hle/service/gsp/gsp_interrupt.h" #include "core/hle/service/service.h" +#include "core/memory.h" namespace Core { class System; @@ -45,6 +46,17 @@ struct FrameBufferInfo { u32 GetPixelFormat() { return format & PIXEL_FORMAT_MASK; } + + template + 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"); @@ -61,6 +73,20 @@ static_assert(sizeof(FrameBufferUpdate) == 0x40, "Struct has incorrect size"); static_assert(offsetof(FrameBufferUpdate, framebuffer_info[1]) == 0x20, "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_POW2 = 256; 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_BOTTOM = Memory::VRAM_VADDR + 0x4C7800; +constexpr size_t VRAM_BACKUP_BANK_SIZE = 0x80000; + class GSP_GPU; class SessionData : public Kernel::SessionRequestHandler::SessionDataBase { @@ -88,6 +116,10 @@ public: u32 thread_id; /// Whether RegisterInterruptRelayQueue was called for this session bool registered = false; + /// Current relay queue flags + RelayEventQueueFlags relay_queue_flags{}; + /// Event to be signaled on VBlank + std::shared_ptr on_vblank_event{}; private: template @@ -95,6 +127,68 @@ private: friend class boost::serialization::access; }; +class VramBackupHandler { +public: + VramBackupHandler() : system(Core::Global()) { + 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, BANK_COUNT> vram_backup{}; + + // TODO(PabloMK7): Figure out what this means, it's taken from + // GSP decompilation. + static constexpr std::array 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 + 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 { public: 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 * requesting. * @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 GetFrameBufferInfo(u32 thread_id, u32 screen_index, + bool force_update = false); /// Gets a pointer to a thread command buffer in GSP shared memory CommandBuffer* GetCommandBuffer(u32 thread_id); @@ -128,7 +223,7 @@ public: * Retreives the ID of the thread with GPU rights. */ u32 GetActiveThreadId() { - return active_thread_id; + return thread_id_with_rights; } /** @@ -138,6 +233,8 @@ public: return active_client_thread_id; } + class ThreadCallback; + private: /** * Signals that the specified interrupt type has occurred to userland code for the specified GSP @@ -399,16 +496,13 @@ private: std::shared_ptr shared_memory; /// Thread id that currently has GPU rights or std::numeric_limits::max() if none. - u32 active_thread_id = std::numeric_limits::max(); + u32 thread_id_with_rights = std::numeric_limits::max(); /// Thread id of the client thread that has GPU rights u32 active_client_thread_id = std::numeric_limits::max(); bool first_initialization = true; - /// VRAM copy saved using SaveVramSysArea. - boost::optional> saved_vram; - /// Maximum number of threads that can be registered at the same time in the GSP module. static constexpr u32 MaxGSPThreads = 4; @@ -527,6 +621,11 @@ private: Core::TimingEventType* SignalInterruptEventType = nullptr; + std::array cached_framebuffer_infos; + std::array force_buffer_swap{}; + + VramBackupHandler vram_backup_handler; + friend class SessionData; template @@ -539,3 +638,4 @@ private: BOOST_CLASS_EXPORT_KEY(Service::GSP::SessionData) BOOST_CLASS_EXPORT_KEY(Service::GSP::GSP_GPU) SERVICE_CONSTRUCT(Service::GSP::GSP_GPU) +BOOST_CLASS_EXPORT_KEY(Service::GSP::GSP_GPU::ThreadCallback) diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 5fb98e4fd..598942dc8 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -486,13 +486,13 @@ void GPU::MemoryTransfer() { } 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 impl->signal_interrupt(Service::GSP::InterruptId::PDC0, 0); impl->signal_interrupt(Service::GSP::InterruptId::PDC1, 0); + // Present renderered frame. + impl->renderer->SwapBuffers(); + // Reschedule recurrent event impl->timing.ScheduleEvent(FRAME_TICKS - cycles_late, impl->vblank_event); }