mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	service/gsp: Implement saving of framebuffers in SaveVramSysArea. (#6821)
* service/gsp: Implement saving of framebuffers in SaveVramSysArea. * Address review comments. * service/apt: Separate capture info and capture buffer info. The former is used with the RequestForSysApplet message and GetCaptureInfo. The latter is used with SendCaptureBufferInfo and ReceiveCaptureBufferInfo.
This commit is contained in:
		
							parent
							
								
									bb364d9bc0
								
							
						
					
					
						commit
						964f9ee3cf
					
				
					 4 changed files with 157 additions and 35 deletions
				
			
		|  | @ -232,7 +232,7 @@ void AppletManager::CancelAndSendParameter(const MessageParameter& parameter) { | ||||||
|                       parameter.sender_id); |                       parameter.sender_id); | ||||||
| 
 | 
 | ||||||
|             if (parameter.buffer.size() >= sizeof(CaptureBufferInfo)) { |             if (parameter.buffer.size() >= sizeof(CaptureBufferInfo)) { | ||||||
|                 SendCaptureBufferInfo(parameter.buffer); |                 SetCaptureInfo(parameter.buffer); | ||||||
|                 CaptureFrameBuffers(); |                 CaptureFrameBuffers(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | @ -511,6 +511,8 @@ ResultCode AppletManager::PrepareToStartLibraryApplet(AppletId applet_id) { | ||||||
|     last_library_launcher_slot = active_slot; |     last_library_launcher_slot = active_slot; | ||||||
|     last_prepared_library_applet = applet_id; |     last_prepared_library_applet = applet_id; | ||||||
| 
 | 
 | ||||||
|  |     capture_buffer_info.reset(); | ||||||
|  | 
 | ||||||
|     auto cfg = Service::CFG::GetModule(system); |     auto cfg = Service::CFG::GetModule(system); | ||||||
|     auto process = |     auto process = | ||||||
|         NS::LaunchTitle(FS::MediaType::NAND, GetTitleIdForApplet(applet_id, cfg->GetRegionValue())); |         NS::LaunchTitle(FS::MediaType::NAND, GetTitleIdForApplet(applet_id, cfg->GetRegionValue())); | ||||||
|  | @ -849,6 +851,9 @@ ResultCode AppletManager::PrepareToJumpToHomeMenu() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     last_jump_to_home_slot = active_slot; |     last_jump_to_home_slot = active_slot; | ||||||
|  | 
 | ||||||
|  |     capture_buffer_info.reset(); | ||||||
|  | 
 | ||||||
|     if (last_jump_to_home_slot == AppletSlot::Application) { |     if (last_jump_to_home_slot == AppletSlot::Application) { | ||||||
|         EnsureHomeMenuLoaded(); |         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_title_id = title_id; | ||||||
|     app_start_parameters->next_media_type = media_type; |     app_start_parameters->next_media_type = media_type; | ||||||
| 
 | 
 | ||||||
|  |     capture_buffer_info.reset(); | ||||||
|  | 
 | ||||||
|     return RESULT_SUCCESS; |     return RESULT_SUCCESS; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -1359,48 +1366,74 @@ void AppletManager::EnsureHomeMenuLoaded() { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void CaptureFrameBuffer(Core::System& system, u32 capture_offset, VAddr src, u32 height, | static u32 GetDisplayBufferModePixelSize(DisplayBufferMode mode) { | ||||||
|                                u32 format) { |     switch (mode) { | ||||||
|     static constexpr auto screen_capture_base_vaddr = static_cast<VAddr>(0x1F500000); |     // NOTE: APT does in fact use pixel size 3 for R8G8B8A8 captures.
 | ||||||
|     static constexpr auto screen_width = 240; |     case DisplayBufferMode::R8G8B8A8: | ||||||
|     static constexpr auto screen_width_pow2 = 256; |     case DisplayBufferMode::R8G8B8: | ||||||
|     const auto bpp = format < 2 ? 3 : 2; |         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); |                                          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); |     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); |     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 y = 0; y < height; y++) { | ||||||
|         for (u32 x = 0; x < screen_width; x++) { |         for (u32 x = 0; x < GSP::FRAMEBUFFER_WIDTH; x++) { | ||||||
|             auto dst_offset = |             const auto dst_offset = VideoCore::GetMortonOffset(x, y, bpp) + | ||||||
|                 VideoCore::GetMortonOffset(x, y, bpp) + (y & ~7) * screen_width_pow2 * bpp; |                                     (y & ~7) * GSP::FRAMEBUFFER_WIDTH_POW2 * bpp; | ||||||
|             auto src_offset = bpp * (screen_width * y + x); |             const auto src_offset = bpp * (GSP::FRAMEBUFFER_WIDTH * y + x); | ||||||
|             std::memcpy(dst_ptr + dst_offset, src_ptr + src_offset, bpp); |             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); |                                          Memory::FlushMode::Invalidate); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AppletManager::CaptureFrameBuffers() { | void AppletManager::CaptureFrameBuffers() { | ||||||
|     auto gsp = |     CaptureFrameBuffer(system, capture_info->bottom_screen_left_offset, | ||||||
|         Core::System::GetInstance().ServiceManager().GetService<Service::GSP::GSP_GPU>("gsp::Gpu"); |                        GSP::FRAMEBUFFER_SAVE_AREA_BOTTOM, GSP::BOTTOM_FRAMEBUFFER_HEIGHT, | ||||||
|     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, |  | ||||||
|                        capture_info->bottom_screen_format); |                        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); |                        capture_info->top_screen_format); | ||||||
|     if (capture_info->is_3d) { |     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); |                            capture_info->top_screen_format); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -210,6 +210,15 @@ private: | ||||||
|     friend class boost::serialization::access; |     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.
 | /// Used by the application to pass information about the current framebuffer to applets.
 | ||||||
| struct CaptureBufferInfo { | struct CaptureBufferInfo { | ||||||
|     u32_le size; |     u32_le size; | ||||||
|  | @ -217,10 +226,10 @@ struct CaptureBufferInfo { | ||||||
|     INSERT_PADDING_BYTES(0x3); // Padding for alignment
 |     INSERT_PADDING_BYTES(0x3); // Padding for alignment
 | ||||||
|     u32_le top_screen_left_offset; |     u32_le top_screen_left_offset; | ||||||
|     u32_le top_screen_right_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_left_offset; | ||||||
|     u32_le bottom_screen_right_offset; |     u32_le bottom_screen_right_offset; | ||||||
|     u32_le bottom_screen_format; |     DisplayBufferMode bottom_screen_format; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     template <class Archive> |     template <class Archive> | ||||||
|  | @ -333,16 +342,27 @@ public: | ||||||
|         } |         } | ||||||
|         return buffer; |         return buffer; | ||||||
|     } |     } | ||||||
|  |     void SetCaptureInfo(std::vector<u8> 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<u8> ReceiveCaptureBufferInfo() { |     std::vector<u8> ReceiveCaptureBufferInfo() { | ||||||
|         std::vector<u8> buffer = GetCaptureInfo(); |         std::vector<u8> buffer; | ||||||
|         capture_info.reset(); |         if (capture_buffer_info) { | ||||||
|  |             buffer.resize(sizeof(CaptureBufferInfo)); | ||||||
|  |             std::memcpy(buffer.data(), &capture_buffer_info.get(), sizeof(CaptureBufferInfo)); | ||||||
|  |             capture_buffer_info.reset(); | ||||||
|  |         } | ||||||
|         return buffer; |         return buffer; | ||||||
|     } |     } | ||||||
|     void SendCaptureBufferInfo(std::vector<u8> buffer) { |     void SendCaptureBufferInfo(std::vector<u8> buffer) { | ||||||
|         ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small."); |         ASSERT_MSG(buffer.size() >= sizeof(CaptureBufferInfo), "CaptureBufferInfo is too small."); | ||||||
| 
 | 
 | ||||||
|         capture_info.emplace(); |         capture_buffer_info.emplace(); | ||||||
|         std::memcpy(&capture_info.get(), buffer.data(), sizeof(CaptureBufferInfo)); |         std::memcpy(&capture_buffer_info.get(), buffer.data(), sizeof(CaptureBufferInfo)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ResultCode PrepareToStartApplication(u64 title_id, FS::MediaType media_type); |     ResultCode PrepareToStartApplication(u64 title_id, FS::MediaType media_type); | ||||||
|  | @ -395,6 +415,7 @@ private: | ||||||
|     boost::optional<DeliverArg> deliver_arg{}; |     boost::optional<DeliverArg> deliver_arg{}; | ||||||
| 
 | 
 | ||||||
|     boost::optional<CaptureBufferInfo> capture_info; |     boost::optional<CaptureBufferInfo> capture_info; | ||||||
|  |     boost::optional<CaptureBufferInfo> capture_buffer_info; | ||||||
| 
 | 
 | ||||||
|     static constexpr std::size_t NumAppletSlot = 4; |     static constexpr std::size_t NumAppletSlot = 4; | ||||||
| 
 | 
 | ||||||
|  | @ -500,6 +521,8 @@ private: | ||||||
|             ar& delayed_parameter; |             ar& delayed_parameter; | ||||||
|             ar& app_start_parameters; |             ar& app_start_parameters; | ||||||
|             ar& deliver_arg; |             ar& deliver_arg; | ||||||
|  |             ar& capture_info; | ||||||
|  |             ar& capture_buffer_info; | ||||||
|             ar& active_slot; |             ar& active_slot; | ||||||
|             ar& last_library_launcher_slot; |             ar& last_library_launcher_slot; | ||||||
|             ar& last_prepared_library_applet; |             ar& last_prepared_library_applet; | ||||||
|  |  | ||||||
|  | @ -704,18 +704,73 @@ void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) { | ||||||
|     LOG_WARNING(Service_GSP, "called"); |     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) { | void GSP_GPU::SaveVramSysArea(Kernel::HLERequestContext& ctx) { | ||||||
|     IPC::RequestParser rp(ctx); |     IPC::RequestParser rp(ctx); | ||||||
| 
 | 
 | ||||||
|  |     if (active_thread_id == std::numeric_limits<u32>::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"); |     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::RasterizerFlushVirtualRegion(Memory::VRAM_VADDR, Memory::VRAM_SIZE, | ||||||
|                                          Memory::FlushMode::Flush); |                                          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<u8>(Memory::VRAM_SIZE)); |     saved_vram.emplace(std::vector<u8>(Memory::VRAM_SIZE)); | ||||||
|     std::memcpy(saved_vram.get().data(), vram, 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); |     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||||
|     rb.Push(RESULT_SUCCESS); |     rb.Push(RESULT_SUCCESS); | ||||||
| } | } | ||||||
|  | @ -819,7 +874,7 @@ SessionData* GSP_GPU::FindRegisteredThreadData(u32 thread_id) { | ||||||
|     return nullptr; |     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[] = { |     static const FunctionInfo functions[] = { | ||||||
|         // clang-format off
 |         // clang-format off
 | ||||||
|         {0x0001, &GSP_GPU::WriteHWRegs, "WriteHWRegs"}, |         {0x0001, &GSP_GPU::WriteHWRegs, "WriteHWRegs"}, | ||||||
|  |  | ||||||
|  | @ -186,6 +186,17 @@ struct CommandBuffer { | ||||||
| }; | }; | ||||||
| static_assert(sizeof(CommandBuffer) == 0x200, "CommandBuffer struct has incorrect size"); | 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 GSP_GPU; | ||||||
| 
 | 
 | ||||||
| class SessionData : public Kernel::SessionRequestHandler::SessionDataBase { | class SessionData : public Kernel::SessionRequestHandler::SessionDataBase { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue