diff --git a/src/core/hle/service/apt/applet_manager.cpp b/src/core/hle/service/apt/applet_manager.cpp index b9aff6fa3b..68f3026eeb 100644 --- a/src/core/hle/service/apt/applet_manager.cpp +++ b/src/core/hle/service/apt/applet_manager.cpp @@ -232,7 +232,7 @@ void AppletManager::CancelAndSendParameter(const MessageParameter& parameter) { parameter.sender_id); if (parameter.buffer.size() >= sizeof(CaptureBufferInfo)) { - SendCaptureBufferInfo(parameter.buffer); + SetCaptureInfo(parameter.buffer); CaptureFrameBuffers(); } @@ -511,6 +511,8 @@ ResultCode AppletManager::PrepareToStartLibraryApplet(AppletId applet_id) { last_library_launcher_slot = active_slot; last_prepared_library_applet = applet_id; + capture_buffer_info.reset(); + auto cfg = Service::CFG::GetModule(system); auto process = NS::LaunchTitle(FS::MediaType::NAND, GetTitleIdForApplet(applet_id, cfg->GetRegionValue())); @@ -849,6 +851,9 @@ ResultCode AppletManager::PrepareToJumpToHomeMenu() { } last_jump_to_home_slot = active_slot; + + capture_buffer_info.reset(); + if (last_jump_to_home_slot == AppletSlot::Application) { EnsureHomeMenuLoaded(); } @@ -1250,6 +1255,8 @@ ResultCode AppletManager::PrepareToStartApplication(u64 title_id, FS::MediaType app_start_parameters->next_title_id = title_id; app_start_parameters->next_media_type = media_type; + capture_buffer_info.reset(); + return RESULT_SUCCESS; } @@ -1359,48 +1366,74 @@ void AppletManager::EnsureHomeMenuLoaded() { } } -static void CaptureFrameBuffer(Core::System& system, u32 capture_offset, VAddr src, u32 height, - u32 format) { - static constexpr auto screen_capture_base_vaddr = static_cast(0x1F500000); - static constexpr auto screen_width = 240; - static constexpr auto screen_width_pow2 = 256; - const auto bpp = format < 2 ? 3 : 2; +static u32 GetDisplayBufferModePixelSize(DisplayBufferMode mode) { + switch (mode) { + // NOTE: APT does in fact use pixel size 3 for R8G8B8A8 captures. + case DisplayBufferMode::R8G8B8A8: + case DisplayBufferMode::R8G8B8: + return 3; + case DisplayBufferMode::R5G6B5: + case DisplayBufferMode::R5G5B5A1: + case DisplayBufferMode::R4G4B4A4: + return 2; + case DisplayBufferMode::Unimportable: + return 0; + default: + UNREACHABLE_MSG("Unknown display buffer mode {}", mode); + return 0; + } +} - Memory::RasterizerFlushVirtualRegion(src, screen_width * height * bpp, +static void CaptureFrameBuffer(Core::System& system, u32 capture_offset, VAddr src, u32 height, + DisplayBufferMode mode) { + const auto bpp = GetDisplayBufferModePixelSize(mode); + if (bpp == 0) { + return; + } + + Memory::RasterizerFlushVirtualRegion(src, GSP::FRAMEBUFFER_WIDTH * height * bpp, Memory::FlushMode::Flush); - auto dst_vaddr = screen_capture_base_vaddr + capture_offset; + // Address in VRAM that APT copies framebuffer captures to. + constexpr VAddr screen_capture_base_vaddr = Memory::VRAM_VADDR + 0x500000; + const auto dst_vaddr = screen_capture_base_vaddr + capture_offset; auto dst_ptr = system.Memory().GetPointer(dst_vaddr); + if (!dst_ptr) { + LOG_ERROR(Service_APT, + "Could not retrieve framebuffer capture destination buffer, skipping screen."); + return; + } + const auto src_ptr = system.Memory().GetPointer(src); + if (!src_ptr) { + LOG_ERROR(Service_APT, + "Could not retrieve framebuffer capture source buffer, skipping screen."); + return; + } + for (u32 y = 0; y < height; y++) { - for (u32 x = 0; x < screen_width; x++) { - auto dst_offset = - VideoCore::GetMortonOffset(x, y, bpp) + (y & ~7) * screen_width_pow2 * bpp; - auto src_offset = bpp * (screen_width * y + x); + for (u32 x = 0; x < GSP::FRAMEBUFFER_WIDTH; x++) { + const auto dst_offset = VideoCore::GetMortonOffset(x, y, bpp) + + (y & ~7) * GSP::FRAMEBUFFER_WIDTH_POW2 * bpp; + const auto src_offset = bpp * (GSP::FRAMEBUFFER_WIDTH * y + x); std::memcpy(dst_ptr + dst_offset, src_ptr + src_offset, bpp); } } - Memory::RasterizerFlushVirtualRegion(dst_vaddr, screen_width_pow2 * height * bpp, + Memory::RasterizerFlushVirtualRegion(dst_vaddr, GSP::FRAMEBUFFER_WIDTH_POW2 * height * bpp, Memory::FlushMode::Invalidate); } void AppletManager::CaptureFrameBuffers() { - auto gsp = - Core::System::GetInstance().ServiceManager().GetService("gsp::Gpu"); - auto active_thread_id = gsp->GetActiveThreadId(); - auto top_screen = gsp->GetFrameBufferInfo(active_thread_id, 0); - auto bottom_screen = gsp->GetFrameBufferInfo(active_thread_id, 1); - - auto top_fb = top_screen->framebuffer_info[top_screen->index]; - auto bottom_fb = bottom_screen->framebuffer_info[bottom_screen->index]; - - CaptureFrameBuffer(system, capture_info->bottom_screen_left_offset, bottom_fb.address_left, 320, + CaptureFrameBuffer(system, capture_info->bottom_screen_left_offset, + GSP::FRAMEBUFFER_SAVE_AREA_BOTTOM, GSP::BOTTOM_FRAMEBUFFER_HEIGHT, capture_info->bottom_screen_format); - CaptureFrameBuffer(system, capture_info->top_screen_left_offset, top_fb.address_left, 400, + CaptureFrameBuffer(system, capture_info->top_screen_left_offset, + GSP::FRAMEBUFFER_SAVE_AREA_TOP_LEFT, GSP::TOP_FRAMEBUFFER_HEIGHT, capture_info->top_screen_format); if (capture_info->is_3d) { - CaptureFrameBuffer(system, capture_info->top_screen_right_offset, top_fb.address_right, 400, + CaptureFrameBuffer(system, capture_info->top_screen_right_offset, + GSP::FRAMEBUFFER_SAVE_AREA_TOP_RIGHT, GSP::TOP_FRAMEBUFFER_HEIGHT, capture_info->top_screen_format); } } diff --git a/src/core/hle/service/apt/applet_manager.h b/src/core/hle/service/apt/applet_manager.h index 1b29108de8..13bbbd2d67 100644 --- a/src/core/hle/service/apt/applet_manager.h +++ b/src/core/hle/service/apt/applet_manager.h @@ -210,6 +210,15 @@ private: friend class boost::serialization::access; }; +enum class DisplayBufferMode : u32_le { + R8G8B8A8 = 0, + R8G8B8 = 1, + R5G6B5 = 2, + R5G5B5A1 = 3, + R4G4B4A4 = 4, + Unimportable = 0xFFFFFFFF, +}; + /// Used by the application to pass information about the current framebuffer to applets. struct CaptureBufferInfo { u32_le size; @@ -217,10 +226,10 @@ struct CaptureBufferInfo { INSERT_PADDING_BYTES(0x3); // Padding for alignment u32_le top_screen_left_offset; u32_le top_screen_right_offset; - u32_le top_screen_format; + DisplayBufferMode top_screen_format; u32_le bottom_screen_left_offset; u32_le bottom_screen_right_offset; - u32_le bottom_screen_format; + DisplayBufferMode bottom_screen_format; private: template @@ -333,16 +342,27 @@ public: } return buffer; } + void SetCaptureInfo(std::vector buffer) { + ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small."); + + capture_info.emplace(); + std::memcpy(&capture_info.get(), buffer.data(), sizeof(CaptureBufferInfo)); + } + std::vector ReceiveCaptureBufferInfo() { - std::vector buffer = GetCaptureInfo(); - capture_info.reset(); + std::vector buffer; + if (capture_buffer_info) { + buffer.resize(sizeof(CaptureBufferInfo)); + std::memcpy(buffer.data(), &capture_buffer_info.get(), sizeof(CaptureBufferInfo)); + capture_buffer_info.reset(); + } return buffer; } void SendCaptureBufferInfo(std::vector buffer) { ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small."); - capture_info.emplace(); - std::memcpy(&capture_info.get(), buffer.data(), sizeof(CaptureBufferInfo)); + capture_buffer_info.emplace(); + std::memcpy(&capture_buffer_info.get(), buffer.data(), sizeof(CaptureBufferInfo)); } ResultCode PrepareToStartApplication(u64 title_id, FS::MediaType media_type); @@ -395,6 +415,7 @@ private: boost::optional deliver_arg{}; boost::optional capture_info; + boost::optional capture_buffer_info; static constexpr std::size_t NumAppletSlot = 4; @@ -500,6 +521,8 @@ private: ar& delayed_parameter; ar& app_start_parameters; ar& deliver_arg; + ar& capture_info; + ar& capture_buffer_info; ar& active_slot; ar& last_library_launcher_slot; ar& last_prepared_library_applet; diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp index f767fea702..abb838fde0 100644 --- a/src/core/hle/service/gsp/gsp_gpu.cpp +++ b/src/core/hle/service/gsp/gsp_gpu.cpp @@ -704,18 +704,73 @@ void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) { 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); + if (!dst_ptr || !src_ptr) { + LOG_WARNING(Service_GSP, + "Could not resolve pointers for framebuffer capture, skipping screen."); + return; + } + + Memory::RasterizerFlushVirtualRegion(src, stride * lines, Memory::FlushMode::Flush); + std::memcpy(dst_ptr, src_ptr, stride * lines); + Memory::RasterizerFlushVirtualRegion(dst, stride * lines, Memory::FlushMode::Invalidate); +} + void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + if (active_thread_id == std::numeric_limits::max()) { + LOG_WARNING(Service_GSP, "Called without an active thread."); + + // TODO: Find the right error code. + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(-1); + return; + } + LOG_INFO(Service_GSP, "called"); - // TODO: This should also DMA framebuffers into VRAM and save LCD register state. + // TODO: This should also save LCD register state. Memory::RasterizerFlushVirtualRegion(Memory::VRAM_VADDR, Memory::VRAM_SIZE, Memory::FlushMode::Flush); - auto vram = system.Memory().GetPointer(Memory::VRAM_VADDR); + 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); + if (top_screen) { + const auto top_fb = top_screen->framebuffer_info[top_screen->index]; + if (top_fb.address_left) { + CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_LEFT, top_fb.address_left, + top_fb.stride, TOP_FRAMEBUFFER_HEIGHT); + } else { + LOG_WARNING(Service_GSP, "No framebuffer bound to top left screen, skipping capture."); + } + if (top_fb.address_right) { + CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_TOP_RIGHT, top_fb.address_right, + top_fb.stride, TOP_FRAMEBUFFER_HEIGHT); + } else { + LOG_WARNING(Service_GSP, "No framebuffer bound to top right screen, skipping capture."); + } + } else { + LOG_WARNING(Service_GSP, "No top screen bound, skipping capture."); + } + + const auto bottom_screen = GetFrameBufferInfo(active_thread_id, 1); + if (bottom_screen) { + const auto bottom_fb = bottom_screen->framebuffer_info[bottom_screen->index]; + if (bottom_fb.address_left) { + CopyFrameBuffer(system, FRAMEBUFFER_SAVE_AREA_BOTTOM, bottom_fb.address_left, + bottom_fb.stride, BOTTOM_FRAMEBUFFER_HEIGHT); + } else { + LOG_WARNING(Service_GSP, "No framebuffer bound to bottom screen, skipping capture."); + } + } else { + LOG_WARNING(Service_GSP, "No bottom screen bound, skipping capture."); + } + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); } @@ -819,7 +874,7 @@ SessionData* GSP_GPU::FindRegisteredThreadData(u32 thread_id) { return nullptr; } -GSP_GPU::GSP_GPU(Core::System& system) : ServiceFramework("gsp::Gpu", 2), system(system) { +GSP_GPU::GSP_GPU(Core::System& system) : ServiceFramework("gsp::Gpu", 4), system(system) { static const FunctionInfo functions[] = { // clang-format off {0x0001, &GSP_GPU::WriteHWRegs, "WriteHWRegs"}, diff --git a/src/core/hle/service/gsp/gsp_gpu.h b/src/core/hle/service/gsp/gsp_gpu.h index cf976654ef..7de8191a24 100644 --- a/src/core/hle/service/gsp/gsp_gpu.h +++ b/src/core/hle/service/gsp/gsp_gpu.h @@ -186,6 +186,17 @@ struct CommandBuffer { }; static_assert(sizeof(CommandBuffer) == 0x200, "CommandBuffer struct has incorrect size"); +constexpr u32 FRAMEBUFFER_WIDTH = 240; +constexpr u32 FRAMEBUFFER_WIDTH_POW2 = 256; +constexpr u32 TOP_FRAMEBUFFER_HEIGHT = 400; +constexpr u32 BOTTOM_FRAMEBUFFER_HEIGHT = 320; +constexpr u32 FRAMEBUFFER_HEIGHT_POW2 = 512; + +// These are the VRAM addresses that GSP copies framebuffers to in SaveVramSysArea. +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; + class GSP_GPU; class SessionData : public Kernel::SessionRequestHandler::SessionDataBase {