From 962c82540c304f909957776908aabcd261f2a7ba Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Mon, 22 Jan 2024 12:40:50 -0500
Subject: [PATCH] nvnflinger/gpu: implement blending

---
 src/core/hle/service/am/am_types.h            |   6 +-
 .../am/frontend/applet_software_keyboard.cpp  |  12 +-
 .../am/frontend/applet_software_keyboard.h    |   2 +-
 .../hle/service/am/library_applet_creator.cpp |   6 +-
 src/core/hle/service/am/self_controller.cpp   |   3 +-
 .../hle/service/am/system_buffer_manager.cpp  |  10 +-
 .../hle/service/am/system_buffer_manager.h    |   3 +-
 .../service/nvdrv/devices/nvdisp_disp0.cpp    |  17 ++
 .../nvnflinger/fb_share_buffer_manager.cpp    | 154 +++++++++++++-----
 .../nvnflinger/fb_share_buffer_manager.h      |  24 ++-
 .../service/nvnflinger/hardware_composer.cpp  |   1 +
 src/core/hle/service/nvnflinger/hwc_layer.h   |  13 ++
 .../hle/service/nvnflinger/nvnflinger.cpp     |   7 +-
 src/core/hle/service/nvnflinger/nvnflinger.h  |   6 +-
 src/core/hle/service/vi/layer/vi_layer.cpp    |   6 +-
 src/core/hle/service/vi/layer/vi_layer.h      |  13 ++
 src/video_core/framebuffer_config.h           |   7 +
 .../host_shaders/fidelityfx_fsr.frag          |  21 ++-
 src/video_core/host_shaders/fxaa.frag         |   2 +-
 .../host_shaders/opengl_fidelityfx_fsr.frag   |  19 +--
 .../host_shaders/opengl_present.frag          |   2 +-
 .../host_shaders/present_bicubic.frag         |   2 +-
 .../host_shaders/present_gaussian.frag        |  14 +-
 .../vulkan_fidelityfx_fsr_easu_fp16.frag      |   1 +
 .../vulkan_fidelityfx_fsr_easu_fp32.frag      |   1 +
 .../vulkan_fidelityfx_fsr_rcas_fp16.frag      |   1 +
 .../vulkan_fidelityfx_fsr_rcas_fp32.frag      |   1 +
 .../vulkan_present_scaleforce_fp16.frag       |   2 +-
 .../vulkan_present_scaleforce_fp32.frag       |   2 +-
 .../present/window_adapt_pass.cpp             |  15 ++
 .../renderer_vulkan/present/util.cpp          |  92 +++++++----
 src/video_core/renderer_vulkan/present/util.h |   9 +-
 .../present/window_adapt_pass.cpp             |  29 +++-
 .../present/window_adapt_pass.h               |   6 +-
 .../renderer_vulkan/renderer_vulkan.cpp       |   4 +-
 .../renderer_vulkan/renderer_vulkan.h         |   3 +
 src/video_core/texture_cache/texture_cache.h  |   8 +-
 37 files changed, 383 insertions(+), 141 deletions(-)

diff --git a/src/core/hle/service/am/am_types.h b/src/core/hle/service/am/am_types.h
index a2b852b122..8c33feb159 100644
--- a/src/core/hle/service/am/am_types.h
+++ b/src/core/hle/service/am/am_types.h
@@ -130,9 +130,9 @@ enum class AppletProgramId : u64 {
 
 enum class LibraryAppletMode : u32 {
     AllForeground = 0,
-    Background = 1,
-    NoUI = 2,
-    BackgroundIndirectDisplay = 3,
+    PartialForeground = 1,
+    NoUi = 2,
+    PartialForegroundIndirectDisplay = 3,
     AllForegroundInitiallyHidden = 4,
 };
 
diff --git a/src/core/hle/service/am/frontend/applet_software_keyboard.cpp b/src/core/hle/service/am/frontend/applet_software_keyboard.cpp
index fbf75d379d..034c62f327 100644
--- a/src/core/hle/service/am/frontend/applet_software_keyboard.cpp
+++ b/src/core/hle/service/am/frontend/applet_software_keyboard.cpp
@@ -68,9 +68,9 @@ void SoftwareKeyboard::Initialize() {
     case LibraryAppletMode::AllForeground:
         InitializeForeground();
         break;
-    case LibraryAppletMode::Background:
-    case LibraryAppletMode::BackgroundIndirectDisplay:
-        InitializeBackground(applet_mode);
+    case LibraryAppletMode::PartialForeground:
+    case LibraryAppletMode::PartialForegroundIndirectDisplay:
+        InitializePartialForeground(applet_mode);
         break;
     default:
         ASSERT_MSG(false, "Invalid LibraryAppletMode={}", applet_mode);
@@ -243,7 +243,7 @@ void SoftwareKeyboard::InitializeForeground() {
     InitializeFrontendNormalKeyboard();
 }
 
-void SoftwareKeyboard::InitializeBackground(LibraryAppletMode library_applet_mode) {
+void SoftwareKeyboard::InitializePartialForeground(LibraryAppletMode library_applet_mode) {
     LOG_INFO(Service_AM, "Initializing Inline Software Keyboard Applet.");
 
     is_background = true;
@@ -258,9 +258,9 @@ void SoftwareKeyboard::InitializeBackground(LibraryAppletMode library_applet_mod
                 swkbd_inline_initialize_arg.size());
 
     if (swkbd_initialize_arg.library_applet_mode_flag) {
-        ASSERT(library_applet_mode == LibraryAppletMode::Background);
+        ASSERT(library_applet_mode == LibraryAppletMode::PartialForeground);
     } else {
-        ASSERT(library_applet_mode == LibraryAppletMode::BackgroundIndirectDisplay);
+        ASSERT(library_applet_mode == LibraryAppletMode::PartialForegroundIndirectDisplay);
     }
 }
 
diff --git a/src/core/hle/service/am/frontend/applet_software_keyboard.h b/src/core/hle/service/am/frontend/applet_software_keyboard.h
index f464b7e156..2a7d01b965 100644
--- a/src/core/hle/service/am/frontend/applet_software_keyboard.h
+++ b/src/core/hle/service/am/frontend/applet_software_keyboard.h
@@ -62,7 +62,7 @@ private:
     void InitializeForeground();
 
     /// Initializes the inline software keyboard.
-    void InitializeBackground(LibraryAppletMode library_applet_mode);
+    void InitializePartialForeground(LibraryAppletMode library_applet_mode);
 
     /// Processes the text check sent by the application.
     void ProcessTextCheck();
diff --git a/src/core/hle/service/am/library_applet_creator.cpp b/src/core/hle/service/am/library_applet_creator.cpp
index 47bab75284..ee646bea55 100644
--- a/src/core/hle/service/am/library_applet_creator.cpp
+++ b/src/core/hle/service/am/library_applet_creator.cpp
@@ -87,7 +87,7 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) {
     // Set focus state
     switch (mode) {
     case LibraryAppletMode::AllForeground:
-    case LibraryAppletMode::NoUI:
+    case LibraryAppletMode::NoUi:
         applet->focus_state = FocusState::InFocus;
         applet->hid_registration.EnableAppletToGetInput(true);
         applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
@@ -99,8 +99,8 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) {
         applet->hid_registration.EnableAppletToGetInput(false);
         applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
         break;
-    case LibraryAppletMode::Background:
-    case LibraryAppletMode::BackgroundIndirectDisplay:
+    case LibraryAppletMode::PartialForeground:
+    case LibraryAppletMode::PartialForegroundIndirectDisplay:
     default:
         applet->focus_state = FocusState::Background;
         applet->hid_registration.EnableAppletToGetInput(true);
diff --git a/src/core/hle/service/am/self_controller.cpp b/src/core/hle/service/am/self_controller.cpp
index 0289f5cf10..b92663b2b0 100644
--- a/src/core/hle/service/am/self_controller.cpp
+++ b/src/core/hle/service/am/self_controller.cpp
@@ -288,7 +288,8 @@ void ISelfController::GetSystemSharedBufferHandle(HLERequestContext& ctx) {
 }
 
 Result ISelfController::EnsureBufferSharingEnabled(Kernel::KProcess* process) {
-    if (applet->system_buffer_manager.Initialize(&nvnflinger, process, applet->applet_id)) {
+    if (applet->system_buffer_manager.Initialize(&nvnflinger, process, applet->applet_id,
+                                                 applet->library_applet_mode)) {
         return ResultSuccess;
     }
 
diff --git a/src/core/hle/service/am/system_buffer_manager.cpp b/src/core/hle/service/am/system_buffer_manager.cpp
index 60a9afc9df..3cccc53888 100644
--- a/src/core/hle/service/am/system_buffer_manager.cpp
+++ b/src/core/hle/service/am/system_buffer_manager.cpp
@@ -17,11 +17,12 @@ SystemBufferManager::~SystemBufferManager() {
 
     // Clean up shared layers.
     if (m_buffer_sharing_enabled) {
+        m_nvnflinger->GetSystemBufferManager().Finalize(m_process);
     }
 }
 
 bool SystemBufferManager::Initialize(Nvnflinger::Nvnflinger* nvnflinger, Kernel::KProcess* process,
-                                     AppletId applet_id) {
+                                     AppletId applet_id, LibraryAppletMode mode) {
     if (m_nvnflinger) {
         return m_buffer_sharing_enabled;
     }
@@ -36,9 +37,14 @@ bool SystemBufferManager::Initialize(Nvnflinger::Nvnflinger* nvnflinger, Kernel:
         return false;
     }
 
+    Nvnflinger::LayerBlending blending = Nvnflinger::LayerBlending::None;
+    if (mode == LibraryAppletMode::PartialForeground) {
+        blending = Nvnflinger::LayerBlending::Coverage;
+    }
+
     const auto display_id = m_nvnflinger->OpenDisplay("Default").value();
     const auto res = m_nvnflinger->GetSystemBufferManager().Initialize(
-        &m_system_shared_buffer_id, &m_system_shared_layer_id, display_id);
+        m_process, &m_system_shared_buffer_id, &m_system_shared_layer_id, display_id, blending);
 
     if (res.IsSuccess()) {
         m_buffer_sharing_enabled = true;
diff --git a/src/core/hle/service/am/system_buffer_manager.h b/src/core/hle/service/am/system_buffer_manager.h
index 98c3cf055a..0690f68b65 100644
--- a/src/core/hle/service/am/system_buffer_manager.h
+++ b/src/core/hle/service/am/system_buffer_manager.h
@@ -27,7 +27,8 @@ public:
     SystemBufferManager();
     ~SystemBufferManager();
 
-    bool Initialize(Nvnflinger::Nvnflinger* flinger, Kernel::KProcess* process, AppletId applet_id);
+    bool Initialize(Nvnflinger::Nvnflinger* flinger, Kernel::KProcess* process, AppletId applet_id,
+                    LibraryAppletMode mode);
 
     void GetSystemSharedLayerHandle(u64* out_system_shared_buffer_id,
                                     u64* out_system_shared_layer_id) {
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index abe95303e2..995646e250 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -15,6 +15,22 @@
 
 namespace Service::Nvidia::Devices {
 
+namespace {
+
+Tegra::BlendMode ConvertBlending(Service::Nvnflinger::LayerBlending blending) {
+    switch (blending) {
+    case Service::Nvnflinger::LayerBlending::None:
+    default:
+        return Tegra::BlendMode::Opaque;
+    case Service::Nvnflinger::LayerBlending::Premultiplied:
+        return Tegra::BlendMode::Premultiplied;
+    case Service::Nvnflinger::LayerBlending::Coverage:
+        return Tegra::BlendMode::Coverage;
+    }
+}
+
+} // namespace
+
 nvdisp_disp0::nvdisp_disp0(Core::System& system_, NvCore::Container& core)
     : nvdevice{system_}, container{core}, nvmap{core.GetNvMapFile()} {}
 nvdisp_disp0::~nvdisp_disp0() = default;
@@ -56,6 +72,7 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers
             .pixel_format = layer.format,
             .transform_flags = layer.transform,
             .crop_rect = layer.crop_rect,
+            .blending = ConvertBlending(layer.blending),
         });
 
         for (size_t i = 0; i < layer.acquire_fence.num_fences; i++) {
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
index e71652cdf1..6a7da0caed 100644
--- a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
@@ -14,24 +14,19 @@
 #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"
+#include "video_core/gpu.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) {
+Result AllocateSharedBufferMemory(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>(
@@ -43,6 +38,30 @@ Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
         Kernel::KMemoryManager::EncodeOption(Kernel::KMemoryManager::Pool::Secure,
                                              Kernel::KMemoryManager::Direction::FromBack)));
 
+    // Fill the output data with red.
+    for (auto& block : *pg) {
+        u32* start = system.DeviceMemory().GetPointer<u32>(block.GetAddress());
+        u32* end = system.DeviceMemory().GetPointer<u32>(block.GetAddress() + block.GetSize());
+
+        for (; start < end; start++) {
+            *start = 0xFF0000FF;
+        }
+    }
+
+    // Return the mapped page group.
+    *out_page_group = std::move(pg);
+
+    // We succeeded.
+    R_SUCCEED();
+}
+
+Result MapSharedBufferIntoProcessAddressSpace(Common::ProcessAddress* out_map_address,
+                                              std::unique_ptr<Kernel::KPageGroup>& pg,
+                                              Kernel::KProcess* process, Core::System& system) {
+    using Core::Memory::YUZU_PAGESIZE;
+
+    auto& page_table = process->GetPageTable();
+
     // 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;
@@ -64,9 +83,6 @@ Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
     // Return failure, if necessary
     R_UNLESS(i < 64, res);
 
-    // Return the mapped page group.
-    *out_page_group = std::move(pg);
-
     // We succeeded.
     R_SUCCEED();
 }
@@ -135,6 +151,13 @@ Result AllocateHandleForBuffer(u32* out_handle, Nvidia::Module& nvdrv, Nvidia::D
     R_RETURN(AllocNvMapHandle(*nvmap, *out_handle, buffer, size, nvmap_fd));
 }
 
+void FreeHandle(u32 handle, Nvidia::Module& nvdrv, Nvidia::DeviceFD nvmap_fd) {
+    auto nvmap = nvdrv.GetDevice<Nvidia::Devices::nvmap>(nvmap_fd);
+    ASSERT(nvmap != nullptr);
+
+    R_ASSERT(FreeNvMapHandle(*nvmap, handle, nvmap_fd));
+}
+
 constexpr auto SharedBufferBlockLinearFormat = android::PixelFormat::Rgba8888;
 constexpr u32 SharedBufferBlockLinearBpp = 4;
 
@@ -186,53 +209,97 @@ FbShareBufferManager::FbShareBufferManager(Core::System& system, Nvnflinger& fli
 
 FbShareBufferManager::~FbShareBufferManager() = default;
 
-Result FbShareBufferManager::Initialize(u64* out_buffer_id, u64* out_layer_id, u64 display_id) {
+Result FbShareBufferManager::Initialize(Kernel::KProcess* owner_process, u64* out_buffer_id,
+                                        u64* out_layer_handle, u64 display_id,
+                                        LayerBlending blending) {
     std::scoped_lock lk{m_guard};
 
-    // Ensure we have not already created a buffer.
-    R_UNLESS(m_buffer_id == 0, VI::ResultOperationFailed);
+    // Ensure we haven't already created.
+    const u64 aruid = owner_process->GetProcessId();
+    R_UNLESS(!m_sessions.contains(aruid), VI::ResultPermissionDenied);
 
-    // 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));
+    // Allocate memory for the shared buffer if needed.
+    if (!m_buffer_page_group) {
+        R_TRY(AllocateSharedBufferMemory(std::addressof(m_buffer_page_group), m_system,
+                                         SharedBufferSize));
+
+        // Record buffer id.
+        m_buffer_id = m_next_buffer_id++;
+
+        // Record display id.
+        m_display_id = display_id;
+    }
+
+    // Map into process.
+    Common::ProcessAddress map_address{};
+    R_TRY(MapSharedBufferIntoProcessAddressSpace(std::addressof(map_address), m_buffer_page_group,
+                                                 owner_process, m_system));
+
+    // Create new session.
+    auto [it, was_emplaced] = m_sessions.emplace(aruid, FbShareSession{});
+    auto& session = it->second;
 
     auto& container = m_nvdrv->GetContainer();
-    m_session_id = container.OpenSession(m_system.ApplicationProcess());
-    m_nvmap_fd = m_nvdrv->Open("/dev/nvmap", m_session_id);
+    session.session_id = container.OpenSession(owner_process);
+    session.nvmap_fd = m_nvdrv->Open("/dev/nvmap", session.session_id);
 
     // Create an nvmap handle for the buffer and assign the memory to it.
-    R_TRY(AllocateHandleForBuffer(std::addressof(m_buffer_nvmap_handle), *m_nvdrv, m_nvmap_fd,
-                                  map_address, SharedBufferSize));
-
-    // Record the display id.
-    m_display_id = display_id;
+    R_TRY(AllocateHandleForBuffer(std::addressof(session.buffer_nvmap_handle), *m_nvdrv,
+                                  session.nvmap_fd, map_address, SharedBufferSize));
 
     // Create and open a layer for the display.
-    m_layer_id = m_flinger.CreateLayer(m_display_id).value();
-    m_flinger.OpenLayer(m_layer_id);
-
-    // Set up the buffer.
-    m_buffer_id = m_next_buffer_id++;
+    session.layer_id = m_flinger.CreateLayer(m_display_id, blending).value();
+    m_flinger.OpenLayer(session.layer_id);
 
     // Get the layer.
-    VI::Layer* layer = m_flinger.FindLayer(m_display_id, m_layer_id);
+    VI::Layer* layer = m_flinger.FindLayer(m_display_id, session.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);
+    MakeGraphicBuffer(producer, 0, session.buffer_nvmap_handle);
+    MakeGraphicBuffer(producer, 1, session.buffer_nvmap_handle);
 
     // Assign outputs.
     *out_buffer_id = m_buffer_id;
-    *out_layer_id = m_layer_id;
+    *out_layer_handle = session.layer_id;
 
     // We succeeded.
     R_SUCCEED();
 }
 
+void FbShareBufferManager::Finalize(Kernel::KProcess* owner_process) {
+    std::scoped_lock lk{m_guard};
+
+    if (m_buffer_id == 0) {
+        return;
+    }
+
+    const u64 aruid = owner_process->GetProcessId();
+    const auto it = m_sessions.find(aruid);
+    if (it == m_sessions.end()) {
+        return;
+    }
+
+    auto& session = it->second;
+
+    // Destroy the layer.
+    m_flinger.DestroyLayer(session.layer_id);
+
+    // Close nvmap handle.
+    FreeHandle(session.buffer_nvmap_handle, *m_nvdrv, session.nvmap_fd);
+
+    // Close nvmap device.
+    m_nvdrv->Close(session.nvmap_fd);
+
+    // Close session.
+    auto& container = m_nvdrv->GetContainer();
+    container.CloseSession(session.session_id);
+
+    // Erase.
+    m_sessions.erase(it);
+}
+
 Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
                                                            s32* out_nvmap_handle,
                                                            SharedMemoryPoolLayout* out_pool_layout,
@@ -242,17 +309,18 @@ Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
 
     R_UNLESS(m_buffer_id > 0, VI::ResultNotFound);
     R_UNLESS(buffer_id == m_buffer_id, VI::ResultNotFound);
+    R_UNLESS(m_sessions.contains(applet_resource_user_id), VI::ResultNotFound);
 
     *out_pool_layout = SharedBufferPoolLayout;
     *out_buffer_size = SharedBufferSize;
-    *out_nvmap_handle = m_buffer_nvmap_handle;
+    *out_nvmap_handle = m_sessions[applet_resource_user_id].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);
+    R_UNLESS(layer_id > 0, VI::ResultNotFound);
 
     // Get the layer.
     VI::Layer* layer = m_flinger.FindLayer(m_display_id, layer_id);
@@ -309,6 +377,10 @@ Result FbShareBufferManager::PresentSharedFrameBuffer(android::Fence fence,
                  android::Status::NoError,
              VI::ResultOperationFailed);
 
+    ON_RESULT_FAILURE {
+        producer.CancelBuffer(static_cast<s32>(slot), fence);
+    };
+
     // Queue the buffer to the producer.
     android::QueueBufferInput input{};
     android::QueueBufferOutput output{};
@@ -342,4 +414,12 @@ Result FbShareBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadab
     R_SUCCEED();
 }
 
+Result FbShareBufferManager::WriteAppletCaptureBuffer(bool* out_was_written,
+                                                      s32* out_layer_index) {
+    // TODO
+    *out_was_written = true;
+    *out_layer_index = 1;
+    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
index 033bf4bbe3..b79a7d23ac 100644
--- a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
@@ -3,9 +3,12 @@
 
 #pragma once
 
+#include <map>
+
 #include "common/math_util.h"
 #include "core/hle/service/nvdrv/core/container.h"
 #include "core/hle/service/nvdrv/nvdata.h"
+#include "core/hle/service/nvnflinger/hwc_layer.h"
 #include "core/hle/service/nvnflinger/nvnflinger.h"
 #include "core/hle/service/nvnflinger/ui/fence.h"
 
@@ -29,13 +32,18 @@ struct SharedMemoryPoolLayout {
 };
 static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout has wrong size");
 
+struct FbShareSession;
+
 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 Initialize(Kernel::KProcess* owner_process, u64* out_buffer_id, u64* out_layer_handle,
+                      u64 display_id, LayerBlending blending);
+    void Finalize(Kernel::KProcess* owner_process);
+
     Result GetSharedBufferMemoryHandleId(u64* out_buffer_size, s32* out_nvmap_handle,
                                          SharedMemoryPoolLayout* out_pool_layout, u64 buffer_id,
                                          u64 applet_resource_user_id);
@@ -45,6 +53,8 @@ public:
                                     u32 transform, s32 swap_interval, u64 layer_id, s64 slot);
     Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id);
 
+    Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index);
+
 private:
     Result GetLayerFromId(VI::Layer** out_layer, u64 layer_id);
 
@@ -52,11 +62,8 @@ 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 = {};
-    Nvidia::DeviceFD m_nvmap_fd = {};
-    Nvidia::NvCore::SessionId m_session_id = {};
+    std::map<u64, FbShareSession> m_sessions;
     std::unique_ptr<Kernel::KPageGroup> m_buffer_page_group;
 
     std::mutex m_guard;
@@ -65,4 +72,11 @@ private:
     std::shared_ptr<Nvidia::Module> m_nvdrv;
 };
 
+struct FbShareSession {
+    Nvidia::DeviceFD nvmap_fd = {};
+    Nvidia::NvCore::SessionId session_id = {};
+    u64 layer_id = {};
+    u32 buffer_nvmap_handle = 0;
+};
+
 } // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/hardware_composer.cpp b/src/core/hle/service/nvnflinger/hardware_composer.cpp
index ba2b5c28c6..be7eb97a3e 100644
--- a/src/core/hle/service/nvnflinger/hardware_composer.cpp
+++ b/src/core/hle/service/nvnflinger/hardware_composer.cpp
@@ -86,6 +86,7 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, VI::Display& display,
             .height = igbp_buffer.Height(),
             .stride = igbp_buffer.Stride(),
             .z_index = 0,
+            .blending = layer.GetBlending(),
             .transform = static_cast<android::BufferTransformFlags>(item.transform),
             .crop_rect = item.crop,
             .acquire_fence = item.fence,
diff --git a/src/core/hle/service/nvnflinger/hwc_layer.h b/src/core/hle/service/nvnflinger/hwc_layer.h
index 3af668a256..f71a5d8227 100644
--- a/src/core/hle/service/nvnflinger/hwc_layer.h
+++ b/src/core/hle/service/nvnflinger/hwc_layer.h
@@ -11,6 +11,18 @@
 
 namespace Service::Nvnflinger {
 
+// hwc_layer_t::blending values
+enum class LayerBlending : u32 {
+    // No blending
+    None = 0x100,
+
+    // ONE / ONE_MINUS_SRC_ALPHA
+    Premultiplied = 0x105,
+
+    // SRC_ALPHA / ONE_MINUS_SRC_ALPHA
+    Coverage = 0x405,
+};
+
 struct HwcLayer {
     u32 buffer_handle;
     u32 offset;
@@ -19,6 +31,7 @@ struct HwcLayer {
     u32 height;
     u32 stride;
     s32 z_index;
+    LayerBlending blending;
     android::BufferTransformFlags transform;
     Common::Rectangle<int> crop_rect;
     android::Fence acquire_fence;
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index d8ba89d432..687ccc9f9e 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -157,7 +157,7 @@ bool Nvnflinger::CloseDisplay(u64 display_id) {
     return true;
 }
 
-std::optional<u64> Nvnflinger::CreateLayer(u64 display_id) {
+std::optional<u64> Nvnflinger::CreateLayer(u64 display_id, LayerBlending blending) {
     const auto lock_guard = Lock();
     auto* const display = FindDisplay(display_id);
 
@@ -166,13 +166,14 @@ std::optional<u64> Nvnflinger::CreateLayer(u64 display_id) {
     }
 
     const u64 layer_id = next_layer_id++;
-    CreateLayerAtId(*display, layer_id);
+    CreateLayerAtId(*display, layer_id, blending);
     return layer_id;
 }
 
-void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
+void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id, LayerBlending blending) {
     const auto buffer_id = next_buffer_queue_id++;
     display.CreateLayer(layer_id, buffer_id, nvdrv->container);
+    display.FindLayer(layer_id)->SetBlending(blending);
 }
 
 bool Nvnflinger::OpenLayer(u64 layer_id) {
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h
index c984d55a00..4cf4f069d7 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.h
+++ b/src/core/hle/service/nvnflinger/nvnflinger.h
@@ -15,6 +15,7 @@
 #include "common/thread.h"
 #include "core/hle/result.h"
 #include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nvnflinger/hwc_layer.h"
 
 namespace Common {
 class Event;
@@ -72,7 +73,8 @@ public:
     /// Creates a layer on the specified display and returns the layer ID.
     ///
     /// If an invalid display ID is specified, then an empty optional is returned.
-    [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id);
+    [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id,
+                                                 LayerBlending blending = LayerBlending::None);
 
     /// Opens a layer on all displays for the given layer ID.
     bool OpenLayer(u64 layer_id);
@@ -128,7 +130,7 @@ private:
     [[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id);
 
     /// Creates a layer with the specified layer ID in the desired display.
-    void CreateLayerAtId(VI::Display& display, u64 layer_id);
+    void CreateLayerAtId(VI::Display& display, u64 layer_id, LayerBlending blending);
 
     void SplitVSync(std::stop_token stop_token);
 
diff --git a/src/core/hle/service/vi/layer/vi_layer.cpp b/src/core/hle/service/vi/layer/vi_layer.cpp
index 493bd6e9e5..eca35d82a9 100644
--- a/src/core/hle/service/vi/layer/vi_layer.cpp
+++ b/src/core/hle/service/vi/layer/vi_layer.cpp
@@ -1,6 +1,7 @@
 // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include "core/hle/service/nvnflinger/hwc_layer.h"
 #include "core/hle/service/vi/layer/vi_layer.h"
 
 namespace Service::VI {
@@ -8,8 +9,9 @@ namespace Service::VI {
 Layer::Layer(u64 layer_id_, u32 binder_id_, android::BufferQueueCore& core_,
              android::BufferQueueProducer& binder_,
              std::shared_ptr<android::BufferItemConsumer>&& consumer_)
-    : layer_id{layer_id_}, binder_id{binder_id_}, core{core_}, binder{binder_},
-      consumer{std::move(consumer_)}, open{false}, visible{true} {}
+    : layer_id{layer_id_}, binder_id{binder_id_}, core{core_}, binder{binder_}, consumer{std::move(
+                                                                                    consumer_)},
+      blending{Nvnflinger::LayerBlending::None}, open{false}, visible{true} {}
 
 Layer::~Layer() = default;
 
diff --git a/src/core/hle/service/vi/layer/vi_layer.h b/src/core/hle/service/vi/layer/vi_layer.h
index b4b031ee75..14e229903a 100644
--- a/src/core/hle/service/vi/layer/vi_layer.h
+++ b/src/core/hle/service/vi/layer/vi_layer.h
@@ -14,6 +14,10 @@ class BufferQueueCore;
 class BufferQueueProducer;
 } // namespace Service::android
 
+namespace Service::Nvnflinger {
+enum class LayerBlending : u32;
+}
+
 namespace Service::VI {
 
 /// Represents a single display layer.
@@ -92,12 +96,21 @@ public:
         return !std::exchange(open, true);
     }
 
+    Nvnflinger::LayerBlending GetBlending() {
+        return blending;
+    }
+
+    void SetBlending(Nvnflinger::LayerBlending b) {
+        blending = b;
+    }
+
 private:
     const u64 layer_id;
     const u32 binder_id;
     android::BufferQueueCore& core;
     android::BufferQueueProducer& binder;
     std::shared_ptr<android::BufferItemConsumer> consumer;
+    Service::Nvnflinger::LayerBlending blending;
     bool open;
     bool visible;
 };
diff --git a/src/video_core/framebuffer_config.h b/src/video_core/framebuffer_config.h
index 6a18b76fb5..8b2a49de5e 100644
--- a/src/video_core/framebuffer_config.h
+++ b/src/video_core/framebuffer_config.h
@@ -11,6 +11,12 @@
 
 namespace Tegra {
 
+enum class BlendMode {
+    Opaque,
+    Premultiplied,
+    Coverage,
+};
+
 /**
  * Struct describing framebuffer configuration
  */
@@ -23,6 +29,7 @@ struct FramebufferConfig {
     Service::android::PixelFormat pixel_format{};
     Service::android::BufferTransformFlags transform_flags{};
     Common::Rectangle<int> crop_rect{};
+    BlendMode blending{};
 };
 
 Common::Rectangle<f32> NormalizeCrop(const FramebufferConfig& framebuffer, u32 texture_width,
diff --git a/src/video_core/host_shaders/fidelityfx_fsr.frag b/src/video_core/host_shaders/fidelityfx_fsr.frag
index a266e1c4eb..54eedb450e 100644
--- a/src/video_core/host_shaders/fidelityfx_fsr.frag
+++ b/src/video_core/host_shaders/fidelityfx_fsr.frag
@@ -37,6 +37,7 @@ layout(set=0,binding=0) uniform sampler2D InputTexture;
 
 #define A_GPU 1
 #define A_GLSL 1
+#define FSR_RCAS_PASSTHROUGH_ALPHA 1
 
 #ifndef YUZU_USE_FP16
     #include "ffx_a.h"
@@ -71,9 +72,7 @@ layout(set=0,binding=0) uniform sampler2D InputTexture;
 
 #include "ffx_fsr1.h"
 
-#if USE_RCAS
-    layout(location = 0) in vec2 frag_texcoord;
-#endif
+layout (location = 0) in vec2 frag_texcoord;
 layout (location = 0) out vec4 frag_color;
 
 void CurrFilter(AU2 pos) {
@@ -81,22 +80,22 @@ void CurrFilter(AU2 pos) {
     #ifndef YUZU_USE_FP16
         AF3 c;
         FsrEasuF(c, pos, Const0, Const1, Const2, Const3);
-        frag_color = AF4(c, 1.0);
+        frag_color = AF4(c, texture(InputTexture, frag_texcoord).a);
     #else
         AH3 c;
         FsrEasuH(c, pos, Const0, Const1, Const2, Const3);
-        frag_color = AH4(c, 1.0);
+        frag_color = AH4(c, texture(InputTexture, frag_texcoord).a);
     #endif
 #endif
 #if USE_RCAS
     #ifndef YUZU_USE_FP16
-        AF3 c;
-        FsrRcasF(c.r, c.g, c.b, pos, Const0);
-        frag_color = AF4(c, 1.0);
+        AF4 c;
+        FsrRcasF(c.r, c.g, c.b, c.a, pos, Const0);
+        frag_color = c;
     #else
-        AH3 c;
-        FsrRcasH(c.r, c.g, c.b, pos, Const0);
-        frag_color = AH4(c, 1.0);
+        AH4 c;
+        FsrRcasH(c.r, c.g, c.b, c.a, pos, Const0);
+        frag_color = c;
     #endif
 #endif
 }
diff --git a/src/video_core/host_shaders/fxaa.frag b/src/video_core/host_shaders/fxaa.frag
index 9bffc20d56..192a602c14 100644
--- a/src/video_core/host_shaders/fxaa.frag
+++ b/src/video_core/host_shaders/fxaa.frag
@@ -71,5 +71,5 @@ vec3 FxaaPixelShader(vec4 posPos, sampler2D tex) {
 }
 
 void main() {
-  frag_color = vec4(FxaaPixelShader(posPos, input_texture), 1.0);
+  frag_color = vec4(FxaaPixelShader(posPos, input_texture), texture(input_texture, posPos.xy).a);
 }
diff --git a/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag b/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag
index 16d22f58e5..fc47d38101 100644
--- a/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag
+++ b/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag
@@ -31,6 +31,7 @@ layout (location = 0) uniform uvec4 constants[4];
 
 #define A_GPU 1
 #define A_GLSL 1
+#define FSR_RCAS_PASSTHROUGH_ALPHA 1
 
 #ifdef YUZU_USE_FP16
     #define A_HALF
@@ -67,9 +68,7 @@ layout (location = 0) uniform uvec4 constants[4];
 
 #include "ffx_fsr1.h"
 
-#if USE_RCAS
-    layout(location = 0) in vec2 frag_texcoord;
-#endif
+layout (location = 0) in vec2 frag_texcoord;
 layout (location = 0) out vec4 frag_color;
 
 void CurrFilter(AU2 pos)
@@ -78,22 +77,22 @@ void CurrFilter(AU2 pos)
     #ifndef YUZU_USE_FP16
         AF3 c;
         FsrEasuF(c, pos, constants[0], constants[1], constants[2], constants[3]);
-        frag_color = AF4(c, 1.0);
+        frag_color = AF4(c, texture(InputTexture, frag_texcoord).a);
     #else
         AH3 c;
         FsrEasuH(c, pos, constants[0], constants[1], constants[2], constants[3]);
-        frag_color = AH4(c, 1.0);
+        frag_color = AH4(c, texture(InputTexture, frag_texcoord).a);
     #endif
 #endif
 #if USE_RCAS
     #ifndef YUZU_USE_FP16
-        AF3 c;
-        FsrRcasF(c.r, c.g, c.b, pos, constants[0]);
-        frag_color = AF4(c, 1.0);
+        AF4 c;
+        FsrRcasF(c.r, c.g, c.b, c.a, pos, constants[0]);
+        frag_color = c;
     #else
         AH3 c;
-        FsrRcasH(c.r, c.g, c.b, pos, constants[0]);
-        frag_color = AH4(c, 1.0);
+        FsrRcasH(c.r, c.g, c.b, c.a, pos, constants[0]);
+        frag_color = c;
     #endif
 #endif
 }
diff --git a/src/video_core/host_shaders/opengl_present.frag b/src/video_core/host_shaders/opengl_present.frag
index 5fd7ad2976..096b4e4dbc 100644
--- a/src/video_core/host_shaders/opengl_present.frag
+++ b/src/video_core/host_shaders/opengl_present.frag
@@ -9,5 +9,5 @@ layout (location = 0) out vec4 color;
 layout (binding = 0) uniform sampler2D color_texture;
 
 void main() {
-    color = vec4(texture(color_texture, frag_tex_coord).rgb, 1.0f);
+    color = vec4(texture(color_texture, frag_tex_coord));
 }
diff --git a/src/video_core/host_shaders/present_bicubic.frag b/src/video_core/host_shaders/present_bicubic.frag
index c814629cf1..a9d9d40a38 100644
--- a/src/video_core/host_shaders/present_bicubic.frag
+++ b/src/video_core/host_shaders/present_bicubic.frag
@@ -52,5 +52,5 @@ vec4 textureBicubic( sampler2D textureSampler, vec2 texCoords ) {
 }
 
 void main() {
-    color = vec4(textureBicubic(color_texture, frag_tex_coord).rgb, 1.0f);
+    color = textureBicubic(color_texture, frag_tex_coord);
 }
diff --git a/src/video_core/host_shaders/present_gaussian.frag b/src/video_core/host_shaders/present_gaussian.frag
index ad9bb76a49..78edeb9b40 100644
--- a/src/video_core/host_shaders/present_gaussian.frag
+++ b/src/video_core/host_shaders/present_gaussian.frag
@@ -46,14 +46,14 @@ vec4 blurDiagonal(sampler2D textureSampler, vec2 coord, vec2 norm) {
 }
 
 void main() {
-    vec3 base = texture(color_texture, vec2(frag_tex_coord)).rgb * weight[0];
+    vec4 base = texture(color_texture, vec2(frag_tex_coord)) * weight[0];
     vec2 tex_offset = 1.0f / textureSize(color_texture, 0);
 
     // TODO(Blinkhawk): This code can be optimized through shader group instructions.
-    vec3 horizontal = blurHorizontal(color_texture, frag_tex_coord, tex_offset).rgb;
-    vec3 vertical = blurVertical(color_texture, frag_tex_coord, tex_offset).rgb;
-    vec3 diagonalA = blurDiagonal(color_texture, frag_tex_coord, tex_offset).rgb;
-    vec3 diagonalB = blurDiagonal(color_texture, frag_tex_coord, tex_offset * vec2(1.0, -1.0)).rgb;
-    vec3 combination = mix(mix(horizontal, vertical, 0.5f), mix(diagonalA, diagonalB, 0.5f), 0.5f);
-    color = vec4(combination + base, 1.0f);
+    vec4 horizontal = blurHorizontal(color_texture, frag_tex_coord, tex_offset);
+    vec4 vertical = blurVertical(color_texture, frag_tex_coord, tex_offset);
+    vec4 diagonalA = blurDiagonal(color_texture, frag_tex_coord, tex_offset);
+    vec4 diagonalB = blurDiagonal(color_texture, frag_tex_coord, tex_offset * vec2(1.0, -1.0));
+    vec4 combination = mix(mix(horizontal, vertical, 0.5f), mix(diagonalA, diagonalB, 0.5f), 0.5f);
+    color = combination + base;
 }
diff --git a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.frag b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.frag
index d369bef069..05d0333104 100644
--- a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.frag
+++ b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.frag
@@ -6,5 +6,6 @@
 
 #define YUZU_USE_FP16
 #define USE_EASU 1
+#define VERSION 1
 
 #include "fidelityfx_fsr.frag"
diff --git a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.frag b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.frag
index 6f25ef00f6..7ae11dd66a 100644
--- a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.frag
+++ b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.frag
@@ -5,5 +5,6 @@
 #extension GL_GOOGLE_include_directive : enable
 
 #define USE_EASU 1
+#define VERSION 1
 
 #include "fidelityfx_fsr.frag"
diff --git a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.frag b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.frag
index 0c953a9009..c017214a51 100644
--- a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.frag
+++ b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.frag
@@ -6,5 +6,6 @@
 
 #define YUZU_USE_FP16
 #define USE_RCAS 1
+#define VERSION 1
 
 #include "fidelityfx_fsr.frag"
diff --git a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.frag b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.frag
index 02e9a27c65..976825f4bb 100644
--- a/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.frag
+++ b/src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.frag
@@ -5,5 +5,6 @@
 #extension GL_GOOGLE_include_directive : enable
 
 #define USE_RCAS 1
+#define VERSION 1
 
 #include "fidelityfx_fsr.frag"
diff --git a/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag b/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag
index 79ea817c2f..cea5dac9da 100644
--- a/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag
+++ b/src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag
@@ -5,7 +5,7 @@
 
 #extension GL_GOOGLE_include_directive : enable
 
-#define VERSION 1
+#define VERSION 2
 #define YUZU_USE_FP16
 
 #include "opengl_present_scaleforce.frag"
diff --git a/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag b/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag
index 9605bb58bd..10ddf04019 100644
--- a/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag
+++ b/src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag
@@ -5,6 +5,6 @@
 
 #extension GL_GOOGLE_include_directive : enable
 
-#define VERSION 1
+#define VERSION 2
 
 #include "opengl_present_scaleforce.frag"
diff --git a/src/video_core/renderer_opengl/present/window_adapt_pass.cpp b/src/video_core/renderer_opengl/present/window_adapt_pass.cpp
index 4d681606b3..0328abd708 100644
--- a/src/video_core/renderer_opengl/present/window_adapt_pass.cpp
+++ b/src/video_core/renderer_opengl/present/window_adapt_pass.cpp
@@ -92,6 +92,21 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
     glClear(GL_COLOR_BUFFER_BIT);
 
     for (size_t i = 0; i < layer_count; i++) {
+        switch (framebuffers[i].blending) {
+        case Tegra::BlendMode::Opaque:
+        default:
+            glDisablei(GL_BLEND, 0);
+            break;
+        case Tegra::BlendMode::Premultiplied:
+            glEnablei(GL_BLEND, 0);
+            glBlendFuncSeparatei(0, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
+            break;
+        case Tegra::BlendMode::Coverage:
+            glEnablei(GL_BLEND, 0);
+            glBlendFuncSeparatei(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
+            break;
+        }
+
         glBindTextureUnit(0, textures[i]);
         glProgramUniformMatrix3x2fv(vert.handle, ModelViewMatrixLocation, 1, GL_FALSE,
                                     matrices[i].data());
diff --git a/src/video_core/renderer_vulkan/present/util.cpp b/src/video_core/renderer_vulkan/present/util.cpp
index 6ee16595df..7f27c7c1b5 100644
--- a/src/video_core/renderer_vulkan/present/util.cpp
+++ b/src/video_core/renderer_vulkan/present/util.cpp
@@ -362,10 +362,10 @@ vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device,
     });
 }
 
-vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
-                                   vk::PipelineLayout& layout,
-                                   std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders,
-                                   bool enable_blending) {
+static vk::Pipeline CreateWrappedPipelineImpl(
+    const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
+    std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders,
+    VkPipelineColorBlendAttachmentState blending) {
     const std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{{
         {
             .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
@@ -443,30 +443,6 @@ vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderp
         .alphaToOneEnable = VK_FALSE,
     };
 
-    constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_disabled{
-        .blendEnable = VK_FALSE,
-        .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO,
-        .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
-        .colorBlendOp = VK_BLEND_OP_ADD,
-        .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
-        .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
-        .alphaBlendOp = VK_BLEND_OP_ADD,
-        .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
-                          VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
-    };
-
-    constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_enabled{
-        .blendEnable = VK_TRUE,
-        .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
-        .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
-        .colorBlendOp = VK_BLEND_OP_ADD,
-        .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
-        .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
-        .alphaBlendOp = VK_BLEND_OP_ADD,
-        .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
-                          VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
-    };
-
     const VkPipelineColorBlendStateCreateInfo color_blend_ci{
         .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
         .pNext = nullptr,
@@ -474,8 +450,7 @@ vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderp
         .logicOpEnable = VK_FALSE,
         .logicOp = VK_LOGIC_OP_COPY,
         .attachmentCount = 1,
-        .pAttachments =
-            enable_blending ? &color_blend_attachment_enabled : &color_blend_attachment_disabled,
+        .pAttachments = &blending,
         .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
     };
 
@@ -515,6 +490,63 @@ vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderp
     });
 }
 
+vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
+                                   vk::PipelineLayout& layout,
+                                   std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders) {
+    constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_disabled{
+        .blendEnable = VK_FALSE,
+        .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO,
+        .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
+        .colorBlendOp = VK_BLEND_OP_ADD,
+        .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+        .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+        .alphaBlendOp = VK_BLEND_OP_ADD,
+        .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+                          VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
+    };
+
+    return CreateWrappedPipelineImpl(device, renderpass, layout, shaders,
+                                     color_blend_attachment_disabled);
+}
+
+vk::Pipeline CreateWrappedPremultipliedBlendingPipeline(
+    const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
+    std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders) {
+    constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_premultiplied{
+        .blendEnable = VK_TRUE,
+        .srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
+        .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+        .colorBlendOp = VK_BLEND_OP_ADD,
+        .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
+        .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+        .alphaBlendOp = VK_BLEND_OP_ADD,
+        .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+                          VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
+    };
+
+    return CreateWrappedPipelineImpl(device, renderpass, layout, shaders,
+                                     color_blend_attachment_premultiplied);
+}
+
+vk::Pipeline CreateWrappedCoverageBlendingPipeline(
+    const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
+    std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders) {
+    constexpr VkPipelineColorBlendAttachmentState color_blend_attachment_coverage{
+        .blendEnable = VK_TRUE,
+        .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
+        .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+        .colorBlendOp = VK_BLEND_OP_ADD,
+        .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
+        .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+        .alphaBlendOp = VK_BLEND_OP_ADD,
+        .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+                          VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
+    };
+
+    return CreateWrappedPipelineImpl(device, renderpass, layout, shaders,
+                                     color_blend_attachment_coverage);
+}
+
 VkWriteDescriptorSet CreateWriteDescriptorSet(std::vector<VkDescriptorImageInfo>& images,
                                               VkSampler sampler, VkImageView view,
                                               VkDescriptorSet set, u32 binding) {
diff --git a/src/video_core/renderer_vulkan/present/util.h b/src/video_core/renderer_vulkan/present/util.h
index 1104aaa157..5b22f0fa82 100644
--- a/src/video_core/renderer_vulkan/present/util.h
+++ b/src/video_core/renderer_vulkan/present/util.h
@@ -42,8 +42,13 @@ vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device,
                                                vk::DescriptorSetLayout& layout);
 vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
                                    vk::PipelineLayout& layout,
-                                   std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders,
-                                   bool enable_blending = false);
+                                   std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
+vk::Pipeline CreateWrappedPremultipliedBlendingPipeline(
+    const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
+    std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
+vk::Pipeline CreateWrappedCoverageBlendingPipeline(
+    const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout,
+    std::tuple<vk::ShaderModule&, vk::ShaderModule&> shaders);
 VkWriteDescriptorSet CreateWriteDescriptorSet(std::vector<VkDescriptorImageInfo>& images,
                                               VkSampler sampler, VkImageView view,
                                               VkDescriptorSet set, u32 binding);
diff --git a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp
index c5db0230d9..22ffacf119 100644
--- a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp
+++ b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp
@@ -22,7 +22,7 @@ WindowAdaptPass::WindowAdaptPass(const Device& device_, VkFormat frame_format,
     CreatePipelineLayout();
     CreateVertexShader();
     CreateRenderPass(frame_format);
-    CreatePipeline();
+    CreatePipelines();
 }
 
 WindowAdaptPass::~WindowAdaptPass() = default;
@@ -34,7 +34,6 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s
 
     const VkFramebuffer host_framebuffer{*dst->framebuffer};
     const VkRenderPass renderpass{*render_pass};
-    const VkPipeline graphics_pipeline{*pipeline};
     const VkPipelineLayout graphics_pipeline_layout{*pipeline_layout};
     const VkExtent2D render_area{
         .width = dst->width,
@@ -44,9 +43,23 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s
     const size_t layer_count = configs.size();
     std::vector<PresentPushConstants> push_constants(layer_count);
     std::vector<VkDescriptorSet> descriptor_sets(layer_count);
+    std::vector<VkPipeline> graphics_pipelines(layer_count);
 
     auto layer_it = layers.begin();
     for (size_t i = 0; i < layer_count; i++) {
+        switch (configs[i].blending) {
+        case Tegra::BlendMode::Opaque:
+        default:
+            graphics_pipelines[i] = *opaque_pipeline;
+            break;
+        case Tegra::BlendMode::Premultiplied:
+            graphics_pipelines[i] = *premultiplied_pipeline;
+            break;
+        case Tegra::BlendMode::Coverage:
+            graphics_pipelines[i] = *coverage_pipeline;
+            break;
+        }
+
         layer_it->ConfigureDraw(&push_constants[i], &descriptor_sets[i], rasterizer, *sampler,
                                 image_index, configs[i], layout);
         layer_it++;
@@ -77,8 +90,8 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s
         BeginRenderPass(cmdbuf, renderpass, host_framebuffer, render_area);
         cmdbuf.ClearAttachments({clear_attachment}, {clear_rect});
 
-        cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline);
         for (size_t i = 0; i < layer_count; i++) {
+            cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipelines[i]);
             cmdbuf.PushConstants(graphics_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT,
                                  push_constants[i]);
             cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline_layout, 0,
@@ -129,9 +142,13 @@ void WindowAdaptPass::CreateRenderPass(VkFormat frame_format) {
     render_pass = CreateWrappedRenderPass(device, frame_format, VK_IMAGE_LAYOUT_UNDEFINED);
 }
 
-void WindowAdaptPass::CreatePipeline() {
-    pipeline = CreateWrappedPipeline(device, render_pass, pipeline_layout,
-                                     std::tie(vertex_shader, fragment_shader), false);
+void WindowAdaptPass::CreatePipelines() {
+    opaque_pipeline = CreateWrappedPipeline(device, render_pass, pipeline_layout,
+                                            std::tie(vertex_shader, fragment_shader));
+    premultiplied_pipeline = CreateWrappedPremultipliedBlendingPipeline(
+        device, render_pass, pipeline_layout, std::tie(vertex_shader, fragment_shader));
+    coverage_pipeline = CreateWrappedCoverageBlendingPipeline(
+        device, render_pass, pipeline_layout, std::tie(vertex_shader, fragment_shader));
 }
 
 } // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/present/window_adapt_pass.h b/src/video_core/renderer_vulkan/present/window_adapt_pass.h
index 0e2edfc312..cf667a4fc6 100644
--- a/src/video_core/renderer_vulkan/present/window_adapt_pass.h
+++ b/src/video_core/renderer_vulkan/present/window_adapt_pass.h
@@ -42,7 +42,7 @@ private:
     void CreatePipelineLayout();
     void CreateVertexShader();
     void CreateRenderPass(VkFormat frame_format);
-    void CreatePipeline();
+    void CreatePipelines();
 
 private:
     const Device& device;
@@ -52,7 +52,9 @@ private:
     vk::ShaderModule vertex_shader;
     vk::ShaderModule fragment_shader;
     vk::RenderPass render_pass;
-    vk::Pipeline pipeline;
+    vk::Pipeline opaque_pipeline;
+    vk::Pipeline premultiplied_pipeline;
+    vk::Pipeline coverage_pipeline;
 };
 
 } // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 48a1053277..c7c234fd81 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -101,8 +101,10 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
                       surface),
       blit_swapchain(device_memory, device, memory_allocator, present_manager, scheduler),
       blit_screenshot(device_memory, device, memory_allocator, present_manager, scheduler),
+      blit_application_layer(device_memory, device, memory_allocator, present_manager, scheduler),
       rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker,
-                 scheduler) {
+                 scheduler),
+      application_frame() {
     if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
         turbo_mode.emplace(instance, dld);
         scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index c6d8a0f216..ed9c7af7f1 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -80,8 +80,11 @@ private:
     PresentManager present_manager;
     BlitScreen blit_swapchain;
     BlitScreen blit_screenshot;
+    BlitScreen blit_application_layer;
     RasterizerVulkan rasterizer;
     std::optional<TurboMode> turbo_mode;
+
+    Frame application_frame;
 };
 
 } // namespace Vulkan
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index a20c956ffc..3a1cc060e7 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -746,7 +746,13 @@ std::pair<typename P::ImageView*, bool> TextureCache<P>::TryFindFramebufferImage
     }();
 
     const auto GetImageViewForFramebuffer = [&](ImageId image_id) {
-        const ImageViewInfo info{ImageViewType::e2D, view_format};
+        ImageViewInfo info{ImageViewType::e2D, view_format};
+        if (config.blending == Tegra::BlendMode::Opaque) {
+            info.x_source = static_cast<u8>(SwizzleSource::R);
+            info.y_source = static_cast<u8>(SwizzleSource::G);
+            info.z_source = static_cast<u8>(SwizzleSource::B);
+            info.w_source = static_cast<u8>(SwizzleSource::OneFloat);
+        }
         return std::make_pair(&slot_image_views[FindOrEmplaceImageView(image_id, info)],
                               slot_images[image_id].IsRescaled());
     };