Преглед изворни кода

Merge pull request #12756 from liamwhite/applet-multiprocess-hwc

general: applet multiprocess
Narr the Reg пре 2 година
родитељ
комит
2ff45cd0da
79 измењених фајлова са 1173 додато и 255 уклоњено
  1. 3 0
      src/common/settings.cpp
  2. 32 0
      src/common/settings.h
  3. 1 0
      src/common/settings_common.h
  4. 2 0
      src/common/settings_enums.h
  5. 4 0
      src/core/file_sys/content_archive.cpp
  6. 1 0
      src/core/file_sys/content_archive.h
  7. 6 1
      src/core/hle/kernel/k_process.cpp
  8. 3 3
      src/core/hle/service/am/am_types.h
  9. 6 6
      src/core/hle/service/am/frontend/applet_software_keyboard.cpp
  10. 1 1
      src/core/hle/service/am/frontend/applet_software_keyboard.h
  11. 60 21
      src/core/hle/service/am/library_applet_creator.cpp
  12. 18 5
      src/core/hle/service/am/process.cpp
  13. 1 1
      src/core/hle/service/am/process.h
  14. 16 2
      src/core/hle/service/am/self_controller.cpp
  15. 2 0
      src/core/hle/service/am/self_controller.h
  16. 15 4
      src/core/hle/service/am/system_buffer_manager.cpp
  17. 2 1
      src/core/hle/service/am/system_buffer_manager.h
  18. 2 2
      src/core/hle/service/am/window_controller.cpp
  19. 6 1
      src/core/hle/service/nvdrv/core/container.cpp
  20. 1 0
      src/core/hle/service/nvdrv/core/container.h
  21. 6 2
      src/core/hle/service/nvdrv/core/nvmap.cpp
  22. 17 0
      src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
  23. 139 37
      src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
  24. 19 5
      src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
  25. 1 0
      src/core/hle/service/nvnflinger/hardware_composer.cpp
  26. 13 0
      src/core/hle/service/nvnflinger/hwc_layer.h
  27. 4 3
      src/core/hle/service/nvnflinger/nvnflinger.cpp
  28. 4 2
      src/core/hle/service/nvnflinger/nvnflinger.h
  29. 4 2
      src/core/hle/service/vi/layer/vi_layer.cpp
  30. 13 0
      src/core/hle/service/vi/layer/vi_layer.h
  31. 18 0
      src/frontend_common/config.cpp
  32. 2 0
      src/frontend_common/config.h
  33. 2 0
      src/video_core/CMakeLists.txt
  34. 36 0
      src/video_core/capture.h
  35. 7 0
      src/video_core/framebuffer_config.h
  36. 15 0
      src/video_core/gpu.cpp
  37. 2 0
      src/video_core/gpu.h
  38. 10 11
      src/video_core/host_shaders/fidelityfx_fsr.frag
  39. 1 1
      src/video_core/host_shaders/fxaa.frag
  40. 9 10
      src/video_core/host_shaders/opengl_fidelityfx_fsr.frag
  41. 1 1
      src/video_core/host_shaders/opengl_present.frag
  42. 1 1
      src/video_core/host_shaders/present_bicubic.frag
  43. 7 7
      src/video_core/host_shaders/present_gaussian.frag
  44. 1 0
      src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16.frag
  45. 1 0
      src/video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32.frag
  46. 1 0
      src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16.frag
  47. 1 0
      src/video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp32.frag
  48. 1 1
      src/video_core/host_shaders/vulkan_present_scaleforce_fp16.frag
  49. 1 1
      src/video_core/host_shaders/vulkan_present_scaleforce_fp32.frag
  50. 37 0
      src/video_core/present.h
  51. 3 0
      src/video_core/renderer_base.h
  52. 5 0
      src/video_core/renderer_null/renderer_null.cpp
  53. 2 0
      src/video_core/renderer_null/renderer_null.h
  54. 8 7
      src/video_core/renderer_opengl/gl_blit_screen.cpp
  55. 5 2
      src/video_core/renderer_opengl/gl_blit_screen.h
  56. 22 13
      src/video_core/renderer_opengl/present/layer.cpp
  57. 6 2
      src/video_core/renderer_opengl/present/layer.h
  58. 17 2
      src/video_core/renderer_opengl/present/window_adapt_pass.cpp
  59. 1 1
      src/video_core/renderer_opengl/present/window_adapt_pass.h
  60. 77 12
      src/video_core/renderer_opengl/renderer_opengl.cpp
  61. 9 0
      src/video_core/renderer_opengl/renderer_opengl.h
  62. 11 8
      src/video_core/renderer_vulkan/present/layer.cpp
  63. 5 1
      src/video_core/renderer_vulkan/present/layer.h
  64. 62 30
      src/video_core/renderer_vulkan/present/util.cpp
  65. 7 2
      src/video_core/renderer_vulkan/present/util.h
  66. 23 6
      src/video_core/renderer_vulkan/present/window_adapt_pass.cpp
  67. 4 2
      src/video_core/renderer_vulkan/present/window_adapt_pass.h
  68. 90 24
      src/video_core/renderer_vulkan/renderer_vulkan.cpp
  69. 10 1
      src/video_core/renderer_vulkan/renderer_vulkan.h
  70. 7 7
      src/video_core/renderer_vulkan/vk_blit_screen.cpp
  71. 4 1
      src/video_core/renderer_vulkan/vk_blit_screen.h
  72. 7 1
      src/video_core/texture_cache/texture_cache.h
  73. 3 0
      src/yuzu/CMakeLists.txt
  74. 86 0
      src/yuzu/configuration/configure_applets.cpp
  75. 48 0
      src/yuzu/configuration/configure_applets.h
  76. 65 0
      src/yuzu/configuration/configure_applets.ui
  77. 6 1
      src/yuzu/configuration/configure_dialog.cpp
  78. 2 0
      src/yuzu/configuration/configure_dialog.h
  79. 22 0
      src/yuzu/configuration/shared_translation.cpp

+ 3 - 0
src/common/settings.cpp

@@ -30,6 +30,7 @@ namespace Settings {
 #define SETTING(TYPE, RANGED) template class Setting<TYPE, RANGED>
 #define SWITCHABLE(TYPE, RANGED) template class SwitchableSetting<TYPE, RANGED>
 
+SETTING(AppletMode, false);
 SETTING(AudioEngine, false);
 SETTING(bool, false);
 SETTING(int, false);
@@ -215,6 +216,8 @@ const char* TranslateCategory(Category category) {
         return "Debugging";
     case Category::GpuDriver:
         return "GpuDriver";
+    case Category::LibraryApplet:
+        return "LibraryApplet";
     case Category::Miscellaneous:
         return "Miscellaneous";
     case Category::Network:

+ 32 - 0
src/common/settings.h

@@ -133,6 +133,38 @@ struct TouchFromButtonMap {
 struct Values {
     Linkage linkage{};
 
+    // Applet
+    Setting<AppletMode> cabinet_applet_mode{linkage, AppletMode::LLE, "cabinet_applet_mode",
+                                            Category::LibraryApplet};
+    Setting<AppletMode> controller_applet_mode{linkage, AppletMode::HLE, "controller_applet_mode",
+                                               Category::LibraryApplet};
+    Setting<AppletMode> data_erase_applet_mode{linkage, AppletMode::HLE, "data_erase_applet_mode",
+                                               Category::LibraryApplet};
+    Setting<AppletMode> error_applet_mode{linkage, AppletMode::HLE, "error_applet_mode",
+                                          Category::LibraryApplet};
+    Setting<AppletMode> net_connect_applet_mode{linkage, AppletMode::HLE, "net_connect_applet_mode",
+                                                Category::LibraryApplet};
+    Setting<AppletMode> player_select_applet_mode{
+        linkage, AppletMode::HLE, "player_select_applet_mode", Category::LibraryApplet};
+    Setting<AppletMode> swkbd_applet_mode{linkage, AppletMode::LLE, "swkbd_applet_mode",
+                                          Category::LibraryApplet};
+    Setting<AppletMode> mii_edit_applet_mode{linkage, AppletMode::LLE, "mii_edit_applet_mode",
+                                             Category::LibraryApplet};
+    Setting<AppletMode> web_applet_mode{linkage, AppletMode::HLE, "web_applet_mode",
+                                        Category::LibraryApplet};
+    Setting<AppletMode> shop_applet_mode{linkage, AppletMode::HLE, "shop_applet_mode",
+                                         Category::LibraryApplet};
+    Setting<AppletMode> photo_viewer_applet_mode{
+        linkage, AppletMode::LLE, "photo_viewer_applet_mode", Category::LibraryApplet};
+    Setting<AppletMode> offline_web_applet_mode{linkage, AppletMode::LLE, "offline_web_applet_mode",
+                                                Category::LibraryApplet};
+    Setting<AppletMode> login_share_applet_mode{linkage, AppletMode::HLE, "login_share_applet_mode",
+                                                Category::LibraryApplet};
+    Setting<AppletMode> wifi_web_auth_applet_mode{
+        linkage, AppletMode::HLE, "wifi_web_auth_applet_mode", Category::LibraryApplet};
+    Setting<AppletMode> my_page_applet_mode{linkage, AppletMode::LLE, "my_page_applet_mode",
+                                            Category::LibraryApplet};
+
     // Audio
     SwitchableSetting<AudioEngine> sink_id{linkage, AudioEngine::Auto, "output_engine",
                                            Category::Audio, Specialization::RuntimeList};

+ 1 - 0
src/common/settings_common.h

@@ -44,6 +44,7 @@ enum class Category : u32 {
     Services,
     Paths,
     Linux,
+    LibraryApplet,
     MaxEnum,
 };
 

+ 2 - 0
src/common/settings_enums.h

@@ -151,6 +151,8 @@ ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch);
 
 ENUM(ConsoleMode, Handheld, Docked);
 
+ENUM(AppletMode, HLE, LLE);
+
 template <typename Type>
 inline std::string CanonicalizeEnum(Type id) {
     const auto group = EnumMetadata<Type>::Canonicalizations();

+ 4 - 0
src/core/file_sys/content_archive.cpp

@@ -172,6 +172,10 @@ u32 NCA::GetSDKVersion() const {
     return reader->GetSdkAddonVersion();
 }
 
+u8 NCA::GetKeyGeneration() const {
+    return reader->GetKeyGeneration();
+}
+
 bool NCA::IsUpdate() const {
     return is_update;
 }

+ 1 - 0
src/core/file_sys/content_archive.h

@@ -77,6 +77,7 @@ public:
     u64 GetTitleId() const;
     RightsId GetRightsId() const;
     u32 GetSDKVersion() const;
+    u8 GetKeyGeneration() const;
     bool IsUpdate() const;
 
     VirtualFile GetRomFS() const;

+ 6 - 1
src/core/hle/kernel/k_process.cpp

@@ -4,8 +4,9 @@
 #include <random>
 #include "common/scope_exit.h"
 #include "common/settings.h"
+#include "core/arm/dynarmic/arm_dynarmic.h"
+#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
 #include "core/core.h"
-#include "core/gpu_dirty_memory_manager.h"
 #include "core/hle/kernel/k_process.h"
 #include "core/hle/kernel/k_scoped_resource_reservation.h"
 #include "core/hle/kernel/k_shared_memory.h"
@@ -1258,6 +1259,10 @@ void KProcess::InitializeInterfaces() {
 
 #ifdef HAS_NCE
     if (this->IsApplication() && Settings::IsNceEnabled()) {
+        // Register the scoped JIT handler before creating any NCE instances
+        // so that its signal handler will appear first in the signal chain.
+        Core::ScopedJitExecution::RegisterHandler();
+
         for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
             m_arm_interfaces[i] = std::make_unique<Core::ArmNce>(m_kernel.System(), true, i);
         }

+ 3 - 3
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,
 };
 

+ 6 - 6
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);
     }
 }
 

+ 1 - 1
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();

+ 60 - 21
src/core/hle/service/am/library_applet_creator.cpp

@@ -1,6 +1,7 @@
 // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include "common/settings.h"
 #include "core/hle/kernel/k_transfer_memory.h"
 #include "core/hle/service/am/applet_data_broker.h"
 #include "core/hle/service/am/applet_manager.h"
@@ -16,6 +17,34 @@ namespace Service::AM {
 
 namespace {
 
+bool ShouldCreateGuestApplet(AppletId applet_id) {
+#define X(Name, name)                                                                              \
+    if (applet_id == AppletId::Name &&                                                             \
+        Settings::values.name##_applet_mode.GetValue() != Settings::AppletMode::LLE) {             \
+        return false;                                                                              \
+    }
+
+    X(Cabinet, cabinet)
+    X(Controller, controller)
+    X(DataErase, data_erase)
+    X(Error, error)
+    X(NetConnect, net_connect)
+    X(ProfileSelect, player_select)
+    X(SoftwareKeyboard, swkbd)
+    X(MiiEdit, mii_edit)
+    X(Web, web)
+    X(Shop, shop)
+    X(PhotoViewer, photo_viewer)
+    X(OfflineWeb, offline_web)
+    X(LoginShare, login_share)
+    X(WebAuth, wifi_web_auth)
+    X(MyPage, my_page)
+
+#undef X
+
+    return true;
+}
+
 AppletProgramId AppletIdToProgramId(AppletId applet_id) {
     switch (applet_id) {
     case AppletId::OverlayDisplay:
@@ -63,17 +92,26 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) {
     }
 }
 
-[[maybe_unused]] std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(
-    Core::System& system, std::shared_ptr<Applet> caller_applet, AppletId applet_id,
-    LibraryAppletMode mode) {
+std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
+                                                          std::shared_ptr<Applet> caller_applet,
+                                                          AppletId applet_id,
+                                                          LibraryAppletMode mode) {
     const auto program_id = static_cast<u64>(AppletIdToProgramId(applet_id));
     if (program_id == 0) {
         // Unknown applet
         return {};
     }
 
+    // TODO: enable other versions of applets
+    enum : u8 {
+        Firmware1400 = 14,
+        Firmware1500 = 15,
+        Firmware1600 = 16,
+        Firmware1700 = 17,
+    };
+
     auto process = std::make_unique<Process>(system);
-    if (!process->Initialize(program_id)) {
+    if (!process->Initialize(program_id, Firmware1400, Firmware1700)) {
         // Couldn't initialize the guest process
         return {};
     }
@@ -87,24 +125,18 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) {
     // Set focus state
     switch (mode) {
     case LibraryAppletMode::AllForeground:
-    case LibraryAppletMode::NoUI:
-        applet->focus_state = FocusState::InFocus;
+    case LibraryAppletMode::NoUi:
+    case LibraryAppletMode::PartialForeground:
+    case LibraryAppletMode::PartialForegroundIndirectDisplay:
         applet->hid_registration.EnableAppletToGetInput(true);
+        applet->focus_state = FocusState::InFocus;
         applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
-        applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
         break;
     case LibraryAppletMode::AllForegroundInitiallyHidden:
-        applet->system_buffer_manager.SetWindowVisibility(false);
-        applet->focus_state = FocusState::NotInFocus;
         applet->hid_registration.EnableAppletToGetInput(false);
-        applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
-        break;
-    case LibraryAppletMode::Background:
-    case LibraryAppletMode::BackgroundIndirectDisplay:
-    default:
-        applet->focus_state = FocusState::Background;
-        applet->hid_registration.EnableAppletToGetInput(true);
-        applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
+        applet->focus_state = FocusState::NotInFocus;
+        applet->system_buffer_manager.SetWindowVisibility(false);
+        applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoBackground);
         break;
     }
 
@@ -117,9 +149,10 @@ AppletProgramId AppletIdToProgramId(AppletId applet_id) {
     return std::make_shared<ILibraryAppletAccessor>(system, broker, applet);
 }
 
-[[maybe_unused]] std::shared_ptr<ILibraryAppletAccessor> CreateFrontendApplet(
-    Core::System& system, std::shared_ptr<Applet> caller_applet, AppletId applet_id,
-    LibraryAppletMode mode) {
+std::shared_ptr<ILibraryAppletAccessor> CreateFrontendApplet(Core::System& system,
+                                                             std::shared_ptr<Applet> caller_applet,
+                                                             AppletId applet_id,
+                                                             LibraryAppletMode mode) {
     const auto program_id = static_cast<u64>(AppletIdToProgramId(applet_id));
 
     auto process = std::make_unique<Process>(system);
@@ -163,7 +196,13 @@ void ILibraryAppletCreator::CreateLibraryApplet(HLERequestContext& ctx) {
     LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", applet_id,
               applet_mode);
 
-    auto library_applet = CreateFrontendApplet(system, applet, applet_id, applet_mode);
+    std::shared_ptr<ILibraryAppletAccessor> library_applet;
+    if (ShouldCreateGuestApplet(applet_id)) {
+        library_applet = CreateGuestApplet(system, applet, applet_id, applet_mode);
+    }
+    if (!library_applet) {
+        library_applet = CreateFrontendApplet(system, applet, applet_id, applet_mode);
+    }
     if (!library_applet) {
         LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id);
 

+ 18 - 5
src/core/hle/service/am/process.cpp

@@ -3,6 +3,7 @@
 
 #include "common/scope_exit.h"
 
+#include "core/file_sys/content_archive.h"
 #include "core/file_sys/nca_metadata.h"
 #include "core/file_sys/registered_cache.h"
 #include "core/hle/kernel/k_process.h"
@@ -20,7 +21,7 @@ Process::~Process() {
     this->Finalize();
 }
 
-bool Process::Initialize(u64 program_id) {
+bool Process::Initialize(u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation) {
     // First, ensure we are not holding another process.
     this->Finalize();
 
@@ -29,21 +30,33 @@ bool Process::Initialize(u64 program_id) {
 
     // Attempt to load program NCA.
     const FileSys::RegisteredCache* bis_system{};
-    FileSys::VirtualFile nca{};
+    FileSys::VirtualFile nca_raw{};
 
     // Get the program NCA from built-in storage.
     bis_system = fsc.GetSystemNANDContents();
     if (bis_system) {
-        nca = bis_system->GetEntryRaw(program_id, FileSys::ContentRecordType::Program);
+        nca_raw = bis_system->GetEntryRaw(program_id, FileSys::ContentRecordType::Program);
     }
 
     // Ensure we retrieved a program NCA.
-    if (!nca) {
+    if (!nca_raw) {
         return false;
     }
 
+    // Ensure we have a suitable version.
+    if (minimum_key_generation > 0) {
+        FileSys::NCA nca(nca_raw);
+        if (nca.GetStatus() == Loader::ResultStatus::Success &&
+            (nca.GetKeyGeneration() < minimum_key_generation ||
+             nca.GetKeyGeneration() > maximum_key_generation)) {
+            LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id,
+                        nca.GetKeyGeneration());
+            return false;
+        }
+    }
+
     // Get the appropriate loader to parse this NCA.
-    auto app_loader = Loader::GetLoader(m_system, nca, program_id, 0);
+    auto app_loader = Loader::GetLoader(m_system, nca_raw, program_id, 0);
 
     // Ensure we have a loader which can parse the NCA.
     if (!app_loader) {

+ 1 - 1
src/core/hle/service/am/process.h

@@ -21,7 +21,7 @@ public:
     explicit Process(Core::System& system);
     ~Process();
 
-    bool Initialize(u64 program_id);
+    bool Initialize(u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation);
     void Finalize();
 
     bool Run();

+ 16 - 2
src/core/hle/service/am/self_controller.cpp

@@ -1,10 +1,13 @@
 // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include "common/logging/log.h"
+#include "core/hle/result.h"
 #include "core/hle/service/am/am_results.h"
 #include "core/hle/service/am/frontend/applets.h"
 #include "core/hle/service/am/self_controller.h"
 #include "core/hle/service/caps/caps_su.h"
+#include "core/hle/service/hle_ipc.h"
 #include "core/hle/service/ipc_helpers.h"
 #include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
 #include "core/hle/service/nvnflinger/nvnflinger.h"
@@ -47,7 +50,7 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet>
         {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
         {51, &ISelfController::ApproveToDisplay, "ApproveToDisplay"},
         {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
-        {61, nullptr, "SetMediaPlaybackState"},
+        {61, &ISelfController::SetMediaPlaybackState, "SetMediaPlaybackState"},
         {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"},
         {63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"},
         {64, nullptr, "SetInputDetectionSourceSet"},
@@ -288,7 +291,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;
     }
 
@@ -323,6 +327,16 @@ void ISelfController::ApproveToDisplay(HLERequestContext& ctx) {
     rb.Push(ResultSuccess);
 }
 
+void ISelfController::SetMediaPlaybackState(HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const u8 state = rp.Pop<u8>();
+
+    LOG_WARNING(Service_AM, "(STUBBED) called, state={}", state);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
 void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
 

+ 2 - 0
src/core/hle/service/am/self_controller.h

@@ -3,6 +3,7 @@
 
 #pragma once
 
+#include "core/hle/service/hle_ipc.h"
 #include "core/hle/service/kernel_helpers.h"
 #include "core/hle/service/service.h"
 
@@ -38,6 +39,7 @@ private:
     void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx);
     void SetHandlesRequestToDisplay(HLERequestContext& ctx);
     void ApproveToDisplay(HLERequestContext& ctx);
+    void SetMediaPlaybackState(HLERequestContext& ctx);
     void SetIdleTimeDetectionExtension(HLERequestContext& ctx);
     void GetIdleTimeDetectionExtension(HLERequestContext& ctx);
     void ReportUserIsActive(HLERequestContext& ctx);

+ 15 - 4
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,15 @@ bool SystemBufferManager::Initialize(Nvnflinger::Nvnflinger* nvnflinger, Kernel:
         return false;
     }
 
+    Nvnflinger::LayerBlending blending = Nvnflinger::LayerBlending::None;
+    if (mode == LibraryAppletMode::PartialForeground ||
+        mode == LibraryAppletMode::PartialForegroundIndirectDisplay) {
+        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;
@@ -62,8 +69,12 @@ void SystemBufferManager::SetWindowVisibility(bool visible) {
 
 Result SystemBufferManager::WriteAppletCaptureBuffer(bool* out_was_written,
                                                      s32* out_fbshare_layer_index) {
-    // TODO
-    R_SUCCEED();
+    if (!m_buffer_sharing_enabled) {
+        return VI::ResultPermissionDenied;
+    }
+
+    return m_nvnflinger->GetSystemBufferManager().WriteAppletCaptureBuffer(out_was_written,
+                                                                           out_fbshare_layer_index);
 }
 
 } // namespace Service::AM

+ 2 - 1
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) {

+ 2 - 2
src/core/hle/service/am/window_controller.cpp

@@ -62,12 +62,12 @@ void IWindowController::SetAppletWindowVisibility(HLERequestContext& ctx) {
     applet->hid_registration.EnableAppletToGetInput(visible);
 
     if (visible) {
-        applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
         applet->focus_state = FocusState::InFocus;
+        applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
     } else {
         applet->focus_state = FocusState::NotInFocus;
+        applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoBackground);
     }
-    applet->message_queue.PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
 
     IPC::ResponseBuilder rb{ctx, 2};
     rb.Push(ResultSuccess);

+ 6 - 1
src/core/hle/service/nvdrv/core/container.cpp

@@ -49,6 +49,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
             continue;
         }
         if (session.process == process) {
+            session.ref_count++;
             return session.id;
         }
     }
@@ -66,6 +67,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
     }
     auto& session = impl->sessions[new_id];
     session.is_active = true;
+    session.ref_count = 1;
     // Optimization
     if (process->IsApplication()) {
         auto& page_table = process->GetPageTable().GetBasePageTable();
@@ -114,8 +116,11 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
 
 void Container::CloseSession(SessionId session_id) {
     std::scoped_lock lk(impl->session_guard);
-    impl->file.UnmapAllHandles(session_id);
     auto& session = impl->sessions[session_id.id];
+    if (--session.ref_count > 0) {
+        return;
+    }
+    impl->file.UnmapAllHandles(session_id);
     auto& smmu = impl->host1x.MemoryManager();
     if (session.has_preallocated_area) {
         const DAddr region_start = session.mapper->GetRegionStart();

+ 1 - 0
src/core/hle/service/nvdrv/core/container.h

@@ -46,6 +46,7 @@ struct Session {
     bool has_preallocated_area{};
     std::unique_ptr<HeapMapper> mapper{};
     bool is_active{};
+    s32 ref_count{};
 };
 
 class Container {

+ 6 - 2
src/core/hle/service/nvdrv/core/nvmap.cpp

@@ -333,9 +333,13 @@ void NvMap::UnmapAllHandles(NvCore::SessionId session_id) {
     }();
 
     for (auto& [id, handle] : handles_copy) {
-        if (handle->session_id.id == session_id.id) {
-            FreeHandle(id, false);
+        {
+            std::scoped_lock lk{handle->mutex};
+            if (handle->session_id.id != session_id.id || handle->dupes <= 0) {
+                continue;
+            }
         }
+        FreeHandle(id, false);
     }
 }
 

+ 17 - 0
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++) {

+ 139 - 37
src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp

@@ -14,24 +14,20 @@
 #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"
+#include "video_core/host1x/host1x.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 +39,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 +84,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 +152,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 +210,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 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));
 
-    // 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 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 +310,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 +378,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 +415,33 @@ Result FbShareBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadab
     R_SUCCEED();
 }
 
+Result FbShareBufferManager::WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index) {
+    std::vector<u8> capture_buffer(m_system.GPU().GetAppletCaptureBuffer());
+    Common::ScratchBuffer<u32> scratch;
+
+    // TODO: this could be optimized
+    s64 e = -1280 * 768 * 4;
+    for (auto& block : *m_buffer_page_group) {
+        u8* start = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress());
+        u8* end = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress() + block.GetSize());
+
+        for (; start < end; start++) {
+            *start = 0;
+
+            if (e >= 0 && e < static_cast<s64>(capture_buffer.size())) {
+                *start = capture_buffer[e];
+            }
+            e++;
+        }
+
+        m_system.GPU().Host1x().MemoryManager().ApplyOpOnPointer(start, scratch, [&](DAddr addr) {
+            m_system.GPU().InvalidateRegion(addr, end - start);
+        });
+    }
+
+    *out_was_written = true;
+    *out_layer_index = 1;
+    R_SUCCEED();
+}
+
 } // namespace Service::Nvnflinger

+ 19 - 5
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

+ 1 - 0
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,

+ 13 - 0
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;

+ 4 - 3
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) {

+ 4 - 2
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);
 

+ 4 - 2
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;
 

+ 13 - 0
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;
 };

+ 18 - 0
src/frontend_common/config.cpp

@@ -401,6 +401,14 @@ void Config::ReadNetworkValues() {
     EndGroup();
 }
 
+void Config::ReadLibraryAppletValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::LibraryApplet));
+
+    ReadCategory(Settings::Category::LibraryApplet);
+
+    EndGroup();
+}
+
 void Config::ReadValues() {
     if (global) {
         ReadDataStorageValues();
@@ -410,6 +418,7 @@ void Config::ReadValues() {
         ReadServiceValues();
         ReadWebServiceValues();
         ReadMiscellaneousValues();
+        ReadLibraryAppletValues();
     }
     ReadControlValues();
     ReadCoreValues();
@@ -511,6 +520,7 @@ void Config::SaveValues() {
         SaveNetworkValues();
         SaveWebServiceValues();
         SaveMiscellaneousValues();
+        SaveLibraryAppletValues();
     } else {
         LOG_DEBUG(Config, "Saving only generic configuration values");
     }
@@ -691,6 +701,14 @@ void Config::SaveWebServiceValues() {
     EndGroup();
 }
 
+void Config::SaveLibraryAppletValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::LibraryApplet));
+
+    WriteCategory(Settings::Category::LibraryApplet);
+
+    EndGroup();
+}
+
 bool Config::ReadBooleanSetting(const std::string& key, const std::optional<bool> default_value) {
     std::string full_key = GetFullKey(key, false);
     if (!default_value.has_value()) {

+ 2 - 0
src/frontend_common/config.h

@@ -88,6 +88,7 @@ protected:
     void ReadSystemValues();
     void ReadWebServiceValues();
     void ReadNetworkValues();
+    void ReadLibraryAppletValues();
 
     // Read platform specific sections
     virtual void ReadHidbusValues() = 0;
@@ -121,6 +122,7 @@ protected:
     void SaveScreenshotValues();
     void SaveSystemValues();
     void SaveWebServiceValues();
+    void SaveLibraryAppletValues();
 
     // Save platform specific sections
     virtual void SaveHidbusValues() = 0;

+ 2 - 0
src/video_core/CMakeLists.txt

@@ -18,6 +18,7 @@ add_library(video_core STATIC
     buffer_cache/usage_tracker.h
     buffer_cache/word_manager.h
     cache_types.h
+    capture.h
     cdma_pusher.cpp
     cdma_pusher.h
     compatible_formats.cpp
@@ -101,6 +102,7 @@ add_library(video_core STATIC
     memory_manager.cpp
     memory_manager.h
     precompiled_headers.h
+    present.h
     pte_kind.h
     query_cache/bank_base.h
     query_cache/query_base.h

+ 36 - 0
src/video_core/capture.h

@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/alignment.h"
+#include "common/bit_util.h"
+#include "common/common_types.h"
+#include "core/frontend/framebuffer_layout.h"
+#include "video_core/surface.h"
+
+namespace VideoCore::Capture {
+
+constexpr u32 BlockHeight = 4;
+constexpr u32 BlockDepth = 0;
+constexpr u32 BppLog2 = 2;
+
+constexpr auto PixelFormat = Surface::PixelFormat::B8G8R8A8_UNORM;
+
+constexpr auto LinearWidth = Layout::ScreenUndocked::Width;
+constexpr auto LinearHeight = Layout::ScreenUndocked::Height;
+constexpr auto LinearDepth = 1U;
+constexpr auto BytesPerPixel = 4U;
+
+constexpr auto TiledWidth = LinearWidth;
+constexpr auto TiledHeight = Common::AlignUpLog2(LinearHeight, BlockHeight + BlockDepth + BppLog2);
+constexpr auto TiledSize = TiledWidth * TiledHeight * (1 << BppLog2);
+
+constexpr Layout::FramebufferLayout Layout{
+    .width = LinearWidth,
+    .height = LinearHeight,
+    .screen = {0, 0, LinearWidth, LinearHeight},
+    .is_srgb = false,
+};
+
+} // namespace VideoCore::Capture

+ 7 - 0
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,

+ 15 - 0
src/video_core/gpu.cpp

@@ -347,6 +347,17 @@ struct GPU::Impl {
         WaitForSyncOperation(wait_fence);
     }
 
+    std::vector<u8> GetAppletCaptureBuffer() {
+        std::vector<u8> out;
+
+        const auto wait_fence =
+            RequestSyncOperation([&] { out = renderer->GetAppletCaptureBuffer(); });
+        gpu_thread.TickGPU();
+        WaitForSyncOperation(wait_fence);
+
+        return out;
+    }
+
     GPU& gpu;
     Core::System& system;
     Host1x::Host1x& host1x;
@@ -505,6 +516,10 @@ void GPU::RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
     impl->RequestComposite(std::move(layers), std::move(fences));
 }
 
+std::vector<u8> GPU::GetAppletCaptureBuffer() {
+    return impl->GetAppletCaptureBuffer();
+}
+
 u64 GPU::GetTicks() const {
     return impl->GetTicks();
 }

+ 2 - 0
src/video_core/gpu.h

@@ -215,6 +215,8 @@ public:
     void RequestComposite(std::vector<Tegra::FramebufferConfig>&& layers,
                           std::vector<Service::Nvidia::NvFence>&& fences);
 
+    std::vector<u8> GetAppletCaptureBuffer();
+
     /// Performs any additional setup necessary in order to begin GPU emulation.
     /// This can be used to launch any necessary threads and register any necessary
     /// core timing events.

+ 10 - 11
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
 }

+ 1 - 1
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);
 }

+ 9 - 10
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
 }

+ 1 - 1
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));
 }

+ 1 - 1
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);
 }

+ 7 - 7
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;
 }

+ 1 - 0
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"

+ 1 - 0
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"

+ 1 - 0
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"

+ 1 - 0
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"

+ 1 - 1
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"

+ 1 - 1
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"

+ 37 - 0
src/video_core/present.h

@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/settings.h"
+
+static inline Settings::ScalingFilter GetScalingFilter() {
+    return Settings::values.scaling_filter.GetValue();
+}
+
+static inline Settings::AntiAliasing GetAntiAliasing() {
+    return Settings::values.anti_aliasing.GetValue();
+}
+
+static inline Settings::ScalingFilter GetScalingFilterForAppletCapture() {
+    return Settings::ScalingFilter::Bilinear;
+}
+
+static inline Settings::AntiAliasing GetAntiAliasingForAppletCapture() {
+    return Settings::AntiAliasing::None;
+}
+
+struct PresentFilters {
+    Settings::ScalingFilter (*get_scaling_filter)();
+    Settings::AntiAliasing (*get_anti_aliasing)();
+};
+
+constexpr PresentFilters PresentFiltersForDisplay{
+    .get_scaling_filter = &GetScalingFilter,
+    .get_anti_aliasing = &GetAntiAliasing,
+};
+
+constexpr PresentFilters PresentFiltersForAppletCapture{
+    .get_scaling_filter = &GetScalingFilterForAppletCapture,
+    .get_anti_aliasing = &GetAntiAliasingForAppletCapture,
+};

+ 3 - 0
src/video_core/renderer_base.h

@@ -40,6 +40,9 @@ public:
     /// Finalize rendering the guest frame and draw into the presentation texture
     virtual void Composite(std::span<const Tegra::FramebufferConfig> layers) = 0;
 
+    /// Get the tiled applet layer capture buffer
+    virtual std::vector<u8> GetAppletCaptureBuffer() = 0;
+
     [[nodiscard]] virtual RasterizerInterface* ReadRasterizer() = 0;
 
     [[nodiscard]] virtual std::string GetDeviceVendor() const = 0;

+ 5 - 0
src/video_core/renderer_null/renderer_null.cpp

@@ -3,6 +3,7 @@
 
 #include "core/frontend/emu_window.h"
 #include "core/frontend/graphics_context.h"
+#include "video_core/capture.h"
 #include "video_core/renderer_null/renderer_null.h"
 
 namespace Null {
@@ -22,4 +23,8 @@ void RendererNull::Composite(std::span<const Tegra::FramebufferConfig> framebuff
     render_window.OnFrameDisplayed();
 }
 
+std::vector<u8> RendererNull::GetAppletCaptureBuffer() {
+    return std::vector<u8>(VideoCore::Capture::TiledSize);
+}
+
 } // namespace Null

+ 2 - 0
src/video_core/renderer_null/renderer_null.h

@@ -19,6 +19,8 @@ public:
 
     void Composite(std::span<const Tegra::FramebufferConfig> framebuffer) override;
 
+    std::vector<u8> GetAppletCaptureBuffer() override;
+
     VideoCore::RasterizerInterface* ReadRasterizer() override {
         return &m_rasterizer;
     }

+ 8 - 7
src/video_core/renderer_opengl/gl_blit_screen.cpp

@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "common/settings.h"
+#include "video_core/present.h"
 #include "video_core/renderer_opengl/gl_blit_screen.h"
 #include "video_core/renderer_opengl/gl_state_tracker.h"
 #include "video_core/renderer_opengl/present/filters.h"
@@ -13,14 +14,14 @@ namespace OpenGL {
 BlitScreen::BlitScreen(RasterizerOpenGL& rasterizer_,
                        Tegra::MaxwellDeviceMemoryManager& device_memory_,
                        StateTracker& state_tracker_, ProgramManager& program_manager_,
-                       Device& device_)
+                       Device& device_, const PresentFilters& filters_)
     : rasterizer(rasterizer_), device_memory(device_memory_), state_tracker(state_tracker_),
-      program_manager(program_manager_), device(device_) {}
+      program_manager(program_manager_), device(device_), filters(filters_) {}
 
 BlitScreen::~BlitScreen() = default;
 
 void BlitScreen::DrawScreen(std::span<const Tegra::FramebufferConfig> framebuffers,
-                            const Layout::FramebufferLayout& layout) {
+                            const Layout::FramebufferLayout& layout, bool invert_y) {
     // TODO: Signal state tracker about these changes
     state_tracker.NotifyScreenDrawVertexArray();
     state_tracker.NotifyPolygonModes();
@@ -56,22 +57,22 @@ void BlitScreen::DrawScreen(std::span<const Tegra::FramebufferConfig> framebuffe
     glDepthRangeIndexed(0, 0.0, 0.0);
 
     while (layers.size() < framebuffers.size()) {
-        layers.emplace_back(rasterizer, device_memory);
+        layers.emplace_back(rasterizer, device_memory, filters);
     }
 
     CreateWindowAdapt();
-    window_adapt->DrawToFramebuffer(program_manager, layers, framebuffers, layout);
+    window_adapt->DrawToFramebuffer(program_manager, layers, framebuffers, layout, invert_y);
 
     // TODO
     // program_manager.RestoreGuestPipeline();
 }
 
 void BlitScreen::CreateWindowAdapt() {
-    if (window_adapt && Settings::values.scaling_filter.GetValue() == current_window_adapt) {
+    if (window_adapt && filters.get_scaling_filter() == current_window_adapt) {
         return;
     }
 
-    current_window_adapt = Settings::values.scaling_filter.GetValue();
+    current_window_adapt = filters.get_scaling_filter();
     switch (current_window_adapt) {
     case Settings::ScalingFilter::NearestNeighbor:
         window_adapt = MakeNearestNeighbor(device);

+ 5 - 2
src/video_core/renderer_opengl/gl_blit_screen.h

@@ -15,6 +15,8 @@ namespace Layout {
 struct FramebufferLayout;
 }
 
+struct PresentFilters;
+
 namespace Tegra {
 struct FramebufferConfig;
 }
@@ -46,12 +48,12 @@ public:
     explicit BlitScreen(RasterizerOpenGL& rasterizer,
                         Tegra::MaxwellDeviceMemoryManager& device_memory,
                         StateTracker& state_tracker, ProgramManager& program_manager,
-                        Device& device);
+                        Device& device, const PresentFilters& filters);
     ~BlitScreen();
 
     /// Draws the emulated screens to the emulator window.
     void DrawScreen(std::span<const Tegra::FramebufferConfig> framebuffers,
-                    const Layout::FramebufferLayout& layout);
+                    const Layout::FramebufferLayout& layout, bool invert_y);
 
 private:
     void CreateWindowAdapt();
@@ -61,6 +63,7 @@ private:
     StateTracker& state_tracker;
     ProgramManager& program_manager;
     Device& device;
+    const PresentFilters& filters;
 
     Settings::ScalingFilter current_window_adapt{};
     std::unique_ptr<WindowAdaptPass> window_adapt;

+ 22 - 13
src/video_core/renderer_opengl/present/layer.cpp

@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "video_core/framebuffer_config.h"
+#include "video_core/present.h"
 #include "video_core/renderer_opengl/gl_blit_screen.h"
 #include "video_core/renderer_opengl/gl_rasterizer.h"
 #include "video_core/renderer_opengl/present/fsr.h"
@@ -14,8 +15,9 @@
 
 namespace OpenGL {
 
-Layer::Layer(RasterizerOpenGL& rasterizer_, Tegra::MaxwellDeviceMemoryManager& device_memory_)
-    : rasterizer(rasterizer_), device_memory(device_memory_) {
+Layer::Layer(RasterizerOpenGL& rasterizer_, Tegra::MaxwellDeviceMemoryManager& device_memory_,
+             const PresentFilters& filters_)
+    : rasterizer(rasterizer_), device_memory(device_memory_), filters(filters_) {
     // Allocate textures for the screen
     framebuffer_texture.resource.Create(GL_TEXTURE_2D);
 
@@ -34,12 +36,12 @@ GLuint Layer::ConfigureDraw(std::array<GLfloat, 3 * 2>& out_matrix,
                             std::array<ScreenRectVertex, 4>& out_vertices,
                             ProgramManager& program_manager,
                             const Tegra::FramebufferConfig& framebuffer,
-                            const Layout::FramebufferLayout& layout) {
+                            const Layout::FramebufferLayout& layout, bool invert_y) {
     FramebufferTextureInfo info = PrepareRenderTarget(framebuffer);
     auto crop = Tegra::NormalizeCrop(framebuffer, info.width, info.height);
     GLuint texture = info.display_texture;
 
-    auto anti_aliasing = Settings::values.anti_aliasing.GetValue();
+    auto anti_aliasing = filters.get_anti_aliasing();
     if (anti_aliasing != Settings::AntiAliasing::None) {
         glEnablei(GL_SCISSOR_TEST, 0);
         auto viewport_width = Settings::values.resolution_info.ScaleUp(framebuffer_texture.width);
@@ -64,7 +66,7 @@ GLuint Layer::ConfigureDraw(std::array<GLfloat, 3 * 2>& out_matrix,
 
     glDisablei(GL_SCISSOR_TEST, 0);
 
-    if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) {
+    if (filters.get_scaling_filter() == Settings::ScalingFilter::Fsr) {
         if (!fsr || fsr->NeedsRecreation(layout.screen)) {
             fsr = std::make_unique<FSR>(layout.screen.GetWidth(), layout.screen.GetHeight());
         }
@@ -83,10 +85,15 @@ GLuint Layer::ConfigureDraw(std::array<GLfloat, 3 * 2>& out_matrix,
     const auto w = screen.GetWidth();
     const auto h = screen.GetHeight();
 
-    out_vertices[0] = ScreenRectVertex(x, y, crop.left, crop.top);
-    out_vertices[1] = ScreenRectVertex(x + w, y, crop.right, crop.top);
-    out_vertices[2] = ScreenRectVertex(x, y + h, crop.left, crop.bottom);
-    out_vertices[3] = ScreenRectVertex(x + w, y + h, crop.right, crop.bottom);
+    const auto left = crop.left;
+    const auto right = crop.right;
+    const auto top = invert_y ? crop.bottom : crop.top;
+    const auto bottom = invert_y ? crop.top : crop.bottom;
+
+    out_vertices[0] = ScreenRectVertex(x, y, left, top);
+    out_vertices[1] = ScreenRectVertex(x + w, y, right, top);
+    out_vertices[2] = ScreenRectVertex(x, y + h, left, bottom);
+    out_vertices[3] = ScreenRectVertex(x + w, y + h, right, bottom);
 
     return texture;
 }
@@ -131,10 +138,12 @@ FramebufferTextureInfo Layer::LoadFBToScreenInfo(const Tegra::FramebufferConfig&
     const u64 size_in_bytes{Tegra::Texture::CalculateSize(
         true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)};
     const u8* const host_ptr{device_memory.GetPointer<u8>(framebuffer_addr)};
-    const std::span<const u8> input_data(host_ptr, size_in_bytes);
-    Tegra::Texture::UnswizzleTexture(gl_framebuffer_data, input_data, bytes_per_pixel,
-                                     framebuffer.width, framebuffer.height, 1, block_height_log2,
-                                     0);
+    if (host_ptr) {
+        const std::span<const u8> input_data(host_ptr, size_in_bytes);
+        Tegra::Texture::UnswizzleTexture(gl_framebuffer_data, input_data, bytes_per_pixel,
+                                         framebuffer.width, framebuffer.height, 1,
+                                         block_height_log2, 0);
+    }
 
     glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
     glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride));

+ 6 - 2
src/video_core/renderer_opengl/present/layer.h

@@ -13,6 +13,8 @@ namespace Layout {
 struct FramebufferLayout;
 }
 
+struct PresentFilters;
+
 namespace Service::android {
 enum class PixelFormat : u32;
 };
@@ -44,14 +46,15 @@ struct ScreenRectVertex;
 
 class Layer {
 public:
-    explicit Layer(RasterizerOpenGL& rasterizer, Tegra::MaxwellDeviceMemoryManager& device_memory);
+    explicit Layer(RasterizerOpenGL& rasterizer, Tegra::MaxwellDeviceMemoryManager& device_memory,
+                   const PresentFilters& filters);
     ~Layer();
 
     GLuint ConfigureDraw(std::array<GLfloat, 3 * 2>& out_matrix,
                          std::array<ScreenRectVertex, 4>& out_vertices,
                          ProgramManager& program_manager,
                          const Tegra::FramebufferConfig& framebuffer,
-                         const Layout::FramebufferLayout& layout);
+                         const Layout::FramebufferLayout& layout, bool invert_y);
 
 private:
     /// Loads framebuffer from emulated memory into the active OpenGL texture.
@@ -65,6 +68,7 @@ private:
 private:
     RasterizerOpenGL& rasterizer;
     Tegra::MaxwellDeviceMemoryManager& device_memory;
+    const PresentFilters& filters;
 
     /// OpenGL framebuffer data
     std::vector<u8> gl_framebuffer_data;

+ 17 - 2
src/video_core/renderer_opengl/present/window_adapt_pass.cpp

@@ -37,7 +37,7 @@ WindowAdaptPass::~WindowAdaptPass() = default;
 
 void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::list<Layer>& layers,
                                         std::span<const Tegra::FramebufferConfig> framebuffers,
-                                        const Layout::FramebufferLayout& layout) {
+                                        const Layout::FramebufferLayout& layout, bool invert_y) {
     GLint old_read_fb;
     GLint old_draw_fb;
     glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
@@ -51,7 +51,7 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
     auto layer_it = layers.begin();
     for (size_t i = 0; i < layer_count; i++) {
         textures[i] = layer_it->ConfigureDraw(matrices[i], vertices[i], program_manager,
-                                              framebuffers[i], layout);
+                                              framebuffers[i], layout, invert_y);
         layer_it++;
     }
 
@@ -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());

+ 1 - 1
src/video_core/renderer_opengl/present/window_adapt_pass.h

@@ -31,7 +31,7 @@ public:
 
     void DrawToFramebuffer(ProgramManager& program_manager, std::list<Layer>& layers,
                            std::span<const Tegra::FramebufferConfig> framebuffers,
-                           const Layout::FramebufferLayout& layout);
+                           const Layout::FramebufferLayout& layout, bool invert_y);
 
 private:
     const Device& device;

+ 77 - 12
src/video_core/renderer_opengl/renderer_opengl.cpp

@@ -16,6 +16,8 @@
 #include "core/core_timing.h"
 #include "core/frontend/emu_window.h"
 #include "core/telemetry_session.h"
+#include "video_core/capture.h"
+#include "video_core/present.h"
 #include "video_core/renderer_opengl/gl_blit_screen.h"
 #include "video_core/renderer_opengl/gl_rasterizer.h"
 #include "video_core/renderer_opengl/gl_shader_manager.h"
@@ -120,7 +122,15 @@ RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
         glEnableClientState(GL_ELEMENT_ARRAY_UNIFIED_NV);
     }
     blit_screen = std::make_unique<BlitScreen>(rasterizer, device_memory, state_tracker,
-                                               program_manager, device);
+                                               program_manager, device, PresentFiltersForDisplay);
+    blit_applet =
+        std::make_unique<BlitScreen>(rasterizer, device_memory, state_tracker, program_manager,
+                                     device, PresentFiltersForAppletCapture);
+    capture_framebuffer.Create();
+    capture_renderbuffer.Create();
+    glBindRenderbuffer(GL_RENDERBUFFER, capture_renderbuffer.handle);
+    glRenderbufferStorage(GL_RENDERBUFFER, GL_SRGB8, VideoCore::Capture::LinearWidth,
+                          VideoCore::Capture::LinearHeight);
 }
 
 RendererOpenGL::~RendererOpenGL() = default;
@@ -130,10 +140,11 @@ void RendererOpenGL::Composite(std::span<const Tegra::FramebufferConfig> framebu
         return;
     }
 
+    RenderAppletCaptureLayer(framebuffers);
     RenderScreenshot(framebuffers);
 
     state_tracker.BindFramebuffer(0);
-    blit_screen->DrawScreen(framebuffers, emu_window.GetFramebufferLayout());
+    blit_screen->DrawScreen(framebuffers, emu_window.GetFramebufferLayout(), false);
 
     ++m_current_frame;
 
@@ -159,11 +170,8 @@ void RendererOpenGL::AddTelemetryFields() {
     telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string(gl_version));
 }
 
-void RendererOpenGL::RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers) {
-    if (!renderer_settings.screenshot_requested) {
-        return;
-    }
-
+void RendererOpenGL::RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
+                                    const Layout::FramebufferLayout& layout, void* dst) {
     GLint old_read_fb;
     GLint old_draw_fb;
     glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
@@ -173,29 +181,86 @@ void RendererOpenGL::RenderScreenshot(std::span<const Tegra::FramebufferConfig>
     screenshot_framebuffer.Create();
     glBindFramebuffer(GL_FRAMEBUFFER, screenshot_framebuffer.handle);
 
-    const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
-
     GLuint renderbuffer;
     glGenRenderbuffers(1, &renderbuffer);
     glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
     glRenderbufferStorage(GL_RENDERBUFFER, GL_SRGB8, layout.width, layout.height);
     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
 
-    blit_screen->DrawScreen(framebuffers, layout);
+    blit_screen->DrawScreen(framebuffers, layout, false);
 
     glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
     glPixelStorei(GL_PACK_ROW_LENGTH, 0);
-    glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
-                 renderer_settings.screenshot_bits);
+    glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, dst);
 
     screenshot_framebuffer.Release();
     glDeleteRenderbuffers(1, &renderbuffer);
 
     glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb);
     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb);
+}
+
+void RendererOpenGL::RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers) {
+    if (!renderer_settings.screenshot_requested) {
+        return;
+    }
+
+    RenderToBuffer(framebuffers, renderer_settings.screenshot_framebuffer_layout,
+                   renderer_settings.screenshot_bits);
 
     renderer_settings.screenshot_complete_callback(true);
     renderer_settings.screenshot_requested = false;
 }
 
+void RendererOpenGL::RenderAppletCaptureLayer(
+    std::span<const Tegra::FramebufferConfig> framebuffers) {
+    GLint old_read_fb;
+    GLint old_draw_fb;
+    glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
+    glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_draw_fb);
+
+    glBindFramebuffer(GL_FRAMEBUFFER, capture_framebuffer.handle);
+    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
+                              capture_renderbuffer.handle);
+
+    blit_applet->DrawScreen(framebuffers, VideoCore::Capture::Layout, true);
+
+    glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb);
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb);
+}
+
+std::vector<u8> RendererOpenGL::GetAppletCaptureBuffer() {
+    using namespace VideoCore::Capture;
+
+    std::vector<u8> linear(TiledSize);
+    std::vector<u8> out(TiledSize);
+
+    GLint old_read_fb;
+    GLint old_draw_fb;
+    GLint old_pixel_pack_buffer;
+    GLint old_pack_row_length;
+    glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
+    glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_draw_fb);
+    glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &old_pixel_pack_buffer);
+    glGetIntegerv(GL_PACK_ROW_LENGTH, &old_pack_row_length);
+
+    glBindFramebuffer(GL_FRAMEBUFFER, capture_framebuffer.handle);
+    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
+                              capture_renderbuffer.handle);
+    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+    glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+    glReadPixels(0, 0, LinearWidth, LinearHeight, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
+                 linear.data());
+
+    glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb);
+    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb);
+    glBindBuffer(GL_PIXEL_PACK_BUFFER, old_pixel_pack_buffer);
+    glPixelStorei(GL_PACK_ROW_LENGTH, old_pack_row_length);
+
+    Tegra::Texture::SwizzleTexture(out, linear, BytesPerPixel, LinearWidth, LinearHeight,
+                                   LinearDepth, BlockHeight, BlockDepth);
+
+    return out;
+}
+
 } // namespace OpenGL

+ 9 - 0
src/video_core/renderer_opengl/renderer_opengl.h

@@ -42,6 +42,8 @@ public:
 
     void Composite(std::span<const Tegra::FramebufferConfig> framebuffers) override;
 
+    std::vector<u8> GetAppletCaptureBuffer() override;
+
     VideoCore::RasterizerInterface* ReadRasterizer() override {
         return &rasterizer;
     }
@@ -52,7 +54,11 @@ public:
 
 private:
     void AddTelemetryFields();
+
+    void RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
+                        const Layout::FramebufferLayout& layout, void* dst);
     void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers);
+    void RenderAppletCaptureLayer(std::span<const Tegra::FramebufferConfig> framebuffers);
 
     Core::TelemetrySession& telemetry_session;
     Core::Frontend::EmuWindow& emu_window;
@@ -64,8 +70,11 @@ private:
     ProgramManager program_manager;
     RasterizerOpenGL rasterizer;
     OGLFramebuffer screenshot_framebuffer;
+    OGLFramebuffer capture_framebuffer;
+    OGLRenderbuffer capture_renderbuffer;
 
     std::unique_ptr<BlitScreen> blit_screen;
+    std::unique_ptr<BlitScreen> blit_applet;
 };
 
 } // namespace OpenGL

+ 11 - 8
src/video_core/renderer_vulkan/present/layer.cpp

@@ -1,6 +1,7 @@
 // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include "video_core/present.h"
 #include "video_core/renderer_vulkan/vk_rasterizer.h"
 
 #include "common/settings.h"
@@ -48,12 +49,12 @@ VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
 
 Layer::Layer(const Device& device_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
              Tegra::MaxwellDeviceMemoryManager& device_memory_, size_t image_count_,
-             VkExtent2D output_size, VkDescriptorSetLayout layout)
+             VkExtent2D output_size, VkDescriptorSetLayout layout, const PresentFilters& filters_)
     : device(device_), memory_allocator(memory_allocator_), scheduler(scheduler_),
-      device_memory(device_memory_), image_count(image_count_) {
+      device_memory(device_memory_), filters(filters_), image_count(image_count_) {
     CreateDescriptorPool();
     CreateDescriptorSets(layout);
-    if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) {
+    if (filters.get_scaling_filter() == Settings::ScalingFilter::Fsr) {
         CreateFSR(output_size);
     }
 }
@@ -171,11 +172,11 @@ void Layer::RefreshResources(const Tegra::FramebufferConfig& framebuffer) {
 }
 
 void Layer::SetAntiAliasPass() {
-    if (anti_alias && anti_alias_setting == Settings::values.anti_aliasing.GetValue()) {
+    if (anti_alias && anti_alias_setting == filters.get_anti_aliasing()) {
         return;
     }
 
-    anti_alias_setting = Settings::values.anti_aliasing.GetValue();
+    anti_alias_setting = filters.get_anti_aliasing();
 
     const VkExtent2D render_area{
         .width = Settings::values.resolution_info.ScaleUp(raw_width),
@@ -270,9 +271,11 @@ void Layer::UpdateRawImage(const Tegra::FramebufferConfig& framebuffer, size_t i
     const u64 linear_size{GetSizeInBytes(framebuffer)};
     const u64 tiled_size{Tegra::Texture::CalculateSize(
         true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)};
-    Tegra::Texture::UnswizzleTexture(
-        mapped_span.subspan(image_offset, linear_size), std::span(host_ptr, tiled_size),
-        bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0);
+    if (host_ptr) {
+        Tegra::Texture::UnswizzleTexture(
+            mapped_span.subspan(image_offset, linear_size), std::span(host_ptr, tiled_size),
+            bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0);
+    }
 
     const VkBufferImageCopy copy{
         .bufferOffset = image_offset,

+ 5 - 1
src/video_core/renderer_vulkan/present/layer.h

@@ -11,6 +11,8 @@ namespace Layout {
 struct FramebufferLayout;
 }
 
+struct PresentFilters;
+
 namespace Tegra {
 struct FramebufferConfig;
 }
@@ -37,7 +39,8 @@ class Layer final {
 public:
     explicit Layer(const Device& device, MemoryAllocator& memory_allocator, Scheduler& scheduler,
                    Tegra::MaxwellDeviceMemoryManager& device_memory, size_t image_count,
-                   VkExtent2D output_size, VkDescriptorSetLayout layout);
+                   VkExtent2D output_size, VkDescriptorSetLayout layout,
+                   const PresentFilters& filters);
     ~Layer();
 
     void ConfigureDraw(PresentPushConstants* out_push_constants,
@@ -71,6 +74,7 @@ private:
     MemoryAllocator& memory_allocator;
     Scheduler& scheduler;
     Tegra::MaxwellDeviceMemoryManager& device_memory;
+    const PresentFilters& filters;
     const size_t image_count{};
     vk::DescriptorPool descriptor_pool{};
     vk::DescriptorSets descriptor_sets{};

+ 62 - 30
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) {

+ 7 - 2
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);

+ 23 - 6
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

+ 4 - 2
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

+ 90 - 24
src/video_core/renderer_vulkan/renderer_vulkan.cpp

@@ -19,7 +19,9 @@
 #include "core/core_timing.h"
 #include "core/frontend/graphics_context.h"
 #include "core/telemetry_session.h"
+#include "video_core/capture.h"
 #include "video_core/gpu.h"
+#include "video_core/present.h"
 #include "video_core/renderer_vulkan/present/util.h"
 #include "video_core/renderer_vulkan/renderer_vulkan.h"
 #include "video_core/renderer_vulkan/vk_blit_screen.h"
@@ -38,6 +40,20 @@
 
 namespace Vulkan {
 namespace {
+
+constexpr VkExtent2D CaptureImageSize{
+    .width = VideoCore::Capture::LinearWidth,
+    .height = VideoCore::Capture::LinearHeight,
+};
+
+constexpr VkExtent3D CaptureImageExtent{
+    .width = VideoCore::Capture::LinearWidth,
+    .height = VideoCore::Capture::LinearHeight,
+    .depth = VideoCore::Capture::LinearDepth,
+};
+
+constexpr VkFormat CaptureFormat = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
+
 std::string GetReadableVersion(u32 version) {
     return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version),
                        VK_VERSION_PATCH(version));
@@ -99,10 +115,15 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
                 render_window.GetFramebufferLayout().height),
       present_manager(instance, render_window, device, memory_allocator, scheduler, swapchain,
                       surface),
-      blit_swapchain(device_memory, device, memory_allocator, present_manager, scheduler),
-      blit_screenshot(device_memory, device, memory_allocator, present_manager, scheduler),
+      blit_swapchain(device_memory, device, memory_allocator, present_manager, scheduler,
+                     PresentFiltersForDisplay),
+      blit_capture(device_memory, device, memory_allocator, present_manager, scheduler,
+                   PresentFiltersForDisplay),
+      blit_applet(device_memory, device, memory_allocator, present_manager, scheduler,
+                  PresentFiltersForAppletCapture),
       rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker,
-                 scheduler) {
+                 scheduler),
+      applet_frame() {
     if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
         turbo_mode.emplace(instance, dld);
         scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
@@ -125,6 +146,8 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
 
     SCOPE_EXIT({ render_window.OnFrameDisplayed(); });
 
+    RenderAppletCaptureLayer(framebuffers);
+
     if (!render_window.IsShown()) {
         return;
     }
@@ -167,30 +190,20 @@ void RendererVulkan::Report() const {
     telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions);
 }
 
-void Vulkan::RendererVulkan::RenderScreenshot(
-    std::span<const Tegra::FramebufferConfig> framebuffers) {
-    if (!renderer_settings.screenshot_requested) {
-        return;
-    }
-
-    constexpr VkFormat ScreenshotFormat{VK_FORMAT_B8G8R8A8_UNORM};
-    const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
-
+vk::Buffer RendererVulkan::RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
+                                          const Layout::FramebufferLayout& layout, VkFormat format,
+                                          VkDeviceSize buffer_size) {
     auto frame = [&]() {
         Frame f{};
-        f.image = CreateWrappedImage(memory_allocator, VkExtent2D{layout.width, layout.height},
-                                     ScreenshotFormat);
-        f.image_view = CreateWrappedImageView(device, f.image, ScreenshotFormat);
-        f.framebuffer = blit_screenshot.CreateFramebuffer(layout, *f.image_view, ScreenshotFormat);
+        f.image =
+            CreateWrappedImage(memory_allocator, VkExtent2D{layout.width, layout.height}, format);
+        f.image_view = CreateWrappedImageView(device, f.image, format);
+        f.framebuffer = blit_capture.CreateFramebuffer(layout, *f.image_view, format);
         return f;
     }();
 
-    blit_screenshot.DrawToFrame(rasterizer, &frame, framebuffers, layout, 1,
-                                VK_FORMAT_B8G8R8A8_UNORM);
-
-    const auto dst_buffer = CreateWrappedBuffer(
-        memory_allocator, static_cast<VkDeviceSize>(layout.width * layout.height * 4),
-        MemoryUsage::Download);
+    auto dst_buffer = CreateWrappedBuffer(memory_allocator, buffer_size, MemoryUsage::Download);
+    blit_capture.DrawToFrame(rasterizer, &frame, framebuffers, layout, 1, format);
 
     scheduler.RequestOutsideRenderPassOperationContext();
     scheduler.Record([&](vk::CommandBuffer cmdbuf) {
@@ -198,15 +211,68 @@ void Vulkan::RendererVulkan::RenderScreenshot(
                            VkExtent3D{layout.width, layout.height, 1});
     });
 
-    // Ensure the copy is fully completed before saving the screenshot
+    // Ensure the copy is fully completed before saving the capture
     scheduler.Finish();
 
-    // Copy backing image data to the QImage screenshot buffer
+    // Copy backing image data to the capture buffer
     dst_buffer.Invalidate();
+    return dst_buffer;
+}
+
+void RendererVulkan::RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers) {
+    if (!renderer_settings.screenshot_requested) {
+        return;
+    }
+
+    const auto& layout{renderer_settings.screenshot_framebuffer_layout};
+    const auto dst_buffer = RenderToBuffer(framebuffers, layout, VK_FORMAT_B8G8R8A8_UNORM,
+                                           layout.width * layout.height * 4);
+
     std::memcpy(renderer_settings.screenshot_bits, dst_buffer.Mapped().data(),
                 dst_buffer.Mapped().size());
     renderer_settings.screenshot_complete_callback(false);
     renderer_settings.screenshot_requested = false;
 }
 
+std::vector<u8> RendererVulkan::GetAppletCaptureBuffer() {
+    using namespace VideoCore::Capture;
+
+    std::vector<u8> out(VideoCore::Capture::TiledSize);
+
+    if (!applet_frame.image) {
+        return out;
+    }
+
+    const auto dst_buffer =
+        CreateWrappedBuffer(memory_allocator, VideoCore::Capture::TiledSize, MemoryUsage::Download);
+
+    scheduler.RequestOutsideRenderPassOperationContext();
+    scheduler.Record([&](vk::CommandBuffer cmdbuf) {
+        DownloadColorImage(cmdbuf, *applet_frame.image, *dst_buffer, CaptureImageExtent);
+    });
+
+    // Ensure the copy is fully completed before writing the capture
+    scheduler.Finish();
+
+    // Swizzle image data to the capture buffer
+    dst_buffer.Invalidate();
+    Tegra::Texture::SwizzleTexture(out, dst_buffer.Mapped(), BytesPerPixel, LinearWidth,
+                                   LinearHeight, LinearDepth, BlockHeight, BlockDepth);
+
+    return out;
+}
+
+void RendererVulkan::RenderAppletCaptureLayer(
+    std::span<const Tegra::FramebufferConfig> framebuffers) {
+    if (!applet_frame.image) {
+        applet_frame.image = CreateWrappedImage(memory_allocator, CaptureImageSize, CaptureFormat);
+        applet_frame.image_view = CreateWrappedImageView(device, applet_frame.image, CaptureFormat);
+        applet_frame.framebuffer = blit_applet.CreateFramebuffer(
+            VideoCore::Capture::Layout, *applet_frame.image_view, CaptureFormat);
+    }
+
+    blit_applet.DrawToFrame(rasterizer, &applet_frame, framebuffers, VideoCore::Capture::Layout, 1,
+                            CaptureFormat);
+}
+
 } // namespace Vulkan

+ 10 - 1
src/video_core/renderer_vulkan/renderer_vulkan.h

@@ -48,6 +48,8 @@ public:
 
     void Composite(std::span<const Tegra::FramebufferConfig> framebuffers) override;
 
+    std::vector<u8> GetAppletCaptureBuffer() override;
+
     VideoCore::RasterizerInterface* ReadRasterizer() override {
         return &rasterizer;
     }
@@ -59,7 +61,11 @@ public:
 private:
     void Report() const;
 
+    vk::Buffer RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
+                              const Layout::FramebufferLayout& layout, VkFormat format,
+                              VkDeviceSize buffer_size);
     void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers);
+    void RenderAppletCaptureLayer(std::span<const Tegra::FramebufferConfig> framebuffers);
 
     Core::TelemetrySession& telemetry_session;
     Tegra::MaxwellDeviceMemoryManager& device_memory;
@@ -79,9 +85,12 @@ private:
     Swapchain swapchain;
     PresentManager present_manager;
     BlitScreen blit_swapchain;
-    BlitScreen blit_screenshot;
+    BlitScreen blit_capture;
+    BlitScreen blit_applet;
     RasterizerVulkan rasterizer;
     std::optional<TurboMode> turbo_mode;
+
+    Frame applet_frame;
 };
 
 } // namespace Vulkan

+ 7 - 7
src/video_core/renderer_vulkan/vk_blit_screen.cpp

@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "video_core/framebuffer_config.h"
+#include "video_core/present.h"
 #include "video_core/renderer_vulkan/present/filters.h"
 #include "video_core/renderer_vulkan/present/layer.h"
 #include "video_core/renderer_vulkan/vk_blit_screen.h"
@@ -12,9 +13,9 @@ namespace Vulkan {
 
 BlitScreen::BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory_, const Device& device_,
                        MemoryAllocator& memory_allocator_, PresentManager& present_manager_,
-                       Scheduler& scheduler_)
+                       Scheduler& scheduler_, const PresentFilters& filters_)
     : device_memory{device_memory_}, device{device_}, memory_allocator{memory_allocator_},
-      present_manager{present_manager_}, scheduler{scheduler_}, image_count{1},
+      present_manager{present_manager_}, scheduler{scheduler_}, filters{filters_}, image_count{1},
       swapchain_view_format{VK_FORMAT_B8G8R8A8_UNORM} {}
 
 BlitScreen::~BlitScreen() = default;
@@ -27,7 +28,7 @@ void BlitScreen::WaitIdle() {
 
 void BlitScreen::SetWindowAdaptPass() {
     layers.clear();
-    scaling_filter = Settings::values.scaling_filter.GetValue();
+    scaling_filter = filters.get_scaling_filter();
 
     switch (scaling_filter) {
     case Settings::ScalingFilter::NearestNeighbor:
@@ -59,7 +60,7 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
     bool presentation_recreate_required = false;
 
     // Recreate dynamic resources if the adapting filter changed
-    if (!window_adapt || scaling_filter != Settings::values.scaling_filter.GetValue()) {
+    if (!window_adapt || scaling_filter != filters.get_scaling_filter()) {
         resource_update_required = true;
     }
 
@@ -102,7 +103,7 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
 
     while (layers.size() < framebuffers.size()) {
         layers.emplace_back(device, memory_allocator, scheduler, device_memory, image_count,
-                            window_size, window_adapt->GetDescriptorSetLayout());
+                            window_size, window_adapt->GetDescriptorSetLayout(), filters);
     }
 
     // Perform the draw
@@ -119,8 +120,7 @@ vk::Framebuffer BlitScreen::CreateFramebuffer(const Layout::FramebufferLayout& l
                                               VkFormat current_view_format) {
     const bool format_updated =
         std::exchange(swapchain_view_format, current_view_format) != current_view_format;
-    if (!window_adapt || scaling_filter != Settings::values.scaling_filter.GetValue() ||
-        format_updated) {
+    if (!window_adapt || scaling_filter != filters.get_scaling_filter() || format_updated) {
         WaitIdle();
         SetWindowAdaptPass();
     }

+ 4 - 1
src/video_core/renderer_vulkan/vk_blit_screen.h

@@ -16,6 +16,8 @@ namespace Core {
 class System;
 }
 
+struct PresentFilters;
+
 namespace Tegra {
 struct FramebufferConfig;
 }
@@ -47,7 +49,7 @@ class BlitScreen {
 public:
     explicit BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory, const Device& device,
                         MemoryAllocator& memory_allocator, PresentManager& present_manager,
-                        Scheduler& scheduler);
+                        Scheduler& scheduler, const PresentFilters& filters);
     ~BlitScreen();
 
     void DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
@@ -70,6 +72,7 @@ private:
     MemoryAllocator& memory_allocator;
     PresentManager& present_manager;
     Scheduler& scheduler;
+    const PresentFilters& filters;
     std::size_t image_count{};
     std::size_t image_index{};
     VkFormat swapchain_view_format{};

+ 7 - 1
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());
     };

+ 3 - 0
src/yuzu/CMakeLists.txt

@@ -41,6 +41,9 @@ add_executable(yuzu
     configuration/configuration_shared.cpp
     configuration/configuration_shared.h
     configuration/configure.ui
+    configuration/configure_applets.cpp
+    configuration/configure_applets.h
+    configuration/configure_applets.ui
     configuration/configure_audio.cpp
     configuration/configure_audio.h
     configuration/configure_audio.ui

+ 86 - 0
src/yuzu/configuration/configure_applets.cpp

@@ -0,0 +1,86 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/settings.h"
+#include "core/core.h"
+#include "ui_configure_applets.h"
+#include "yuzu/configuration/configuration_shared.h"
+#include "yuzu/configuration/configure_applets.h"
+#include "yuzu/configuration/shared_widget.h"
+
+ConfigureApplets::ConfigureApplets(Core::System& system_,
+                                   std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+                                   const ConfigurationShared::Builder& builder, QWidget* parent)
+    : Tab(group_, parent), ui{std::make_unique<Ui::ConfigureApplets>()}, system{system_} {
+    ui->setupUi(this);
+
+    Setup(builder);
+
+    SetConfiguration();
+}
+
+ConfigureApplets::~ConfigureApplets() = default;
+
+void ConfigureApplets::changeEvent(QEvent* event) {
+    if (event->type() == QEvent::LanguageChange) {
+        RetranslateUI();
+    }
+
+    QWidget::changeEvent(event);
+}
+
+void ConfigureApplets::RetranslateUI() {
+    ui->retranslateUi(this);
+}
+
+void ConfigureApplets::Setup(const ConfigurationShared::Builder& builder) {
+    auto& library_applets_layout = *ui->group_library_applet_modes->layout();
+    std::map<u32, QWidget*> applets_hold{};
+
+    std::vector<Settings::BasicSetting*> settings;
+    auto push = [&settings](auto& list) {
+        for (auto setting : list) {
+            settings.push_back(setting);
+        }
+    };
+
+    push(Settings::values.linkage.by_category[Settings::Category::LibraryApplet]);
+
+    for (auto setting : settings) {
+        ConfigurationShared::Widget* widget = builder.BuildWidget(setting, apply_funcs);
+
+        if (widget == nullptr) {
+            continue;
+        }
+        if (!widget->Valid()) {
+            widget->deleteLater();
+            continue;
+        }
+
+        // Untested applets
+        if (setting->Id() == Settings::values.data_erase_applet_mode.Id() ||
+            setting->Id() == Settings::values.error_applet_mode.Id() ||
+            setting->Id() == Settings::values.net_connect_applet_mode.Id() ||
+            setting->Id() == Settings::values.web_applet_mode.Id() ||
+            setting->Id() == Settings::values.shop_applet_mode.Id() ||
+            setting->Id() == Settings::values.login_share_applet_mode.Id() ||
+            setting->Id() == Settings::values.wifi_web_auth_applet_mode.Id() ||
+            setting->Id() == Settings::values.my_page_applet_mode.Id()) {
+            widget->setHidden(true);
+        }
+
+        applets_hold.emplace(setting->Id(), widget);
+    }
+    for (const auto& [label, widget] : applets_hold) {
+        library_applets_layout.addWidget(widget);
+    }
+}
+
+void ConfigureApplets::SetConfiguration() {}
+
+void ConfigureApplets::ApplyConfiguration() {
+    const bool powered_on = system.IsPoweredOn();
+    for (const auto& func : apply_funcs) {
+        func(powered_on);
+    }
+}

+ 48 - 0
src/yuzu/configuration/configure_applets.h

@@ -0,0 +1,48 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <QWidget>
+#include "yuzu/configuration/configuration_shared.h"
+
+class QCheckBox;
+class QLineEdit;
+class QComboBox;
+class QDateTimeEdit;
+namespace Core {
+class System;
+}
+
+namespace Ui {
+class ConfigureApplets;
+}
+
+namespace ConfigurationShared {
+class Builder;
+}
+
+class ConfigureApplets : public ConfigurationShared::Tab {
+public:
+    explicit ConfigureApplets(Core::System& system_,
+                              std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group,
+                              const ConfigurationShared::Builder& builder,
+                              QWidget* parent = nullptr);
+    ~ConfigureApplets() override;
+
+    void ApplyConfiguration() override;
+    void SetConfiguration() override;
+
+private:
+    void changeEvent(QEvent* event) override;
+    void RetranslateUI();
+
+    void Setup(const ConfigurationShared::Builder& builder);
+
+    std::vector<std::function<void(bool)>> apply_funcs{};
+
+    std::unique_ptr<Ui::ConfigureApplets> ui;
+    bool enabled = false;
+
+    Core::System& system;
+};

+ 65 - 0
src/yuzu/configuration/configure_applets.ui

@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureApplets</class>
+ <widget class="QWidget" name="ConfigureApplets">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>605</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <property name="accessibleName">
+   <string>Applets</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_1">
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <widget class="QGroupBox" name="group_library_applet_modes">
+       <property name="title">
+        <string>Applet mode preference</string>
+       </property>
+       <layout class="QVBoxLayout">
+        <item>
+         <widget class="QWidget" name="applets_widget" native="true">
+          <layout class="QVBoxLayout" name="verticalLayout_3">
+           <property name="leftMargin">
+            <number>0</number>
+           </property>
+           <property name="topMargin">
+            <number>0</number>
+           </property>
+           <property name="rightMargin">
+            <number>0</number>
+           </property>
+          </layout>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 6 - 1
src/yuzu/configuration/configure_dialog.cpp

@@ -8,6 +8,7 @@
 #include "core/core.h"
 #include "ui_configure.h"
 #include "vk_device_info.h"
+#include "yuzu/configuration/configure_applets.h"
 #include "yuzu/configuration/configure_audio.h"
 #include "yuzu/configuration/configure_cpu.h"
 #include "yuzu/configuration/configure_debug_tab.h"
@@ -34,6 +35,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
     : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()},
       registry(registry_), system{system_}, builder{std::make_unique<ConfigurationShared::Builder>(
                                                 this, !system_.IsPoweredOn())},
+      applets_tab{std::make_unique<ConfigureApplets>(system_, nullptr, *builder, this)},
       audio_tab{std::make_unique<ConfigureAudio>(system_, nullptr, *builder, this)},
       cpu_tab{std::make_unique<ConfigureCpu>(system_, nullptr, *builder, this)},
       debug_tab_tab{std::make_unique<ConfigureDebugTab>(system_, this)},
@@ -58,6 +60,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
 
     ui->setupUi(this);
 
+    ui->tabWidget->addTab(applets_tab.get(), tr("Applets"));
     ui->tabWidget->addTab(audio_tab.get(), tr("Audio"));
     ui->tabWidget->addTab(cpu_tab.get(), tr("CPU"));
     ui->tabWidget->addTab(debug_tab_tab.get(), tr("Debug"));
@@ -124,6 +127,7 @@ void ConfigureDialog::ApplyConfiguration() {
     debug_tab_tab->ApplyConfiguration();
     web_tab->ApplyConfiguration();
     network_tab->ApplyConfiguration();
+    applets_tab->ApplyConfiguration();
     system.ApplySettings();
     Settings::LogSettings();
 }
@@ -161,7 +165,8 @@ void ConfigureDialog::PopulateSelectionList() {
         {{tr("General"),
           {general_tab.get(), hotkeys_tab.get(), ui_tab.get(), web_tab.get(), debug_tab_tab.get()}},
          {tr("System"),
-          {system_tab.get(), profile_tab.get(), network_tab.get(), filesystem_tab.get()}},
+          {system_tab.get(), profile_tab.get(), network_tab.get(), filesystem_tab.get(),
+           applets_tab.get()}},
          {tr("CPU"), {cpu_tab.get()}},
          {tr("Graphics"), {graphics_tab.get(), graphics_advanced_tab.get()}},
          {tr("Audio"), {audio_tab.get()}},

+ 2 - 0
src/yuzu/configuration/configure_dialog.h

@@ -15,6 +15,7 @@ namespace Core {
 class System;
 }
 
+class ConfigureApplets;
 class ConfigureAudio;
 class ConfigureCpu;
 class ConfigureDebugTab;
@@ -75,6 +76,7 @@ private:
     std::unique_ptr<ConfigurationShared::Builder> builder;
     std::vector<ConfigurationShared::Tab*> tab_group;
 
+    std::unique_ptr<ConfigureApplets> applets_tab;
     std::unique_ptr<ConfigureAudio> audio_tab;
     std::unique_ptr<ConfigureCpu> cpu_tab;
     std::unique_ptr<ConfigureDebugTab> debug_tab_tab;

+ 22 - 0
src/yuzu/configuration/shared_translation.cpp

@@ -26,6 +26,23 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
 
     // A setting can be ignored by giving it a blank name
 
+    // Applets
+    INSERT(Settings, cabinet_applet_mode, tr("Amiibo editor"), QStringLiteral());
+    INSERT(Settings, controller_applet_mode, tr("Controller configuration"), QStringLiteral());
+    INSERT(Settings, data_erase_applet_mode, tr("Data erase"), QStringLiteral());
+    INSERT(Settings, error_applet_mode, tr("Error"), QStringLiteral());
+    INSERT(Settings, net_connect_applet_mode, tr("Net connect"), QStringLiteral());
+    INSERT(Settings, player_select_applet_mode, tr("Player select"), QStringLiteral());
+    INSERT(Settings, swkbd_applet_mode, tr("Software keyboard"), QStringLiteral());
+    INSERT(Settings, mii_edit_applet_mode, tr("Mii Edit"), QStringLiteral());
+    INSERT(Settings, web_applet_mode, tr("Online web"), QStringLiteral());
+    INSERT(Settings, shop_applet_mode, tr("Shop"), QStringLiteral());
+    INSERT(Settings, photo_viewer_applet_mode, tr("Photo viewer"), QStringLiteral());
+    INSERT(Settings, offline_web_applet_mode, tr("Offline web"), QStringLiteral());
+    INSERT(Settings, login_share_applet_mode, tr("Login share"), QStringLiteral());
+    INSERT(Settings, wifi_web_auth_applet_mode, tr("Wifi web auth"), QStringLiteral());
+    INSERT(Settings, my_page_applet_mode, tr("My page"), QStringLiteral());
+
     // Audio
     INSERT(Settings, sink_id, tr("Output Engine:"), QStringLiteral());
     INSERT(Settings, audio_output_device_id, tr("Output Device:"), QStringLiteral());
@@ -203,6 +220,11 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
 #define PAIR(ENUM, VALUE, TRANSLATION) {static_cast<u32>(Settings::ENUM::VALUE), (TRANSLATION)}
 
     // Intentionally skipping VSyncMode to let the UI fill that one out
+    translations->insert({Settings::EnumMetadata<Settings::AppletMode>::Index(),
+                          {
+                              PAIR(AppletMode, HLE, tr("Custom frontend")),
+                              PAIR(AppletMode, LLE, tr("Real applet")),
+                          }});
 
     translations->insert({Settings::EnumMetadata<Settings::AstcDecodeMode>::Index(),
                           {