service: nvnflinger: Implement shared buffer
Co-authored-by: Liam <byteslice@airmail.cc>
This commit is contained in:
		| @@ -698,6 +698,8 @@ add_library(core STATIC | |||||||
|     hle/service/nvnflinger/consumer_base.cpp |     hle/service/nvnflinger/consumer_base.cpp | ||||||
|     hle/service/nvnflinger/consumer_base.h |     hle/service/nvnflinger/consumer_base.h | ||||||
|     hle/service/nvnflinger/consumer_listener.h |     hle/service/nvnflinger/consumer_listener.h | ||||||
|  |     hle/service/nvnflinger/fb_share_buffer_manager.cpp | ||||||
|  |     hle/service/nvnflinger/fb_share_buffer_manager.h | ||||||
|     hle/service/nvnflinger/graphic_buffer_producer.cpp |     hle/service/nvnflinger/graphic_buffer_producer.cpp | ||||||
|     hle/service/nvnflinger/graphic_buffer_producer.h |     hle/service/nvnflinger/graphic_buffer_producer.h | ||||||
|     hle/service/nvnflinger/hos_binder_driver_server.cpp |     hle/service/nvnflinger/hos_binder_driver_server.cpp | ||||||
|   | |||||||
| @@ -45,13 +45,6 @@ public: | |||||||
|         IsSharedMemMapped = 6 |         IsSharedMemMapped = 6 | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| private: |  | ||||||
|     /// Id to use for the next handle that is created. |  | ||||||
|     u32 next_handle = 0; |  | ||||||
|  |  | ||||||
|     /// Id to use for the next object that is created. |  | ||||||
|     u32 next_id = 0; |  | ||||||
|  |  | ||||||
|     struct IocCreateParams { |     struct IocCreateParams { | ||||||
|         // Input |         // Input | ||||||
|         u32_le size{}; |         u32_le size{}; | ||||||
| @@ -113,6 +106,13 @@ private: | |||||||
|     NvResult IocParam(std::span<const u8> input, std::span<u8> output); |     NvResult IocParam(std::span<const u8> input, std::span<u8> output); | ||||||
|     NvResult IocFree(std::span<const u8> input, std::span<u8> output); |     NvResult IocFree(std::span<const u8> input, std::span<u8> output); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     /// Id to use for the next handle that is created. | ||||||
|  |     u32 next_handle = 0; | ||||||
|  |  | ||||||
|  |     /// Id to use for the next object that is created. | ||||||
|  |     u32 next_id = 0; | ||||||
|  |  | ||||||
|     NvCore::Container& container; |     NvCore::Container& container; | ||||||
|     NvCore::NvMap& file; |     NvCore::NvMap& file; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ | |||||||
|  |  | ||||||
| namespace Service::android { | namespace Service::android { | ||||||
|  |  | ||||||
| class GraphicBuffer; | struct GraphicBuffer; | ||||||
|  |  | ||||||
| class BufferItem final { | class BufferItem final { | ||||||
| public: | public: | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ | |||||||
|  |  | ||||||
| namespace Service::android { | namespace Service::android { | ||||||
|  |  | ||||||
| class GraphicBuffer; | struct GraphicBuffer; | ||||||
|  |  | ||||||
| enum class BufferState : u32 { | enum class BufferState : u32 { | ||||||
|     Free = 0, |     Free = 0, | ||||||
|   | |||||||
							
								
								
									
										351
									
								
								src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										351
									
								
								src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,351 @@ | |||||||
|  | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||||||
|  | // SPDX-License-Identifier: GPL-2.0-or-later | ||||||
|  |  | ||||||
|  | #include <random> | ||||||
|  |  | ||||||
|  | #include "core/core.h" | ||||||
|  | #include "core/hle/kernel/k_process.h" | ||||||
|  | #include "core/hle/kernel/k_system_resource.h" | ||||||
|  | #include "core/hle/service/nvdrv/devices/nvmap.h" | ||||||
|  | #include "core/hle/service/nvdrv/nvdrv.h" | ||||||
|  | #include "core/hle/service/nvnflinger/buffer_queue_producer.h" | ||||||
|  | #include "core/hle/service/nvnflinger/fb_share_buffer_manager.h" | ||||||
|  | #include "core/hle/service/nvnflinger/pixel_format.h" | ||||||
|  | #include "core/hle/service/nvnflinger/ui/graphic_buffer.h" | ||||||
|  | #include "core/hle/service/vi/layer/vi_layer.h" | ||||||
|  | #include "core/hle/service/vi/vi_results.h" | ||||||
|  |  | ||||||
|  | namespace Service::Nvnflinger { | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address, | ||||||
|  |                                         std::unique_ptr<Kernel::KPageGroup>* out_page_group, | ||||||
|  |                                         Core::System& system, u32 size) { | ||||||
|  |     using Core::Memory::YUZU_PAGESIZE; | ||||||
|  |  | ||||||
|  |     // Allocate memory for the system shared buffer. | ||||||
|  |     // FIXME: Because the gmmu can only point to cpu addresses, we need | ||||||
|  |     //        to map this in the application space to allow it to be used. | ||||||
|  |     // FIXME: Add proper smmu emulation. | ||||||
|  |     // FIXME: This memory belongs to vi's .data section. | ||||||
|  |     auto& kernel = system.Kernel(); | ||||||
|  |     auto* process = system.ApplicationProcess(); | ||||||
|  |     auto& page_table = process->GetPageTable(); | ||||||
|  |  | ||||||
|  |     // Hold a temporary page group reference while we try to map it. | ||||||
|  |     auto pg = std::make_unique<Kernel::KPageGroup>( | ||||||
|  |         kernel, std::addressof(kernel.GetSystemSystemResource().GetBlockInfoManager())); | ||||||
|  |  | ||||||
|  |     // Allocate memory from secure pool. | ||||||
|  |     R_TRY(kernel.MemoryManager().AllocateAndOpen( | ||||||
|  |         pg.get(), size / YUZU_PAGESIZE, | ||||||
|  |         Kernel::KMemoryManager::EncodeOption(Kernel::KMemoryManager::Pool::Secure, | ||||||
|  |                                              Kernel::KMemoryManager::Direction::FromBack))); | ||||||
|  |  | ||||||
|  |     // Get bounds of where mapping is possible. | ||||||
|  |     const VAddr alias_code_begin = GetInteger(page_table.GetAliasCodeRegionStart()); | ||||||
|  |     const VAddr alias_code_size = page_table.GetAliasCodeRegionSize() / YUZU_PAGESIZE; | ||||||
|  |     const auto state = Kernel::KMemoryState::Io; | ||||||
|  |     const auto perm = Kernel::KMemoryPermission::UserReadWrite; | ||||||
|  |     std::mt19937_64 rng{process->GetRandomEntropy(0)}; | ||||||
|  |  | ||||||
|  |     // Retry up to 64 times to map into alias code range. | ||||||
|  |     Result res = ResultSuccess; | ||||||
|  |     int i; | ||||||
|  |     for (i = 0; i < 64; i++) { | ||||||
|  |         *out_map_address = alias_code_begin + ((rng() % alias_code_size) * YUZU_PAGESIZE); | ||||||
|  |         res = page_table.MapPageGroup(*out_map_address, *pg, state, perm); | ||||||
|  |         if (R_SUCCEEDED(res)) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Return failure, if necessary | ||||||
|  |     R_UNLESS(i < 64, res); | ||||||
|  |  | ||||||
|  |     // Return the mapped page group. | ||||||
|  |     *out_page_group = std::move(pg); | ||||||
|  |  | ||||||
|  |     // We succeeded. | ||||||
|  |     R_SUCCEED(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename T> | ||||||
|  | std::span<u8> SerializeIoc(T& params) { | ||||||
|  |     return std::span(reinterpret_cast<u8*>(std::addressof(params)), sizeof(T)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Result CreateNvMapHandle(u32* out_nv_map_handle, Nvidia::Devices::nvmap& nvmap, u32 size) { | ||||||
|  |     // Create a handle. | ||||||
|  |     Nvidia::Devices::nvmap::IocCreateParams create_in_params{ | ||||||
|  |         .size = size, | ||||||
|  |         .handle = 0, | ||||||
|  |     }; | ||||||
|  |     Nvidia::Devices::nvmap::IocCreateParams create_out_params{}; | ||||||
|  |     R_UNLESS(nvmap.IocCreate(SerializeIoc(create_in_params), SerializeIoc(create_out_params)) == | ||||||
|  |                  Nvidia::NvResult::Success, | ||||||
|  |              VI::ResultOperationFailed); | ||||||
|  |  | ||||||
|  |     // Assign the output handle. | ||||||
|  |     *out_nv_map_handle = create_out_params.handle; | ||||||
|  |  | ||||||
|  |     // We succeeded. | ||||||
|  |     R_SUCCEED(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Result FreeNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle) { | ||||||
|  |     // Free the handle. | ||||||
|  |     Nvidia::Devices::nvmap::IocFreeParams free_in_params{ | ||||||
|  |         .handle = handle, | ||||||
|  |     }; | ||||||
|  |     Nvidia::Devices::nvmap::IocFreeParams free_out_params{}; | ||||||
|  |     R_UNLESS(nvmap.IocFree(SerializeIoc(free_in_params), SerializeIoc(free_out_params)) == | ||||||
|  |                  Nvidia::NvResult::Success, | ||||||
|  |              VI::ResultOperationFailed); | ||||||
|  |  | ||||||
|  |     // We succeeded. | ||||||
|  |     R_SUCCEED(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Result AllocNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle, Common::ProcessAddress buffer, | ||||||
|  |                         u32 size) { | ||||||
|  |     // Assign the allocated memory to the handle. | ||||||
|  |     Nvidia::Devices::nvmap::IocAllocParams alloc_in_params{ | ||||||
|  |         .handle = handle, | ||||||
|  |         .heap_mask = 0, | ||||||
|  |         .flags = {}, | ||||||
|  |         .align = 0, | ||||||
|  |         .kind = 0, | ||||||
|  |         .address = GetInteger(buffer), | ||||||
|  |     }; | ||||||
|  |     Nvidia::Devices::nvmap::IocAllocParams alloc_out_params{}; | ||||||
|  |     R_UNLESS(nvmap.IocAlloc(SerializeIoc(alloc_in_params), SerializeIoc(alloc_out_params)) == | ||||||
|  |                  Nvidia::NvResult::Success, | ||||||
|  |              VI::ResultOperationFailed); | ||||||
|  |  | ||||||
|  |     // We succeeded. | ||||||
|  |     R_SUCCEED(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Result AllocateHandleForBuffer(u32* out_handle, Nvidia::Module& nvdrv, | ||||||
|  |                                Common::ProcessAddress buffer, u32 size) { | ||||||
|  |     // Get the nvmap device. | ||||||
|  |     auto nvmap_fd = nvdrv.Open("/dev/nvmap"); | ||||||
|  |     auto nvmap = nvdrv.GetDevice<Nvidia::Devices::nvmap>(nvmap_fd); | ||||||
|  |     ASSERT(nvmap != nullptr); | ||||||
|  |  | ||||||
|  |     // Create a handle. | ||||||
|  |     R_TRY(CreateNvMapHandle(out_handle, *nvmap, size)); | ||||||
|  |  | ||||||
|  |     // Ensure we maintain a clean state on failure. | ||||||
|  |     ON_RESULT_FAILURE { | ||||||
|  |         ASSERT(R_SUCCEEDED(FreeNvMapHandle(*nvmap, *out_handle))); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     // Assign the allocated memory to the handle. | ||||||
|  |     R_RETURN(AllocNvMapHandle(*nvmap, *out_handle, buffer, size)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | constexpr auto SharedBufferBlockLinearFormat = android::PixelFormat::Rgba8888; | ||||||
|  | constexpr u32 SharedBufferBlockLinearBpp = 4; | ||||||
|  |  | ||||||
|  | constexpr u32 SharedBufferBlockLinearWidth = 1280; | ||||||
|  | constexpr u32 SharedBufferBlockLinearHeight = 768; | ||||||
|  | constexpr u32 SharedBufferBlockLinearStride = | ||||||
|  |     SharedBufferBlockLinearWidth * SharedBufferBlockLinearBpp; | ||||||
|  | constexpr u32 SharedBufferNumSlots = 7; | ||||||
|  |  | ||||||
|  | constexpr u32 SharedBufferWidth = 1280; | ||||||
|  | constexpr u32 SharedBufferHeight = 720; | ||||||
|  | constexpr u32 SharedBufferAsync = false; | ||||||
|  |  | ||||||
|  | constexpr u32 SharedBufferSlotSize = | ||||||
|  |     SharedBufferBlockLinearWidth * SharedBufferBlockLinearHeight * SharedBufferBlockLinearBpp; | ||||||
|  | constexpr u32 SharedBufferSize = SharedBufferSlotSize * SharedBufferNumSlots; | ||||||
|  |  | ||||||
|  | constexpr SharedMemoryPoolLayout SharedBufferPoolLayout = [] { | ||||||
|  |     SharedMemoryPoolLayout layout{}; | ||||||
|  |     layout.num_slots = SharedBufferNumSlots; | ||||||
|  |  | ||||||
|  |     for (u32 i = 0; i < SharedBufferNumSlots; i++) { | ||||||
|  |         layout.slots[i].buffer_offset = i * SharedBufferSlotSize; | ||||||
|  |         layout.slots[i].size = SharedBufferSlotSize; | ||||||
|  |         layout.slots[i].width = SharedBufferWidth; | ||||||
|  |         layout.slots[i].height = SharedBufferHeight; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return layout; | ||||||
|  | }(); | ||||||
|  |  | ||||||
|  | void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 slot, u32 handle) { | ||||||
|  |     auto buffer = std::make_shared<android::GraphicBuffer>(); | ||||||
|  |     buffer->width = SharedBufferWidth; | ||||||
|  |     buffer->height = SharedBufferHeight; | ||||||
|  |     buffer->stride = SharedBufferBlockLinearStride; | ||||||
|  |     buffer->format = SharedBufferBlockLinearFormat; | ||||||
|  |     buffer->buffer_id = handle; | ||||||
|  |     buffer->offset = slot * SharedBufferSlotSize; | ||||||
|  |     ASSERT(producer.SetPreallocatedBuffer(slot, buffer) == android::Status::NoError); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace | ||||||
|  |  | ||||||
|  | FbShareBufferManager::FbShareBufferManager(Core::System& system, Nvnflinger& flinger, | ||||||
|  |                                            std::shared_ptr<Nvidia::Module> nvdrv) | ||||||
|  |     : m_system(system), m_flinger(flinger), m_nvdrv(std::move(nvdrv)) {} | ||||||
|  |  | ||||||
|  | FbShareBufferManager::~FbShareBufferManager() = default; | ||||||
|  |  | ||||||
|  | Result FbShareBufferManager::Initialize(u64* out_buffer_id, u64* out_layer_id, u64 display_id) { | ||||||
|  |     std::scoped_lock lk{m_guard}; | ||||||
|  |  | ||||||
|  |     // Ensure we have not already created a buffer. | ||||||
|  |     R_UNLESS(m_buffer_id == 0, VI::ResultOperationFailed); | ||||||
|  |  | ||||||
|  |     // Allocate memory and space for the shared buffer. | ||||||
|  |     Common::ProcessAddress map_address; | ||||||
|  |     R_TRY(AllocateIoForProcessAddressSpace(std::addressof(map_address), | ||||||
|  |                                            std::addressof(m_buffer_page_group), m_system, | ||||||
|  |                                            SharedBufferSize)); | ||||||
|  |  | ||||||
|  |     // Create an nvmap handle for the buffer and assign the memory to it. | ||||||
|  |     R_TRY(AllocateHandleForBuffer(std::addressof(m_buffer_nvmap_handle), *m_nvdrv, map_address, | ||||||
|  |                                   SharedBufferSize)); | ||||||
|  |  | ||||||
|  |     // Record the display id. | ||||||
|  |     m_display_id = display_id; | ||||||
|  |  | ||||||
|  |     // Create a layer for the display. | ||||||
|  |     m_layer_id = m_flinger.CreateLayer(m_display_id).value(); | ||||||
|  |  | ||||||
|  |     // Set up the buffer. | ||||||
|  |     m_buffer_id = m_next_buffer_id++; | ||||||
|  |  | ||||||
|  |     // Get the layer. | ||||||
|  |     VI::Layer* layer = m_flinger.FindLayer(m_display_id, m_layer_id); | ||||||
|  |     ASSERT(layer != nullptr); | ||||||
|  |  | ||||||
|  |     // Get the producer and set preallocated buffers. | ||||||
|  |     auto& producer = layer->GetBufferQueue(); | ||||||
|  |     MakeGraphicBuffer(producer, 0, m_buffer_nvmap_handle); | ||||||
|  |     MakeGraphicBuffer(producer, 1, m_buffer_nvmap_handle); | ||||||
|  |  | ||||||
|  |     // Assign outputs. | ||||||
|  |     *out_buffer_id = m_buffer_id; | ||||||
|  |     *out_layer_id = m_layer_id; | ||||||
|  |  | ||||||
|  |     // We succeeded. | ||||||
|  |     R_SUCCEED(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size, | ||||||
|  |                                                            s32* out_nvmap_handle, | ||||||
|  |                                                            SharedMemoryPoolLayout* out_pool_layout, | ||||||
|  |                                                            u64 buffer_id, | ||||||
|  |                                                            u64 applet_resource_user_id) { | ||||||
|  |     std::scoped_lock lk{m_guard}; | ||||||
|  |  | ||||||
|  |     R_UNLESS(m_buffer_id > 0, VI::ResultNotFound); | ||||||
|  |     R_UNLESS(buffer_id == m_buffer_id, VI::ResultNotFound); | ||||||
|  |  | ||||||
|  |     *out_pool_layout = SharedBufferPoolLayout; | ||||||
|  |     *out_buffer_size = SharedBufferSize; | ||||||
|  |     *out_nvmap_handle = m_buffer_nvmap_handle; | ||||||
|  |  | ||||||
|  |     R_SUCCEED(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Result FbShareBufferManager::GetLayerFromId(VI::Layer** out_layer, u64 layer_id) { | ||||||
|  |     // Ensure the layer id is valid. | ||||||
|  |     R_UNLESS(m_layer_id > 0 && layer_id == m_layer_id, VI::ResultNotFound); | ||||||
|  |  | ||||||
|  |     // Get the layer. | ||||||
|  |     VI::Layer* layer = m_flinger.FindLayer(m_display_id, layer_id); | ||||||
|  |     R_UNLESS(layer != nullptr, VI::ResultNotFound); | ||||||
|  |  | ||||||
|  |     // We succeeded. | ||||||
|  |     *out_layer = layer; | ||||||
|  |     R_SUCCEED(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Result FbShareBufferManager::AcquireSharedFrameBuffer(android::Fence* out_fence, | ||||||
|  |                                                       std::array<s32, 4>& out_slot_indexes, | ||||||
|  |                                                       s64* out_target_slot, u64 layer_id) { | ||||||
|  |     std::scoped_lock lk{m_guard}; | ||||||
|  |  | ||||||
|  |     // Get the layer. | ||||||
|  |     VI::Layer* layer; | ||||||
|  |     R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id)); | ||||||
|  |  | ||||||
|  |     // Get the producer. | ||||||
|  |     auto& producer = layer->GetBufferQueue(); | ||||||
|  |  | ||||||
|  |     // Get the next buffer from the producer. | ||||||
|  |     s32 slot; | ||||||
|  |     R_UNLESS(producer.DequeueBuffer(std::addressof(slot), out_fence, SharedBufferAsync != 0, | ||||||
|  |                                     SharedBufferWidth, SharedBufferHeight, | ||||||
|  |                                     SharedBufferBlockLinearFormat, 0) == android::Status::NoError, | ||||||
|  |              VI::ResultOperationFailed); | ||||||
|  |  | ||||||
|  |     // Assign remaining outputs. | ||||||
|  |     *out_target_slot = slot; | ||||||
|  |     out_slot_indexes = {0, 1, -1, -1}; | ||||||
|  |  | ||||||
|  |     // We succeeded. | ||||||
|  |     R_SUCCEED(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Result FbShareBufferManager::PresentSharedFrameBuffer(android::Fence fence, | ||||||
|  |                                                       Common::Rectangle<s32> crop_region, | ||||||
|  |                                                       u32 transform, s32 swap_interval, | ||||||
|  |                                                       u64 layer_id, s64 slot) { | ||||||
|  |     std::scoped_lock lk{m_guard}; | ||||||
|  |  | ||||||
|  |     // Get the layer. | ||||||
|  |     VI::Layer* layer; | ||||||
|  |     R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id)); | ||||||
|  |  | ||||||
|  |     // Get the producer. | ||||||
|  |     auto& producer = layer->GetBufferQueue(); | ||||||
|  |  | ||||||
|  |     // Request to queue the buffer. | ||||||
|  |     std::shared_ptr<android::GraphicBuffer> buffer; | ||||||
|  |     R_UNLESS(producer.RequestBuffer(static_cast<s32>(slot), std::addressof(buffer)) == | ||||||
|  |                  android::Status::NoError, | ||||||
|  |              VI::ResultOperationFailed); | ||||||
|  |  | ||||||
|  |     // Queue the buffer to the producer. | ||||||
|  |     android::QueueBufferInput input{}; | ||||||
|  |     android::QueueBufferOutput output{}; | ||||||
|  |     input.crop = crop_region; | ||||||
|  |     input.fence = fence; | ||||||
|  |     input.transform = static_cast<android::NativeWindowTransform>(transform); | ||||||
|  |     input.swap_interval = swap_interval; | ||||||
|  |     R_UNLESS(producer.QueueBuffer(static_cast<s32>(slot), input, std::addressof(output)) == | ||||||
|  |                  android::Status::NoError, | ||||||
|  |              VI::ResultOperationFailed); | ||||||
|  |  | ||||||
|  |     // We succeeded. | ||||||
|  |     R_SUCCEED(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Result FbShareBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, | ||||||
|  |                                                                  u64 layer_id) { | ||||||
|  |     std::scoped_lock lk{m_guard}; | ||||||
|  |  | ||||||
|  |     // Get the layer. | ||||||
|  |     VI::Layer* layer; | ||||||
|  |     R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id)); | ||||||
|  |  | ||||||
|  |     // Get the producer. | ||||||
|  |     auto& producer = layer->GetBufferQueue(); | ||||||
|  |  | ||||||
|  |     // Set the event. | ||||||
|  |     *out_event = std::addressof(producer.GetNativeHandle()); | ||||||
|  |  | ||||||
|  |     // We succeeded. | ||||||
|  |     R_SUCCEED(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace Service::Nvnflinger | ||||||
							
								
								
									
										65
									
								
								src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||||||
|  | // SPDX-License-Identifier: GPL-2.0-or-later | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "common/math_util.h" | ||||||
|  | #include "core/hle/service/nvnflinger/nvnflinger.h" | ||||||
|  | #include "core/hle/service/nvnflinger/ui/fence.h" | ||||||
|  |  | ||||||
|  | namespace Kernel { | ||||||
|  | class KPageGroup; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | namespace Service::Nvnflinger { | ||||||
|  |  | ||||||
|  | struct SharedMemorySlot { | ||||||
|  |     u64 buffer_offset; | ||||||
|  |     u64 size; | ||||||
|  |     s32 width; | ||||||
|  |     s32 height; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(SharedMemorySlot) == 0x18, "SharedMemorySlot has wrong size"); | ||||||
|  |  | ||||||
|  | struct SharedMemoryPoolLayout { | ||||||
|  |     s32 num_slots; | ||||||
|  |     std::array<SharedMemorySlot, 0x10> slots; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout has wrong size"); | ||||||
|  |  | ||||||
|  | class FbShareBufferManager final { | ||||||
|  | public: | ||||||
|  |     explicit FbShareBufferManager(Core::System& system, Nvnflinger& flinger, | ||||||
|  |                                   std::shared_ptr<Nvidia::Module> nvdrv); | ||||||
|  |     ~FbShareBufferManager(); | ||||||
|  |  | ||||||
|  |     Result Initialize(u64* out_buffer_id, u64* out_layer_handle, u64 display_id); | ||||||
|  |     Result GetSharedBufferMemoryHandleId(u64* out_buffer_size, s32* out_nvmap_handle, | ||||||
|  |                                          SharedMemoryPoolLayout* out_pool_layout, u64 buffer_id, | ||||||
|  |                                          u64 applet_resource_user_id); | ||||||
|  |     Result AcquireSharedFrameBuffer(android::Fence* out_fence, std::array<s32, 4>& out_slots, | ||||||
|  |                                     s64* out_target_slot, u64 layer_id); | ||||||
|  |     Result PresentSharedFrameBuffer(android::Fence fence, Common::Rectangle<s32> crop_region, | ||||||
|  |                                     u32 transform, s32 swap_interval, u64 layer_id, s64 slot); | ||||||
|  |     Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     Result GetLayerFromId(VI::Layer** out_layer, u64 layer_id); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     u64 m_next_buffer_id = 1; | ||||||
|  |     u64 m_display_id = 0; | ||||||
|  |     u64 m_buffer_id = 0; | ||||||
|  |     u64 m_layer_id = 0; | ||||||
|  |     u32 m_buffer_nvmap_handle = 0; | ||||||
|  |     SharedMemoryPoolLayout m_pool_layout = {}; | ||||||
|  |  | ||||||
|  |     std::unique_ptr<Kernel::KPageGroup> m_buffer_page_group; | ||||||
|  |  | ||||||
|  |     std::mutex m_guard; | ||||||
|  |     Core::System& m_system; | ||||||
|  |     Nvnflinger& m_flinger; | ||||||
|  |     std::shared_ptr<Nvidia::Module> m_nvdrv; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace Service::Nvnflinger | ||||||
| @@ -19,6 +19,7 @@ class InputParcel; | |||||||
| #pragma pack(push, 1) | #pragma pack(push, 1) | ||||||
| struct QueueBufferInput final { | struct QueueBufferInput final { | ||||||
|     explicit QueueBufferInput(InputParcel& parcel); |     explicit QueueBufferInput(InputParcel& parcel); | ||||||
|  |     explicit QueueBufferInput() = default; | ||||||
|  |  | ||||||
|     void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_, |     void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_, | ||||||
|                  NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_, |                  NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_, | ||||||
| @@ -34,7 +35,6 @@ struct QueueBufferInput final { | |||||||
|         *fence_ = fence; |         *fence_ = fence; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| private: |  | ||||||
|     s64 timestamp{}; |     s64 timestamp{}; | ||||||
|     s32 is_auto_timestamp{}; |     s32 is_auto_timestamp{}; | ||||||
|     Common::Rectangle<s32> crop{}; |     Common::Rectangle<s32> crop{}; | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
| #include "core/hle/service/nvdrv/nvdrv.h" | #include "core/hle/service/nvdrv/nvdrv.h" | ||||||
| #include "core/hle/service/nvnflinger/buffer_item_consumer.h" | #include "core/hle/service/nvnflinger/buffer_item_consumer.h" | ||||||
| #include "core/hle/service/nvnflinger/buffer_queue_core.h" | #include "core/hle/service/nvnflinger/buffer_queue_core.h" | ||||||
|  | #include "core/hle/service/nvnflinger/fb_share_buffer_manager.h" | ||||||
| #include "core/hle/service/nvnflinger/hos_binder_driver_server.h" | #include "core/hle/service/nvnflinger/hos_binder_driver_server.h" | ||||||
| #include "core/hle/service/nvnflinger/nvnflinger.h" | #include "core/hle/service/nvnflinger/nvnflinger.h" | ||||||
| #include "core/hle/service/nvnflinger/ui/graphic_buffer.h" | #include "core/hle/service/nvnflinger/ui/graphic_buffer.h" | ||||||
| @@ -331,4 +332,14 @@ s64 Nvnflinger::GetNextTicks() const { | |||||||
|     return static_cast<s64>(speed_scale * (1000000000.f / effective_fps)); |     return static_cast<s64>(speed_scale * (1000000000.f / effective_fps)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | FbShareBufferManager& Nvnflinger::GetSystemBufferManager() { | ||||||
|  |     const auto lock_guard = Lock(); | ||||||
|  |  | ||||||
|  |     if (!system_buffer_manager) { | ||||||
|  |         system_buffer_manager = std::make_unique<FbShareBufferManager>(system, *this, nvdrv); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return *system_buffer_manager; | ||||||
|  | } | ||||||
|  |  | ||||||
| } // namespace Service::Nvnflinger | } // namespace Service::Nvnflinger | ||||||
|   | |||||||
| @@ -45,6 +45,9 @@ class BufferQueueProducer; | |||||||
|  |  | ||||||
| namespace Service::Nvnflinger { | namespace Service::Nvnflinger { | ||||||
|  |  | ||||||
|  | class FbShareBufferManager; | ||||||
|  | class HosBinderDriverServer; | ||||||
|  |  | ||||||
| class Nvnflinger final { | class Nvnflinger final { | ||||||
| public: | public: | ||||||
|     explicit Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_); |     explicit Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_); | ||||||
| @@ -90,12 +93,16 @@ public: | |||||||
|  |  | ||||||
|     [[nodiscard]] s64 GetNextTicks() const; |     [[nodiscard]] s64 GetNextTicks() const; | ||||||
|  |  | ||||||
|  |     FbShareBufferManager& GetSystemBufferManager(); | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     struct Layer { |     struct Layer { | ||||||
|         std::unique_ptr<android::BufferQueueCore> core; |         std::unique_ptr<android::BufferQueueCore> core; | ||||||
|         std::unique_ptr<android::BufferQueueProducer> producer; |         std::unique_ptr<android::BufferQueueProducer> producer; | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     friend class FbShareBufferManager; | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     [[nodiscard]] std::unique_lock<std::mutex> Lock() const { |     [[nodiscard]] std::unique_lock<std::mutex> Lock() const { | ||||||
|         return std::unique_lock{*guard}; |         return std::unique_lock{*guard}; | ||||||
| @@ -140,6 +147,8 @@ private: | |||||||
|     std::shared_ptr<Core::Timing::EventType> multi_composition_event; |     std::shared_ptr<Core::Timing::EventType> multi_composition_event; | ||||||
|     std::shared_ptr<Core::Timing::EventType> single_composition_event; |     std::shared_ptr<Core::Timing::EventType> single_composition_event; | ||||||
|  |  | ||||||
|  |     std::unique_ptr<FbShareBufferManager> system_buffer_manager; | ||||||
|  |  | ||||||
|     std::shared_ptr<std::mutex> guard; |     std::shared_ptr<std::mutex> guard; | ||||||
|  |  | ||||||
|     Core::System& system; |     Core::System& system; | ||||||
|   | |||||||
| @@ -20,6 +20,9 @@ public: | |||||||
|     static constexpr Fence NoFence() { |     static constexpr Fence NoFence() { | ||||||
|         Fence fence; |         Fence fence; | ||||||
|         fence.fences[0].id = -1; |         fence.fences[0].id = -1; | ||||||
|  |         fence.fences[1].id = -1; | ||||||
|  |         fence.fences[2].id = -1; | ||||||
|  |         fence.fences[3].id = -1; | ||||||
|         return fence; |         return fence; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,8 +12,7 @@ | |||||||
|  |  | ||||||
| namespace Service::android { | namespace Service::android { | ||||||
|  |  | ||||||
| class GraphicBuffer final { | struct GraphicBuffer final { | ||||||
| public: |  | ||||||
|     constexpr GraphicBuffer() = default; |     constexpr GraphicBuffer() = default; | ||||||
|  |  | ||||||
|     constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_) |     constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_) | ||||||
| @@ -77,7 +76,6 @@ public: | |||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| private: |  | ||||||
|     u32 magic{}; |     u32 magic{}; | ||||||
|     s32 width{}; |     s32 width{}; | ||||||
|     s32 height{}; |     s32 height{}; | ||||||
|   | |||||||
| @@ -20,9 +20,12 @@ | |||||||
| #include "core/hle/kernel/k_readable_event.h" | #include "core/hle/kernel/k_readable_event.h" | ||||||
| #include "core/hle/kernel/k_thread.h" | #include "core/hle/kernel/k_thread.h" | ||||||
| #include "core/hle/service/ipc_helpers.h" | #include "core/hle/service/ipc_helpers.h" | ||||||
|  | #include "core/hle/service/nvdrv/devices/nvmap.h" | ||||||
| #include "core/hle/service/nvdrv/nvdata.h" | #include "core/hle/service/nvdrv/nvdata.h" | ||||||
|  | #include "core/hle/service/nvdrv/nvdrv.h" | ||||||
| #include "core/hle/service/nvnflinger/binder.h" | #include "core/hle/service/nvnflinger/binder.h" | ||||||
| #include "core/hle/service/nvnflinger/buffer_queue_producer.h" | #include "core/hle/service/nvnflinger/buffer_queue_producer.h" | ||||||
|  | #include "core/hle/service/nvnflinger/fb_share_buffer_manager.h" | ||||||
| #include "core/hle/service/nvnflinger/hos_binder_driver_server.h" | #include "core/hle/service/nvnflinger/hos_binder_driver_server.h" | ||||||
| #include "core/hle/service/nvnflinger/nvnflinger.h" | #include "core/hle/service/nvnflinger/nvnflinger.h" | ||||||
| #include "core/hle/service/nvnflinger/parcel.h" | #include "core/hle/service/nvnflinger/parcel.h" | ||||||
| @@ -131,8 +134,9 @@ private: | |||||||
|  |  | ||||||
| class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> { | class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> { | ||||||
| public: | public: | ||||||
|     explicit ISystemDisplayService(Core::System& system_) |     explicit ISystemDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger_) | ||||||
|         : ServiceFramework{system_, "ISystemDisplayService"} { |         : ServiceFramework{system_, "ISystemDisplayService"}, nvnflinger{nvnflinger_} { | ||||||
|  |         // clang-format off | ||||||
|         static const FunctionInfo functions[] = { |         static const FunctionInfo functions[] = { | ||||||
|             {1200, nullptr, "GetZOrderCountMin"}, |             {1200, nullptr, "GetZOrderCountMin"}, | ||||||
|             {1202, nullptr, "GetZOrderCountMax"}, |             {1202, nullptr, "GetZOrderCountMax"}, | ||||||
| @@ -170,22 +174,126 @@ public: | |||||||
|             {3217, nullptr, "SetDisplayCmuLuma"}, |             {3217, nullptr, "SetDisplayCmuLuma"}, | ||||||
|             {3218, nullptr, "SetDisplayCrcMode"}, |             {3218, nullptr, "SetDisplayCrcMode"}, | ||||||
|             {6013, nullptr, "GetLayerPresentationSubmissionTimestamps"}, |             {6013, nullptr, "GetLayerPresentationSubmissionTimestamps"}, | ||||||
|             {8225, nullptr, "GetSharedBufferMemoryHandleId"}, |             {8225, &ISystemDisplayService::GetSharedBufferMemoryHandleId, "GetSharedBufferMemoryHandleId"}, | ||||||
|             {8250, nullptr, "OpenSharedLayer"}, |             {8250, &ISystemDisplayService::OpenSharedLayer, "OpenSharedLayer"}, | ||||||
|             {8251, nullptr, "CloseSharedLayer"}, |             {8251, nullptr, "CloseSharedLayer"}, | ||||||
|             {8252, nullptr, "ConnectSharedLayer"}, |             {8252, &ISystemDisplayService::ConnectSharedLayer, "ConnectSharedLayer"}, | ||||||
|             {8253, nullptr, "DisconnectSharedLayer"}, |             {8253, nullptr, "DisconnectSharedLayer"}, | ||||||
|             {8254, nullptr, "AcquireSharedFrameBuffer"}, |             {8254, &ISystemDisplayService::AcquireSharedFrameBuffer, "AcquireSharedFrameBuffer"}, | ||||||
|             {8255, nullptr, "PresentSharedFrameBuffer"}, |             {8255, &ISystemDisplayService::PresentSharedFrameBuffer, "PresentSharedFrameBuffer"}, | ||||||
|             {8256, nullptr, "GetSharedFrameBufferAcquirableEvent"}, |             {8256, &ISystemDisplayService::GetSharedFrameBufferAcquirableEvent, "GetSharedFrameBufferAcquirableEvent"}, | ||||||
|             {8257, nullptr, "FillSharedFrameBufferColor"}, |             {8257, nullptr, "FillSharedFrameBufferColor"}, | ||||||
|             {8258, nullptr, "CancelSharedFrameBuffer"}, |             {8258, nullptr, "CancelSharedFrameBuffer"}, | ||||||
|             {9000, nullptr, "GetDp2hdmiController"}, |             {9000, nullptr, "GetDp2hdmiController"}, | ||||||
|         }; |         }; | ||||||
|  |         // clang-format on | ||||||
|         RegisterHandlers(functions); |         RegisterHandlers(functions); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| private: | private: | ||||||
|  |     void GetSharedBufferMemoryHandleId(HLERequestContext& ctx) { | ||||||
|  |         IPC::RequestParser rp{ctx}; | ||||||
|  |         const u64 buffer_id = rp.PopRaw<u64>(); | ||||||
|  |  | ||||||
|  |         LOG_INFO(Service_VI, "called. buffer_id={:#x}", buffer_id); | ||||||
|  |  | ||||||
|  |         struct OutputParameters { | ||||||
|  |             s32 nvmap_handle; | ||||||
|  |             u64 size; | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         OutputParameters out{}; | ||||||
|  |         Nvnflinger::SharedMemoryPoolLayout layout{}; | ||||||
|  |         const auto result = nvnflinger.GetSystemBufferManager().GetSharedBufferMemoryHandleId( | ||||||
|  |             &out.size, &out.nvmap_handle, &layout, buffer_id, 0); | ||||||
|  |  | ||||||
|  |         ctx.WriteBuffer(&layout, sizeof(layout)); | ||||||
|  |  | ||||||
|  |         IPC::ResponseBuilder rb{ctx, 6}; | ||||||
|  |         rb.Push(result); | ||||||
|  |         rb.PushRaw(out); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void OpenSharedLayer(HLERequestContext& ctx) { | ||||||
|  |         IPC::RequestParser rp{ctx}; | ||||||
|  |         const u64 layer_id = rp.PopRaw<u64>(); | ||||||
|  |  | ||||||
|  |         LOG_INFO(Service_VI, "(STUBBED) called. layer_id={:#x}", layer_id); | ||||||
|  |  | ||||||
|  |         IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|  |         rb.Push(ResultSuccess); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void ConnectSharedLayer(HLERequestContext& ctx) { | ||||||
|  |         IPC::RequestParser rp{ctx}; | ||||||
|  |         const u64 layer_id = rp.PopRaw<u64>(); | ||||||
|  |  | ||||||
|  |         LOG_INFO(Service_VI, "(STUBBED) called. layer_id={:#x}", layer_id); | ||||||
|  |  | ||||||
|  |         IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|  |         rb.Push(ResultSuccess); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void GetSharedFrameBufferAcquirableEvent(HLERequestContext& ctx) { | ||||||
|  |         LOG_DEBUG(Service_VI, "called"); | ||||||
|  |  | ||||||
|  |         IPC::RequestParser rp{ctx}; | ||||||
|  |         const u64 layer_id = rp.PopRaw<u64>(); | ||||||
|  |  | ||||||
|  |         Kernel::KReadableEvent* event{}; | ||||||
|  |         const auto result = nvnflinger.GetSystemBufferManager().GetSharedFrameBufferAcquirableEvent( | ||||||
|  |             &event, layer_id); | ||||||
|  |  | ||||||
|  |         IPC::ResponseBuilder rb{ctx, 2, 1}; | ||||||
|  |         rb.Push(result); | ||||||
|  |         rb.PushCopyObjects(event); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void AcquireSharedFrameBuffer(HLERequestContext& ctx) { | ||||||
|  |         LOG_DEBUG(Service_VI, "called"); | ||||||
|  |  | ||||||
|  |         IPC::RequestParser rp{ctx}; | ||||||
|  |         const u64 layer_id = rp.PopRaw<u64>(); | ||||||
|  |  | ||||||
|  |         struct OutputParameters { | ||||||
|  |             android::Fence fence; | ||||||
|  |             std::array<s32, 4> slots; | ||||||
|  |             s64 target_slot; | ||||||
|  |         }; | ||||||
|  |         static_assert(sizeof(OutputParameters) == 0x40, "OutputParameters has wrong size"); | ||||||
|  |  | ||||||
|  |         OutputParameters out{}; | ||||||
|  |         const auto result = nvnflinger.GetSystemBufferManager().AcquireSharedFrameBuffer( | ||||||
|  |             &out.fence, out.slots, &out.target_slot, layer_id); | ||||||
|  |  | ||||||
|  |         IPC::ResponseBuilder rb{ctx, 18}; | ||||||
|  |         rb.Push(result); | ||||||
|  |         rb.PushRaw(out); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void PresentSharedFrameBuffer(HLERequestContext& ctx) { | ||||||
|  |         LOG_DEBUG(Service_VI, "called"); | ||||||
|  |  | ||||||
|  |         struct InputParameters { | ||||||
|  |             android::Fence fence; | ||||||
|  |             Common::Rectangle<s32> crop_region; | ||||||
|  |             u32 window_transform; | ||||||
|  |             s32 swap_interval; | ||||||
|  |             u64 layer_id; | ||||||
|  |             s64 surface_id; | ||||||
|  |         }; | ||||||
|  |         static_assert(sizeof(InputParameters) == 0x50, "InputParameters has wrong size"); | ||||||
|  |  | ||||||
|  |         IPC::RequestParser rp{ctx}; | ||||||
|  |         auto input = rp.PopRaw<InputParameters>(); | ||||||
|  |  | ||||||
|  |         const auto result = nvnflinger.GetSystemBufferManager().PresentSharedFrameBuffer( | ||||||
|  |             input.fence, input.crop_region, input.window_transform, input.swap_interval, | ||||||
|  |             input.layer_id, input.surface_id); | ||||||
|  |         IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|  |         rb.Push(result); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     void SetLayerZ(HLERequestContext& ctx) { |     void SetLayerZ(HLERequestContext& ctx) { | ||||||
|         IPC::RequestParser rp{ctx}; |         IPC::RequestParser rp{ctx}; | ||||||
|         const u64 layer_id = rp.Pop<u64>(); |         const u64 layer_id = rp.Pop<u64>(); | ||||||
| @@ -228,6 +336,9 @@ private: | |||||||
|         rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games. |         rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games. | ||||||
|         rb.Push<u32>(0); |         rb.Push<u32>(0); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     Nvnflinger::Nvnflinger& nvnflinger; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> { | class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> { | ||||||
| @@ -453,7 +564,7 @@ private: | |||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |         IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | ||||||
|         rb.Push(ResultSuccess); |         rb.Push(ResultSuccess); | ||||||
|         rb.PushIpcInterface<ISystemDisplayService>(system); |         rb.PushIpcInterface<ISystemDisplayService>(system, nv_flinger); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void GetManagerDisplayService(HLERequestContext& ctx) { |     void GetManagerDisplayService(HLERequestContext& ctx) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user