From 109f5fb730530bbd5c5dc2af0220c0479e8a3d4a Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sat, 20 Dec 2025 21:34:23 +0100 Subject: [PATCH] core: Fix home menu garbled suspended application image (#1518) --- src/core/hle/service/gsp/gsp_gpu.cpp | 125 ++++++++++++++++++++++----- src/core/hle/service/gsp/gsp_gpu.h | 6 ++ 2 files changed, 107 insertions(+), 24 deletions(-) diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp index dc33488b4..b488a8ce2 100644 --- a/src/core/hle/service/gsp/gsp_gpu.cpp +++ b/src/core/hle/service/gsp/gsp_gpu.cpp @@ -467,6 +467,8 @@ void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) { void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + LOG_DEBUG(Service_GSP, "called"); + if (active_thread_id == std::numeric_limits::max()) { LOG_WARNING(Service_GSP, "Called without an active thread."); @@ -503,28 +505,64 @@ void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) { rb.Push(ResultSuccess); rb.PushRaw(top_entry); rb.PushRaw(bottom_entry); - - LOG_WARNING(Service_GSP, "called"); } -static void CopyFrameBuffer(Core::System& system, VAddr dst, VAddr src, u32 stride, u32 lines) { - auto dst_ptr = system.Memory().GetPointer(dst); - const auto src_ptr = system.Memory().GetPointer(src); +static void CopyFrameBuffer(Core::System& system, VAddr dst, VAddr src, u32 dst_stride, + u32 src_stride, u32 lines) { + auto* dst_ptr = system.Memory().GetPointer(dst); + const auto* src_ptr = system.Memory().GetPointer(src); + if (!dst_ptr || !src_ptr) { LOG_WARNING(Service_GSP, "Could not resolve pointers for framebuffer capture, skipping screen."); return; } - system.Memory().RasterizerFlushVirtualRegion(src, stride * lines, Memory::FlushMode::Flush); - std::memcpy(dst_ptr, src_ptr, stride * lines); - system.Memory().RasterizerFlushVirtualRegion(dst, stride * lines, + system.Memory().RasterizerFlushVirtualRegion(src, src_stride * lines, Memory::FlushMode::Flush); + + const u32 copy_bytes_per_line = std::min(src_stride, dst_stride); + for (u32 y = 0; y < lines; ++y) { + std::memcpy(dst_ptr, src_ptr, copy_bytes_per_line); + src_ptr += src_stride; + dst_ptr += dst_stride; + } + + system.Memory().RasterizerFlushVirtualRegion(dst, dst_stride * lines, + Memory::FlushMode::Invalidate); +} + +static void ClearFramebuffer(Core::System& system, VAddr dst, u32 dst_stride, u32 lines) { + auto* dst_ptr = system.Memory().GetPointer(dst); + + if (!dst_ptr) { + LOG_WARNING(Service_GSP, + "Could not resolve pointers for framebuffer clear, skipping screen."); + return; + } + + const u32 set_bytes_per_line = dst_stride; + for (u32 y = 0; y < lines; ++y) { + std::memset(dst_ptr, 0, set_bytes_per_line); + dst_ptr += dst_stride; + } + + system.Memory().RasterizerFlushVirtualRegion(dst, dst_stride * lines, Memory::FlushMode::Invalidate); } void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + LOG_DEBUG(Service_GSP, "called"); + + // Taken from GSP decomp. TODO: GSP seems to so something special + // when the fb format results in bpp of 0 or 4, most likely clearing + // it, more research needed. + static const u8 bpp_per_format[] = {// Valid values + 4, 3, 2, 2, 2, + // Invalid values + 0, 0, 0}; + if (active_thread_id == std::numeric_limits::max()) { LOG_WARNING(Service_GSP, "Called without an active thread."); @@ -534,47 +572,71 @@ void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) { return; } - LOG_INFO(Service_GSP, "called"); - - // TODO: This should also save LCD register state. 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); - const auto top_screen = GetFrameBufferInfo(active_thread_id, 0); + auto top_screen = GetFrameBufferInfo(active_thread_id, 0); 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]; - if (top_fb.address_left) { + if (top_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) { CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_LEFT, top_fb.address_left, - top_fb.stride, TOP_FRAMEBUFFER_HEIGHT); + FRAMEBUFFER_WIDTH * bytes_per_pixel, top_fb.stride, + TOP_FRAMEBUFFER_HEIGHT); } else { - LOG_WARNING(Service_GSP, "No framebuffer bound to top left screen, skipping capture."); + LOG_DEBUG(Service_GSP, "Invalid framebuffer bound to top left screen, clearing..."); + ClearFramebuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_LEFT, + FRAMEBUFFER_WIDTH * bytes_per_pixel, TOP_FRAMEBUFFER_HEIGHT); } - if (top_fb.address_right) { + if (top_fb.address_right && bytes_per_pixel != 0 && bytes_per_pixel != 4) { CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_RIGHT, top_fb.address_right, - top_fb.stride, TOP_FRAMEBUFFER_HEIGHT); + FRAMEBUFFER_WIDTH * bytes_per_pixel, top_fb.stride, + TOP_FRAMEBUFFER_HEIGHT); } else { - LOG_WARNING(Service_GSP, "No framebuffer bound to top right screen, skipping capture."); + LOG_DEBUG(Service_GSP, "Invalid framebuffer bound to top right screen, clearing..."); + ClearFramebuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_RIGHT, + 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); } else { LOG_WARNING(Service_GSP, "No top screen bound, skipping capture."); } - const auto bottom_screen = GetFrameBufferInfo(active_thread_id, 1); + auto bottom_screen = GetFrameBufferInfo(active_thread_id, 1); 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]; - if (bottom_fb.address_left) { + if (bottom_fb.address_left && bytes_per_pixel != 0 && bytes_per_pixel != 4) { CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_BOTTOM, bottom_fb.address_left, - bottom_fb.stride, BOTTOM_FRAMEBUFFER_HEIGHT); + FRAMEBUFFER_WIDTH * bytes_per_pixel, bottom_fb.stride, + BOTTOM_FRAMEBUFFER_HEIGHT); } else { - LOG_WARNING(Service_GSP, "No framebuffer bound to bottom screen, skipping capture."); + LOG_DEBUG(Service_GSP, "Invalid framebuffer bound to bottom screen, clearing..."); + 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); } else { LOG_WARNING(Service_GSP, "No bottom screen bound, skipping capture."); } + // Real GSP waits for VBlank here, but we don't need it (?). + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultSuccess); } @@ -582,16 +644,31 @@ void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) { void GSP_GPU::RestoreVramSysArea(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - LOG_INFO(Service_GSP, "called"); + LOG_DEBUG(Service_GSP, "called"); if (saved_vram) { - // TODO: This should also restore LCD register state. 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); } + 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); rb.Push(ResultSuccess); } diff --git a/src/core/hle/service/gsp/gsp_gpu.h b/src/core/hle/service/gsp/gsp_gpu.h index f5f810c44..0864e37de 100644 --- a/src/core/hle/service/gsp/gsp_gpu.h +++ b/src/core/hle/service/gsp/gsp_gpu.h @@ -30,6 +30,8 @@ class SharedMemory; namespace Service::GSP { struct FrameBufferInfo { + static constexpr u32 PIXEL_FORMAT_MASK = 0x7; + u32 active_fb; // 0 = first, 1 = second u32 address_left; u32 address_right; @@ -37,6 +39,10 @@ struct FrameBufferInfo { u32 format; // maps to 0x1EF00X70 ? u32 shown_fb; // maps to 0x1EF00X78 ? u32 unknown; + + u32 GetPixelFormat() { + return format & PIXEL_FORMAT_MASK; + } }; static_assert(sizeof(FrameBufferInfo) == 0x1c, "Struct has incorrect size");