core: Fix home menu garbled suspended application image (#1518)
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux (appimage) (push) Has been cancelled
citra-build / linux (appimage-wayland) (push) Has been cancelled
citra-build / linux (fresh) (push) Has been cancelled
citra-build / macos (arm64) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / android (push) Has been cancelled
citra-build / docker (push) Has been cancelled
citra-format / clang-format (push) Has been cancelled
citra-transifex / transifex (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled

This commit is contained in:
PabloMK7 2025-12-20 21:34:23 +01:00 committed by GitHub
parent f7eaf13a4d
commit 109f5fb730
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 107 additions and 24 deletions

View File

@ -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<u32>::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<u32>::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<u8>(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);
}

View File

@ -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");