From 35f25882e027fd3c466edd44db1fc1c5bec75bde Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Thu, 28 Sep 2023 23:45:49 -0600
Subject: [PATCH] service: nvnflinger: Implement shared buffer

Co-authored-by: Liam <byteslice@airmail.cc>
---
 src/core/CMakeLists.txt                       |   2 +
 src/core/hle/service/nvdrv/devices/nvmap.h    |  14 +-
 src/core/hle/service/nvnflinger/buffer_item.h |   2 +-
 src/core/hle/service/nvnflinger/buffer_slot.h |   2 +-
 .../nvnflinger/fb_share_buffer_manager.cpp    | 351 ++++++++++++++++++
 .../nvnflinger/fb_share_buffer_manager.h      |  65 ++++
 .../nvnflinger/graphic_buffer_producer.h      |   2 +-
 .../hle/service/nvnflinger/nvnflinger.cpp     |  11 +
 src/core/hle/service/nvnflinger/nvnflinger.h  |   9 +
 src/core/hle/service/nvnflinger/ui/fence.h    |   3 +
 .../service/nvnflinger/ui/graphic_buffer.h    |   4 +-
 src/core/hle/service/vi/vi.cpp                | 129 ++++++-
 12 files changed, 572 insertions(+), 22 deletions(-)
 create mode 100644 src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
 create mode 100644 src/core/hle/service/nvnflinger/fb_share_buffer_manager.h

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index d0f76e57e..e02ededfc 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -698,6 +698,8 @@ add_library(core STATIC
     hle/service/nvnflinger/consumer_base.cpp
     hle/service/nvnflinger/consumer_base.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.h
     hle/service/nvnflinger/hos_binder_driver_server.cpp
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index 40c65b430..4c0cc71cd 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -45,13 +45,6 @@ public:
         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 {
         // Input
         u32_le size{};
@@ -113,6 +106,13 @@ private:
     NvResult IocParam(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::NvMap& file;
 };
diff --git a/src/core/hle/service/nvnflinger/buffer_item.h b/src/core/hle/service/nvnflinger/buffer_item.h
index 7fd808f54..3da8cc3aa 100644
--- a/src/core/hle/service/nvnflinger/buffer_item.h
+++ b/src/core/hle/service/nvnflinger/buffer_item.h
@@ -15,7 +15,7 @@
 
 namespace Service::android {
 
-class GraphicBuffer;
+struct GraphicBuffer;
 
 class BufferItem final {
 public:
diff --git a/src/core/hle/service/nvnflinger/buffer_slot.h b/src/core/hle/service/nvnflinger/buffer_slot.h
index d25bca049..d8c9dec3b 100644
--- a/src/core/hle/service/nvnflinger/buffer_slot.h
+++ b/src/core/hle/service/nvnflinger/buffer_slot.h
@@ -13,7 +13,7 @@
 
 namespace Service::android {
 
-class GraphicBuffer;
+struct GraphicBuffer;
 
 enum class BufferState : u32 {
     Free = 0,
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
new file mode 100644
index 000000000..469a53244
--- /dev/null
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
@@ -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
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
new file mode 100644
index 000000000..c809c01b4
--- /dev/null
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
@@ -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
diff --git a/src/core/hle/service/nvnflinger/graphic_buffer_producer.h b/src/core/hle/service/nvnflinger/graphic_buffer_producer.h
index 21d7b31f3..5d7cff7d3 100644
--- a/src/core/hle/service/nvnflinger/graphic_buffer_producer.h
+++ b/src/core/hle/service/nvnflinger/graphic_buffer_producer.h
@@ -19,6 +19,7 @@ class InputParcel;
 #pragma pack(push, 1)
 struct QueueBufferInput final {
     explicit QueueBufferInput(InputParcel& parcel);
+    explicit QueueBufferInput() = default;
 
     void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_,
                  NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_,
@@ -34,7 +35,6 @@ struct QueueBufferInput final {
         *fence_ = fence;
     }
 
-private:
     s64 timestamp{};
     s32 is_auto_timestamp{};
     Common::Rectangle<s32> crop{};
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index 21f31f7a0..a07c621d9 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -17,6 +17,7 @@
 #include "core/hle/service/nvdrv/nvdrv.h"
 #include "core/hle/service/nvnflinger/buffer_item_consumer.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/nvnflinger.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));
 }
 
+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
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h
index f478c2bc6..14c783582 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.h
+++ b/src/core/hle/service/nvnflinger/nvnflinger.h
@@ -45,6 +45,9 @@ class BufferQueueProducer;
 
 namespace Service::Nvnflinger {
 
+class FbShareBufferManager;
+class HosBinderDriverServer;
+
 class Nvnflinger final {
 public:
     explicit Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_);
@@ -90,12 +93,16 @@ public:
 
     [[nodiscard]] s64 GetNextTicks() const;
 
+    FbShareBufferManager& GetSystemBufferManager();
+
 private:
     struct Layer {
         std::unique_ptr<android::BufferQueueCore> core;
         std::unique_ptr<android::BufferQueueProducer> producer;
     };
 
+    friend class FbShareBufferManager;
+
 private:
     [[nodiscard]] std::unique_lock<std::mutex> Lock() const {
         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> single_composition_event;
 
+    std::unique_ptr<FbShareBufferManager> system_buffer_manager;
+
     std::shared_ptr<std::mutex> guard;
 
     Core::System& system;
diff --git a/src/core/hle/service/nvnflinger/ui/fence.h b/src/core/hle/service/nvnflinger/ui/fence.h
index 536e8156d..177aed758 100644
--- a/src/core/hle/service/nvnflinger/ui/fence.h
+++ b/src/core/hle/service/nvnflinger/ui/fence.h
@@ -20,6 +20,9 @@ public:
     static constexpr Fence NoFence() {
         Fence fence;
         fence.fences[0].id = -1;
+        fence.fences[1].id = -1;
+        fence.fences[2].id = -1;
+        fence.fences[3].id = -1;
         return fence;
     }
 
diff --git a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
index 75d1705a8..3eac5cedd 100644
--- a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
+++ b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
@@ -12,8 +12,7 @@
 
 namespace Service::android {
 
-class GraphicBuffer final {
-public:
+struct GraphicBuffer final {
     constexpr GraphicBuffer() = default;
 
     constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_)
@@ -77,7 +76,6 @@ public:
         return false;
     }
 
-private:
     u32 magic{};
     s32 width{};
     s32 height{};
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 2eb978379..b1bfb9898 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -20,9 +20,12 @@
 #include "core/hle/kernel/k_readable_event.h"
 #include "core/hle/kernel/k_thread.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/nvdrv.h"
 #include "core/hle/service/nvnflinger/binder.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/nvnflinger.h"
 #include "core/hle/service/nvnflinger/parcel.h"
@@ -131,8 +134,9 @@ private:
 
 class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> {
 public:
-    explicit ISystemDisplayService(Core::System& system_)
-        : ServiceFramework{system_, "ISystemDisplayService"} {
+    explicit ISystemDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger_)
+        : ServiceFramework{system_, "ISystemDisplayService"}, nvnflinger{nvnflinger_} {
+        // clang-format off
         static const FunctionInfo functions[] = {
             {1200, nullptr, "GetZOrderCountMin"},
             {1202, nullptr, "GetZOrderCountMax"},
@@ -170,22 +174,126 @@ public:
             {3217, nullptr, "SetDisplayCmuLuma"},
             {3218, nullptr, "SetDisplayCrcMode"},
             {6013, nullptr, "GetLayerPresentationSubmissionTimestamps"},
-            {8225, nullptr, "GetSharedBufferMemoryHandleId"},
-            {8250, nullptr, "OpenSharedLayer"},
+            {8225, &ISystemDisplayService::GetSharedBufferMemoryHandleId, "GetSharedBufferMemoryHandleId"},
+            {8250, &ISystemDisplayService::OpenSharedLayer, "OpenSharedLayer"},
             {8251, nullptr, "CloseSharedLayer"},
-            {8252, nullptr, "ConnectSharedLayer"},
+            {8252, &ISystemDisplayService::ConnectSharedLayer, "ConnectSharedLayer"},
             {8253, nullptr, "DisconnectSharedLayer"},
-            {8254, nullptr, "AcquireSharedFrameBuffer"},
-            {8255, nullptr, "PresentSharedFrameBuffer"},
-            {8256, nullptr, "GetSharedFrameBufferAcquirableEvent"},
+            {8254, &ISystemDisplayService::AcquireSharedFrameBuffer, "AcquireSharedFrameBuffer"},
+            {8255, &ISystemDisplayService::PresentSharedFrameBuffer, "PresentSharedFrameBuffer"},
+            {8256, &ISystemDisplayService::GetSharedFrameBufferAcquirableEvent, "GetSharedFrameBufferAcquirableEvent"},
             {8257, nullptr, "FillSharedFrameBufferColor"},
             {8258, nullptr, "CancelSharedFrameBuffer"},
             {9000, nullptr, "GetDp2hdmiController"},
         };
+        // clang-format on
         RegisterHandlers(functions);
     }
 
 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) {
         IPC::RequestParser rp{ctx};
         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.Push<u32>(0);
     }
+
+private:
+    Nvnflinger::Nvnflinger& nvnflinger;
 };
 
 class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> {
@@ -453,7 +564,7 @@ private:
 
         IPC::ResponseBuilder rb{ctx, 2, 0, 1};
         rb.Push(ResultSuccess);
-        rb.PushIpcInterface<ISystemDisplayService>(system);
+        rb.PushIpcInterface<ISystemDisplayService>(system, nv_flinger);
     }
 
     void GetManagerDisplayService(HLERequestContext& ctx) {