diff options
34 files changed, 847 insertions, 258 deletions
| diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 312a49f42..5e3a74c0f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -113,6 +113,9 @@ else()          $<$<CXX_COMPILER_ID:Clang>:-Wno-braced-scalar-init>          $<$<CXX_COMPILER_ID:Clang>:-Wno-unused-private-field> +        $<$<CXX_COMPILER_ID:Clang>:-Werror=shadow-uncaptured-local> +        $<$<CXX_COMPILER_ID:Clang>:-Werror=implicit-fallthrough> +        $<$<CXX_COMPILER_ID:Clang>:-Werror=type-limits>          $<$<CXX_COMPILER_ID:AppleClang>:-Wno-braced-scalar-init>          $<$<CXX_COMPILER_ID:AppleClang>:-Wno-unused-private-field>      ) diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 84955030b..cb1bca467 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -45,6 +45,7 @@ void LogSettings() {      log_setting("System_LanguageIndex", values.language_index.GetValue());      log_setting("System_RegionIndex", values.region_index.GetValue());      log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue()); +    log_setting("System_UnsafeMemoryLayout", values.use_unsafe_extended_memory_layout.GetValue());      log_setting("Core_UseMultiCore", values.use_multi_core.GetValue());      log_setting("CPU_Accuracy", values.cpu_accuracy.GetValue());      log_setting("Renderer_UseResolutionScaling", values.resolution_setup.GetValue()); @@ -191,7 +192,7 @@ void RestoreGlobalState(bool is_powered_on) {      // Core      values.use_multi_core.SetGlobal(true); -    values.use_extended_memory_layout.SetGlobal(true); +    values.use_unsafe_extended_memory_layout.SetGlobal(true);      // CPU      values.cpu_accuracy.SetGlobal(true); @@ -205,6 +206,7 @@ void RestoreGlobalState(bool is_powered_on) {      // Renderer      values.fsr_sharpening_slider.SetGlobal(true);      values.renderer_backend.SetGlobal(true); +    values.async_presentation.SetGlobal(true);      values.renderer_force_max_clock.SetGlobal(true);      values.vulkan_device.SetGlobal(true);      values.fullscreen_mode.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index b77a1580a..adebb0ca7 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -388,7 +388,8 @@ struct Values {      // Core      SwitchableSetting<bool> use_multi_core{true, "use_multi_core"}; -    SwitchableSetting<bool> use_extended_memory_layout{false, "use_extended_memory_layout"}; +    SwitchableSetting<bool> use_unsafe_extended_memory_layout{false, +                                                              "use_unsafe_extended_memory_layout"};      // Cpu      SwitchableSetting<CPUAccuracy, true> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto, @@ -422,6 +423,7 @@ struct Values {      // Renderer      SwitchableSetting<RendererBackend, true> renderer_backend{          RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Null, "backend"}; +    SwitchableSetting<bool> async_presentation{false, "async_presentation"};      SwitchableSetting<bool> renderer_force_max_clock{false, "force_max_clock"};      Setting<bool> renderer_debug{false, "debug"};      Setting<bool> renderer_shader_feedback{false, "shader_feedback"}; diff --git a/src/core/core.cpp b/src/core/core.cpp index caa6a77be..ac0fb7872 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -137,7 +137,7 @@ struct System::Impl {          device_memory = std::make_unique<Core::DeviceMemory>();          is_multicore = Settings::values.use_multi_core.GetValue(); -        extended_memory_layout = Settings::values.use_extended_memory_layout.GetValue(); +        extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue();          core_timing.SetMulticore(is_multicore);          core_timing.Initialize([&system]() { system.RegisterHostThread(); }); @@ -169,7 +169,7 @@ struct System::Impl {      void ReinitializeIfNecessary(System& system) {          const bool must_reinitialize =              is_multicore != Settings::values.use_multi_core.GetValue() || -            extended_memory_layout != Settings::values.use_extended_memory_layout.GetValue(); +            extended_memory_layout != Settings::values.use_unsafe_extended_memory_layout.GetValue();          if (!must_reinitialize) {              return; @@ -178,7 +178,7 @@ struct System::Impl {          LOG_DEBUG(Kernel, "Re-initializing");          is_multicore = Settings::values.use_multi_core.GetValue(); -        extended_memory_layout = Settings::values.use_extended_memory_layout.GetValue(); +        extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue();          Initialize(system);      } diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp index 36d0d20d2..49bdc671e 100644 --- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp +++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp @@ -35,12 +35,13 @@ namespace {  using namespace Common::Literals;  u32 GetMemorySizeForInit() { -    return Settings::values.use_extended_memory_layout ? Smc::MemorySize_8GB : Smc::MemorySize_4GB; +    return Settings::values.use_unsafe_extended_memory_layout ? Smc::MemorySize_8GB +                                                              : Smc::MemorySize_4GB;  }  Smc::MemoryArrangement GetMemoryArrangeForInit() { -    return Settings::values.use_extended_memory_layout ? Smc::MemoryArrangement_8GB -                                                       : Smc::MemoryArrangement_4GB; +    return Settings::values.use_unsafe_extended_memory_layout ? Smc::MemoryArrangement_8GB +                                                              : Smc::MemoryArrangement_4GB;  }  } // namespace diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index 0cd87a48f..fee510f7b 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -473,7 +473,8 @@ void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value) {  }  void EmitSetSampleMask(EmitContext& ctx, Id value) { -    ctx.OpStore(ctx.sample_mask, value); +    const Id pointer{ctx.OpAccessChain(ctx.output_u32, ctx.sample_mask, ctx.u32_zero_value)}; +    ctx.OpStore(pointer, value);  }  void EmitSetFragDepth(EmitContext& ctx, Id value) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index d48d4860e..47739794f 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1572,7 +1572,8 @@ void EmitContext::DefineOutputs(const IR::Program& program) {              Decorate(frag_depth, spv::Decoration::BuiltIn, spv::BuiltIn::FragDepth);          }          if (info.stores_sample_mask) { -            sample_mask = DefineOutput(*this, U32[1], std::nullopt); +            const Id array_type{TypeArray(U32[1], Const(1U))}; +            sample_mask = DefineOutput(*this, array_type, std::nullopt);              Decorate(sample_mask, spv::Decoration::BuiltIn, spv::BuiltIn::SampleMask);          }          break; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 92cab93f3..a0009a36f 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -179,6 +179,8 @@ add_library(video_core STATIC      renderer_vulkan/vk_master_semaphore.h      renderer_vulkan/vk_pipeline_cache.cpp      renderer_vulkan/vk_pipeline_cache.h +    renderer_vulkan/vk_present_manager.cpp +    renderer_vulkan/vk_present_manager.h      renderer_vulkan/vk_query_cache.cpp      renderer_vulkan/vk_query_cache.h      renderer_vulkan/vk_rasterizer.cpp diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 2a8d9e377..69dc76180 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -93,8 +93,9 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,        state_tracker(), scheduler(device, state_tracker),        swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width,                  render_window.GetFramebufferLayout().height, false), -      blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, scheduler, -                  screen_info), +      present_manager(render_window, device, memory_allocator, scheduler, swapchain), +      blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, present_manager, +                  scheduler, screen_info),        rasterizer(render_window, gpu, cpu_memory, screen_info, device, memory_allocator,                   state_tracker, scheduler) {      if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) { @@ -121,46 +122,19 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {          return;      }      // Update screen info if the framebuffer size has changed. -    if (screen_info.width != framebuffer->width || screen_info.height != framebuffer->height) { -        screen_info.width = framebuffer->width; -        screen_info.height = framebuffer->height; -    } +    screen_info.width = framebuffer->width; +    screen_info.height = framebuffer->height; +      const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset;      const bool use_accelerated =          rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);      const bool is_srgb = use_accelerated && screen_info.is_srgb;      RenderScreenshot(*framebuffer, use_accelerated); -    bool has_been_recreated = false; -    const auto recreate_swapchain = [&](u32 width, u32 height) { -        if (!has_been_recreated) { -            has_been_recreated = true; -            scheduler.Finish(); -        } -        swapchain.Create(width, height, is_srgb); -    }; - -    const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout(); -    if (swapchain.NeedsRecreation(is_srgb) || swapchain.GetWidth() != layout.width || -        swapchain.GetHeight() != layout.height) { -        recreate_swapchain(layout.width, layout.height); -    } -    bool is_outdated; -    do { -        swapchain.AcquireNextImage(); -        is_outdated = swapchain.IsOutDated(); -        if (is_outdated) { -            recreate_swapchain(layout.width, layout.height); -        } -    } while (is_outdated); -    if (has_been_recreated) { -        blit_screen.Recreate(); -    } -    const VkSemaphore render_semaphore = blit_screen.DrawToSwapchain(*framebuffer, use_accelerated); -    const VkSemaphore present_semaphore = swapchain.CurrentPresentSemaphore(); -    scheduler.Flush(render_semaphore, present_semaphore); -    scheduler.WaitWorker(); -    swapchain.Present(render_semaphore); +    Frame* frame = present_manager.GetRenderFrame(); +    blit_screen.DrawToSwapchain(frame, *framebuffer, use_accelerated, is_srgb); +    scheduler.Flush(*frame->render_ready); +    scheduler.Record([this, frame](vk::CommandBuffer) { present_manager.PushFrame(frame); });      gpu.RendererFrameEndNotify();      rasterizer.TickFrame(); @@ -246,8 +220,7 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr      });      const VkExtent2D render_area{.width = layout.width, .height = layout.height};      const vk::Framebuffer screenshot_fb = blit_screen.CreateFramebuffer(*dst_view, render_area); -    // Since we're not rendering to the screen, ignore the render semaphore. -    void(blit_screen.Draw(framebuffer, *screenshot_fb, layout, render_area, use_accelerated)); +    blit_screen.Draw(framebuffer, *screenshot_fb, layout, render_area, use_accelerated);      const auto buffer_size = static_cast<VkDeviceSize>(layout.width * layout.height * 4);      const VkBufferCreateInfo dst_buffer_info{ @@ -270,7 +243,7 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr              .pNext = nullptr,              .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,              .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, -            .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, +            .oldLayout = VK_IMAGE_LAYOUT_GENERAL,              .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,              .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,              .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 009e75e0d..f44367cb2 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -9,6 +9,7 @@  #include "common/dynamic_library.h"  #include "video_core/renderer_base.h"  #include "video_core/renderer_vulkan/vk_blit_screen.h" +#include "video_core/renderer_vulkan/vk_present_manager.h"  #include "video_core/renderer_vulkan/vk_rasterizer.h"  #include "video_core/renderer_vulkan/vk_scheduler.h"  #include "video_core/renderer_vulkan/vk_state_tracker.h" @@ -76,6 +77,7 @@ private:      StateTracker state_tracker;      Scheduler scheduler;      Swapchain swapchain; +    PresentManager present_manager;      BlitScreen blit_screen;      RasterizerVulkan rasterizer;      std::optional<TurboMode> turbo_mode; diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 2f0cc27e8..1e0fdd3d9 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -122,10 +122,12 @@ struct BlitScreen::BufferData {  BlitScreen::BlitScreen(Core::Memory::Memory& cpu_memory_, Core::Frontend::EmuWindow& render_window_,                         const Device& device_, MemoryAllocator& memory_allocator_, -                       Swapchain& swapchain_, Scheduler& scheduler_, const ScreenInfo& screen_info_) +                       Swapchain& swapchain_, PresentManager& present_manager_, +                       Scheduler& scheduler_, const ScreenInfo& screen_info_)      : cpu_memory{cpu_memory_}, render_window{render_window_}, device{device_}, -      memory_allocator{memory_allocator_}, swapchain{swapchain_}, scheduler{scheduler_}, -      image_count{swapchain.GetImageCount()}, screen_info{screen_info_} { +      memory_allocator{memory_allocator_}, swapchain{swapchain_}, present_manager{present_manager_}, +      scheduler{scheduler_}, image_count{swapchain.GetImageCount()}, screen_info{screen_info_}, +      current_srgb{swapchain.IsSrgb()}, image_view_format{swapchain.GetImageViewFormat()} {      resource_ticks.resize(image_count);      CreateStaticResources(); @@ -135,25 +137,20 @@ BlitScreen::BlitScreen(Core::Memory::Memory& cpu_memory_, Core::Frontend::EmuWin  BlitScreen::~BlitScreen() = default;  void BlitScreen::Recreate() { +    present_manager.WaitPresent(); +    scheduler.Finish(); +    device.GetLogical().WaitIdle();      CreateDynamicResources();  } -VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, -                             const VkFramebuffer& host_framebuffer, -                             const Layout::FramebufferLayout layout, VkExtent2D render_area, -                             bool use_accelerated) { +void BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, +                      const VkFramebuffer& host_framebuffer, const Layout::FramebufferLayout layout, +                      VkExtent2D render_area, bool use_accelerated) {      RefreshResources(framebuffer);      // Finish any pending renderpass      scheduler.RequestOutsideRenderPassOperationContext(); -    if (const auto swapchain_images = swapchain.GetImageCount(); swapchain_images != image_count) { -        image_count = swapchain_images; -        Recreate(); -    } - -    const std::size_t image_index = swapchain.GetImageIndex(); -      scheduler.Wait(resource_ticks[image_index]);      resource_ticks[image_index] = scheduler.CurrentTick(); @@ -169,7 +166,7 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,      std::memcpy(mapped_span.data(), &data, sizeof(data));      if (!use_accelerated) { -        const u64 image_offset = GetRawImageOffset(framebuffer, image_index); +        const u64 image_offset = GetRawImageOffset(framebuffer);          const VAddr framebuffer_addr = framebuffer.address + framebuffer.offset;          const u8* const host_ptr = cpu_memory.GetPointer(framebuffer_addr); @@ -204,8 +201,8 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,                      .depth = 1,                  },          }; -        scheduler.Record([this, copy, image_index](vk::CommandBuffer cmdbuf) { -            const VkImage image = *raw_images[image_index]; +        scheduler.Record([this, copy, index = image_index](vk::CommandBuffer cmdbuf) { +            const VkImage image = *raw_images[index];              const VkImageMemoryBarrier base_barrier{                  .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,                  .pNext = nullptr, @@ -245,14 +242,15 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,      const auto anti_alias_pass = Settings::values.anti_aliasing.GetValue();      if (use_accelerated && anti_alias_pass == Settings::AntiAliasing::Fxaa) { -        UpdateAADescriptorSet(image_index, source_image_view, false); +        UpdateAADescriptorSet(source_image_view, false);          const u32 up_scale = Settings::values.resolution_info.up_scale;          const u32 down_shift = Settings::values.resolution_info.down_shift;          VkExtent2D size{              .width = (up_scale * framebuffer.width) >> down_shift,              .height = (up_scale * framebuffer.height) >> down_shift,          }; -        scheduler.Record([this, image_index, size, anti_alias_pass](vk::CommandBuffer cmdbuf) { +        scheduler.Record([this, index = image_index, size, +                          anti_alias_pass](vk::CommandBuffer cmdbuf) {              const VkImageMemoryBarrier base_barrier{                  .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,                  .pNext = nullptr, @@ -326,7 +324,7 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,              cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices));              cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *aa_pipeline_layout, 0, -                                      aa_descriptor_sets[image_index], {}); +                                      aa_descriptor_sets[index], {});              cmdbuf.Draw(4, 1, 0, 0);              cmdbuf.EndRenderPass(); @@ -369,81 +367,99 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,          };          VkImageView fsr_image_view =              fsr->Draw(scheduler, image_index, source_image_view, fsr_input_size, crop_rect); -        UpdateDescriptorSet(image_index, fsr_image_view, true); +        UpdateDescriptorSet(fsr_image_view, true);      } else {          const bool is_nn =              Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::NearestNeighbor; -        UpdateDescriptorSet(image_index, source_image_view, is_nn); +        UpdateDescriptorSet(source_image_view, is_nn);      } -    scheduler.Record( -        [this, host_framebuffer, image_index, size = render_area](vk::CommandBuffer cmdbuf) { -            const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f; -            const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f; -            const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f; -            const VkClearValue clear_color{ -                .color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}}, -            }; -            const VkRenderPassBeginInfo renderpass_bi{ -                .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, -                .pNext = nullptr, -                .renderPass = *renderpass, -                .framebuffer = host_framebuffer, -                .renderArea = -                    { -                        .offset = {0, 0}, -                        .extent = size, -                    }, -                .clearValueCount = 1, -                .pClearValues = &clear_color, -            }; -            const VkViewport viewport{ -                .x = 0.0f, -                .y = 0.0f, -                .width = static_cast<float>(size.width), -                .height = static_cast<float>(size.height), -                .minDepth = 0.0f, -                .maxDepth = 1.0f, -            }; -            const VkRect2D scissor{ -                .offset = {0, 0}, -                .extent = size, -            }; -            cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); -            auto graphics_pipeline = [this]() { -                switch (Settings::values.scaling_filter.GetValue()) { -                case Settings::ScalingFilter::NearestNeighbor: -                case Settings::ScalingFilter::Bilinear: -                    return *bilinear_pipeline; -                case Settings::ScalingFilter::Bicubic: -                    return *bicubic_pipeline; -                case Settings::ScalingFilter::Gaussian: -                    return *gaussian_pipeline; -                case Settings::ScalingFilter::ScaleForce: -                    return *scaleforce_pipeline; -                default: -                    return *bilinear_pipeline; -                } -            }(); -            cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline); -            cmdbuf.SetViewport(0, viewport); -            cmdbuf.SetScissor(0, scissor); - -            cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices)); -            cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0, -                                      descriptor_sets[image_index], {}); -            cmdbuf.Draw(4, 1, 0, 0); -            cmdbuf.EndRenderPass(); -        }); -    return *semaphores[image_index]; +    scheduler.Record([this, host_framebuffer, index = image_index, +                      size = render_area](vk::CommandBuffer cmdbuf) { +        const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f; +        const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f; +        const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f; +        const VkClearValue clear_color{ +            .color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}}, +        }; +        const VkRenderPassBeginInfo renderpass_bi{ +            .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, +            .pNext = nullptr, +            .renderPass = *renderpass, +            .framebuffer = host_framebuffer, +            .renderArea = +                { +                    .offset = {0, 0}, +                    .extent = size, +                }, +            .clearValueCount = 1, +            .pClearValues = &clear_color, +        }; +        const VkViewport viewport{ +            .x = 0.0f, +            .y = 0.0f, +            .width = static_cast<float>(size.width), +            .height = static_cast<float>(size.height), +            .minDepth = 0.0f, +            .maxDepth = 1.0f, +        }; +        const VkRect2D scissor{ +            .offset = {0, 0}, +            .extent = size, +        }; +        cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); +        auto graphics_pipeline = [this]() { +            switch (Settings::values.scaling_filter.GetValue()) { +            case Settings::ScalingFilter::NearestNeighbor: +            case Settings::ScalingFilter::Bilinear: +                return *bilinear_pipeline; +            case Settings::ScalingFilter::Bicubic: +                return *bicubic_pipeline; +            case Settings::ScalingFilter::Gaussian: +                return *gaussian_pipeline; +            case Settings::ScalingFilter::ScaleForce: +                return *scaleforce_pipeline; +            default: +                return *bilinear_pipeline; +            } +        }(); +        cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline); +        cmdbuf.SetViewport(0, viewport); +        cmdbuf.SetScissor(0, scissor); + +        cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices)); +        cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0, +                                  descriptor_sets[index], {}); +        cmdbuf.Draw(4, 1, 0, 0); +        cmdbuf.EndRenderPass(); +    });  } -VkSemaphore BlitScreen::DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer, -                                        bool use_accelerated) { -    const std::size_t image_index = swapchain.GetImageIndex(); -    const VkExtent2D render_area = swapchain.GetSize(); +void BlitScreen::DrawToSwapchain(Frame* frame, const Tegra::FramebufferConfig& framebuffer, +                                 bool use_accelerated, bool is_srgb) { +    // Recreate dynamic resources if the the image count or colorspace changed +    if (const std::size_t swapchain_images = swapchain.GetImageCount(); +        swapchain_images != image_count || current_srgb != is_srgb) { +        current_srgb = is_srgb; +        image_view_format = current_srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM; +        image_count = swapchain_images; +        Recreate(); +    } + +    // Recreate the presentation frame if the dimensions of the window changed      const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout(); -    return Draw(framebuffer, *framebuffers[image_index], layout, render_area, use_accelerated); +    if (layout.width != frame->width || layout.height != frame->height || +        is_srgb != frame->is_srgb) { +        Recreate(); +        present_manager.RecreateFrame(frame, layout.width, layout.height, is_srgb, +                                      image_view_format, *renderpass); +    } + +    const VkExtent2D render_area{frame->width, frame->height}; +    Draw(framebuffer, *frame->framebuffer, layout, render_area, use_accelerated); +    if (++image_index >= image_count) { +        image_index = 0; +    }  }  vk::Framebuffer BlitScreen::CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent) { @@ -471,13 +487,11 @@ void BlitScreen::CreateStaticResources() {  }  void BlitScreen::CreateDynamicResources() { -    CreateSemaphores();      CreateDescriptorPool();      CreateDescriptorSetLayout();      CreateDescriptorSets();      CreatePipelineLayout();      CreateRenderPass(); -    CreateFramebuffers();      CreateGraphicsPipeline();      fsr.reset();      smaa.reset(); @@ -525,11 +539,6 @@ void BlitScreen::CreateShaders() {      }  } -void BlitScreen::CreateSemaphores() { -    semaphores.resize(image_count); -    std::ranges::generate(semaphores, [this] { return device.GetLogical().CreateSemaphore(); }); -} -  void BlitScreen::CreateDescriptorPool() {      const std::array<VkDescriptorPoolSize, 2> pool_sizes{{          { @@ -571,10 +580,10 @@ void BlitScreen::CreateDescriptorPool() {  }  void BlitScreen::CreateRenderPass() { -    renderpass = CreateRenderPassImpl(swapchain.GetImageViewFormat()); +    renderpass = CreateRenderPassImpl(image_view_format);  } -vk::RenderPass BlitScreen::CreateRenderPassImpl(VkFormat format, bool is_present) { +vk::RenderPass BlitScreen::CreateRenderPassImpl(VkFormat format) {      const VkAttachmentDescription color_attachment{          .flags = 0,          .format = format, @@ -584,7 +593,7 @@ vk::RenderPass BlitScreen::CreateRenderPassImpl(VkFormat format, bool is_present          .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,          .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,          .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, -        .finalLayout = is_present ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_GENERAL, +        .finalLayout = VK_IMAGE_LAYOUT_GENERAL,      };      const VkAttachmentReference color_attachment_ref{ @@ -1052,16 +1061,6 @@ void BlitScreen::CreateSampler() {      nn_sampler = device.GetLogical().CreateSampler(ci_nn);  } -void BlitScreen::CreateFramebuffers() { -    const VkExtent2D size{swapchain.GetSize()}; -    framebuffers.resize(image_count); - -    for (std::size_t i = 0; i < image_count; ++i) { -        const VkImageView image_view{swapchain.GetImageViewIndex(i)}; -        framebuffers[i] = CreateFramebuffer(image_view, size, renderpass); -    } -} -  void BlitScreen::ReleaseRawImages() {      for (const u64 tick : resource_ticks) {          scheduler.Wait(tick); @@ -1175,7 +1174,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {          aa_framebuffer = CreateFramebuffer(*aa_image_view, size, aa_renderpass);          return;      } -    aa_renderpass = CreateRenderPassImpl(GetFormat(framebuffer), false); +    aa_renderpass = CreateRenderPassImpl(GetFormat(framebuffer));      aa_framebuffer = CreateFramebuffer(*aa_image_view, size, aa_renderpass);      const std::array<VkPipelineShaderStageCreateInfo, 2> fxaa_shader_stages{{ @@ -1319,8 +1318,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {      aa_pipeline = device.GetLogical().CreateGraphicsPipeline(fxaa_pipeline_ci);  } -void BlitScreen::UpdateAADescriptorSet(std::size_t image_index, VkImageView image_view, -                                       bool nn) const { +void BlitScreen::UpdateAADescriptorSet(VkImageView image_view, bool nn) const {      const VkDescriptorImageInfo image_info{          .sampler = nn ? *nn_sampler : *sampler,          .imageView = image_view, @@ -1356,8 +1354,7 @@ void BlitScreen::UpdateAADescriptorSet(std::size_t image_index, VkImageView imag      device.GetLogical().UpdateDescriptorSets(std::array{sampler_write, sampler_write_2}, {});  } -void BlitScreen::UpdateDescriptorSet(std::size_t image_index, VkImageView image_view, -                                     bool nn) const { +void BlitScreen::UpdateDescriptorSet(VkImageView image_view, bool nn) const {      const VkDescriptorBufferInfo buffer_info{          .buffer = *buffer,          .offset = offsetof(BufferData, uniform), @@ -1480,8 +1477,7 @@ u64 BlitScreen::CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer)      return sizeof(BufferData) + GetSizeInBytes(framebuffer) * image_count;  } -u64 BlitScreen::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, -                                  std::size_t image_index) const { +u64 BlitScreen::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer) const {      constexpr auto first_image_offset = static_cast<u64>(sizeof(BufferData));      return first_image_offset + GetSizeInBytes(framebuffer) * image_index;  } diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h index ebe10b08b..68ec20253 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.h +++ b/src/video_core/renderer_vulkan/vk_blit_screen.h @@ -5,6 +5,7 @@  #include <memory> +#include "core/frontend/framebuffer_layout.h"  #include "video_core/vulkan_common/vulkan_memory_allocator.h"  #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -42,6 +43,9 @@ class RasterizerVulkan;  class Scheduler;  class SMAA;  class Swapchain; +class PresentManager; + +struct Frame;  struct ScreenInfo {      VkImage image{}; @@ -55,18 +59,17 @@ class BlitScreen {  public:      explicit BlitScreen(Core::Memory::Memory& cpu_memory, Core::Frontend::EmuWindow& render_window,                          const Device& device, MemoryAllocator& memory_manager, Swapchain& swapchain, -                        Scheduler& scheduler, const ScreenInfo& screen_info); +                        PresentManager& present_manager, Scheduler& scheduler, +                        const ScreenInfo& screen_info);      ~BlitScreen();      void Recreate(); -    [[nodiscard]] VkSemaphore Draw(const Tegra::FramebufferConfig& framebuffer, -                                   const VkFramebuffer& host_framebuffer, -                                   const Layout::FramebufferLayout layout, VkExtent2D render_area, -                                   bool use_accelerated); +    void Draw(const Tegra::FramebufferConfig& framebuffer, const VkFramebuffer& host_framebuffer, +              const Layout::FramebufferLayout layout, VkExtent2D render_area, bool use_accelerated); -    [[nodiscard]] VkSemaphore DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer, -                                              bool use_accelerated); +    void DrawToSwapchain(Frame* frame, const Tegra::FramebufferConfig& framebuffer, +                         bool use_accelerated, bool is_srgb);      [[nodiscard]] vk::Framebuffer CreateFramebuffer(const VkImageView& image_view,                                                      VkExtent2D extent); @@ -79,10 +82,9 @@ private:      void CreateStaticResources();      void CreateShaders(); -    void CreateSemaphores();      void CreateDescriptorPool();      void CreateRenderPass(); -    vk::RenderPass CreateRenderPassImpl(VkFormat, bool is_present = true); +    vk::RenderPass CreateRenderPassImpl(VkFormat format);      void CreateDescriptorSetLayout();      void CreateDescriptorSets();      void CreatePipelineLayout(); @@ -90,15 +92,14 @@ private:      void CreateSampler();      void CreateDynamicResources(); -    void CreateFramebuffers();      void RefreshResources(const Tegra::FramebufferConfig& framebuffer);      void ReleaseRawImages();      void CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer);      void CreateRawImages(const Tegra::FramebufferConfig& framebuffer); -    void UpdateDescriptorSet(std::size_t image_index, VkImageView image_view, bool nn) const; -    void UpdateAADescriptorSet(std::size_t image_index, VkImageView image_view, bool nn) const; +    void UpdateDescriptorSet(VkImageView image_view, bool nn) const; +    void UpdateAADescriptorSet(VkImageView image_view, bool nn) const;      void SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const;      void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer,                         const Layout::FramebufferLayout layout) const; @@ -107,16 +108,17 @@ private:      void CreateFSR();      u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const; -    u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, -                          std::size_t image_index) const; +    u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer) const;      Core::Memory::Memory& cpu_memory;      Core::Frontend::EmuWindow& render_window;      const Device& device;      MemoryAllocator& memory_allocator;      Swapchain& swapchain; +    PresentManager& present_manager;      Scheduler& scheduler;      std::size_t image_count; +    std::size_t image_index{};      const ScreenInfo& screen_info;      vk::ShaderModule vertex_shader; @@ -135,7 +137,6 @@ private:      vk::Pipeline gaussian_pipeline;      vk::Pipeline scaleforce_pipeline;      vk::RenderPass renderpass; -    std::vector<vk::Framebuffer> framebuffers;      vk::DescriptorSets descriptor_sets;      vk::Sampler nn_sampler;      vk::Sampler sampler; @@ -145,7 +146,6 @@ private:      std::vector<u64> resource_ticks; -    std::vector<vk::Semaphore> semaphores;      std::vector<vk::Image> raw_images;      std::vector<vk::ImageView> raw_image_views;      std::vector<MemoryCommit> raw_buffer_commits; @@ -164,6 +164,8 @@ private:      u32 raw_width = 0;      u32 raw_height = 0;      Service::android::PixelFormat pixel_format{}; +    bool current_srgb; +    VkFormat image_view_format;      std::unique_ptr<FSR> fsr;      std::unique_ptr<SMAA> smaa; diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp new file mode 100644 index 000000000..a137c66f2 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -0,0 +1,454 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/microprofile.h" +#include "common/settings.h" +#include "common/thread.h" +#include "video_core/renderer_vulkan/vk_present_manager.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_swapchain.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +MICROPROFILE_DEFINE(Vulkan_WaitPresent, "Vulkan", "Wait For Present", MP_RGB(128, 128, 128)); +MICROPROFILE_DEFINE(Vulkan_CopyToSwapchain, "Vulkan", "Copy to swapchain", MP_RGB(192, 255, 192)); + +namespace { + +bool CanBlitToSwapchain(const vk::PhysicalDevice& physical_device, VkFormat format) { +    const VkFormatProperties props{physical_device.GetFormatProperties(format)}; +    return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT); +} + +[[nodiscard]] VkImageSubresourceLayers MakeImageSubresourceLayers() { +    return VkImageSubresourceLayers{ +        .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, +        .mipLevel = 0, +        .baseArrayLayer = 0, +        .layerCount = 1, +    }; +} + +[[nodiscard]] VkImageBlit MakeImageBlit(s32 frame_width, s32 frame_height, s32 swapchain_width, +                                        s32 swapchain_height) { +    return VkImageBlit{ +        .srcSubresource = MakeImageSubresourceLayers(), +        .srcOffsets = +            { +                { +                    .x = 0, +                    .y = 0, +                    .z = 0, +                }, +                { +                    .x = frame_width, +                    .y = frame_height, +                    .z = 1, +                }, +            }, +        .dstSubresource = MakeImageSubresourceLayers(), +        .dstOffsets = +            { +                { +                    .x = 0, +                    .y = 0, +                    .z = 0, +                }, +                { +                    .x = swapchain_width, +                    .y = swapchain_height, +                    .z = 1, +                }, +            }, +    }; +} + +[[nodiscard]] VkImageCopy MakeImageCopy(u32 frame_width, u32 frame_height, u32 swapchain_width, +                                        u32 swapchain_height) { +    return VkImageCopy{ +        .srcSubresource = MakeImageSubresourceLayers(), +        .srcOffset = +            { +                .x = 0, +                .y = 0, +                .z = 0, +            }, +        .dstSubresource = MakeImageSubresourceLayers(), +        .dstOffset = +            { +                .x = 0, +                .y = 0, +                .z = 0, +            }, +        .extent = +            { +                .width = std::min(frame_width, swapchain_width), +                .height = std::min(frame_height, swapchain_height), +                .depth = 1, +            }, +    }; +} + +} // Anonymous namespace + +PresentManager::PresentManager(Core::Frontend::EmuWindow& render_window_, const Device& device_, +                               MemoryAllocator& memory_allocator_, Scheduler& scheduler_, +                               Swapchain& swapchain_) +    : render_window{render_window_}, device{device_}, +      memory_allocator{memory_allocator_}, scheduler{scheduler_}, swapchain{swapchain_}, +      blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())}, +      use_present_thread{Settings::values.async_presentation.GetValue()}, +      image_count{swapchain.GetImageCount()} { + +    auto& dld = device.GetLogical(); +    cmdpool = dld.CreateCommandPool({ +        .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, +        .pNext = nullptr, +        .flags = +            VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, +        .queueFamilyIndex = device.GetGraphicsFamily(), +    }); +    auto cmdbuffers = cmdpool.Allocate(image_count); + +    frames.resize(image_count); +    for (u32 i = 0; i < frames.size(); i++) { +        Frame& frame = frames[i]; +        frame.cmdbuf = vk::CommandBuffer{cmdbuffers[i], device.GetDispatchLoader()}; +        frame.render_ready = dld.CreateSemaphore({ +            .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, +            .pNext = nullptr, +            .flags = 0, +        }); +        frame.present_done = dld.CreateFence({ +            .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, +            .pNext = nullptr, +            .flags = VK_FENCE_CREATE_SIGNALED_BIT, +        }); +        free_queue.push(&frame); +    } + +    if (use_present_thread) { +        present_thread = std::jthread([this](std::stop_token token) { PresentThread(token); }); +    } +} + +PresentManager::~PresentManager() = default; + +Frame* PresentManager::GetRenderFrame() { +    MICROPROFILE_SCOPE(Vulkan_WaitPresent); + +    // Wait for free presentation frames +    std::unique_lock lock{free_mutex}; +    free_cv.wait(lock, [this] { return !free_queue.empty(); }); + +    // Take the frame from the queue +    Frame* frame = free_queue.front(); +    free_queue.pop(); + +    // Wait for the presentation to be finished so all frame resources are free +    frame->present_done.Wait(); +    frame->present_done.Reset(); + +    return frame; +} + +void PresentManager::PushFrame(Frame* frame) { +    if (!use_present_thread) { +        CopyToSwapchain(frame); +        free_queue.push(frame); +        return; +    } + +    std::unique_lock lock{queue_mutex}; +    present_queue.push(frame); +    frame_cv.notify_one(); +} + +void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, bool is_srgb, +                                   VkFormat image_view_format, VkRenderPass rd) { +    auto& dld = device.GetLogical(); + +    frame->width = width; +    frame->height = height; +    frame->is_srgb = is_srgb; + +    frame->image = dld.CreateImage({ +        .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, +        .pNext = nullptr, +        .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, +        .imageType = VK_IMAGE_TYPE_2D, +        .format = swapchain.GetImageFormat(), +        .extent = +            { +                .width = width, +                .height = height, +                .depth = 1, +            }, +        .mipLevels = 1, +        .arrayLayers = 1, +        .samples = VK_SAMPLE_COUNT_1_BIT, +        .tiling = VK_IMAGE_TILING_OPTIMAL, +        .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, +        .sharingMode = VK_SHARING_MODE_EXCLUSIVE, +        .queueFamilyIndexCount = 0, +        .pQueueFamilyIndices = nullptr, +        .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, +    }); + +    frame->image_commit = memory_allocator.Commit(frame->image, MemoryUsage::DeviceLocal); + +    frame->image_view = dld.CreateImageView({ +        .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, +        .pNext = nullptr, +        .flags = 0, +        .image = *frame->image, +        .viewType = VK_IMAGE_VIEW_TYPE_2D, +        .format = image_view_format, +        .components = +            { +                .r = VK_COMPONENT_SWIZZLE_IDENTITY, +                .g = VK_COMPONENT_SWIZZLE_IDENTITY, +                .b = VK_COMPONENT_SWIZZLE_IDENTITY, +                .a = VK_COMPONENT_SWIZZLE_IDENTITY, +            }, +        .subresourceRange = +            { +                .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, +                .baseMipLevel = 0, +                .levelCount = 1, +                .baseArrayLayer = 0, +                .layerCount = 1, +            }, +    }); + +    const VkImageView image_view{*frame->image_view}; +    frame->framebuffer = dld.CreateFramebuffer({ +        .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, +        .pNext = nullptr, +        .flags = 0, +        .renderPass = rd, +        .attachmentCount = 1, +        .pAttachments = &image_view, +        .width = width, +        .height = height, +        .layers = 1, +    }); +} + +void PresentManager::WaitPresent() { +    if (!use_present_thread) { +        return; +    } + +    // Wait for the present queue to be empty +    { +        std::unique_lock queue_lock{queue_mutex}; +        frame_cv.wait(queue_lock, [this] { return present_queue.empty(); }); +    } + +    // The above condition will be satisfied when the last frame is taken from the queue. +    // To ensure that frame has been presented as well take hold of the swapchain +    // mutex. +    std::scoped_lock swapchain_lock{swapchain_mutex}; +} + +void PresentManager::PresentThread(std::stop_token token) { +    Common::SetCurrentThreadName("VulkanPresent"); +    while (!token.stop_requested()) { +        std::unique_lock lock{queue_mutex}; + +        // Wait for presentation frames +        Common::CondvarWait(frame_cv, lock, token, [this] { return !present_queue.empty(); }); +        if (token.stop_requested()) { +            return; +        } + +        // Take the frame and notify anyone waiting +        Frame* frame = present_queue.front(); +        present_queue.pop(); +        frame_cv.notify_one(); + +        // By exchanging the lock ownership we take the swapchain lock +        // before the queue lock goes out of scope. This way the swapchain +        // lock in WaitPresent is guaranteed to occur after here. +        std::exchange(lock, std::unique_lock{swapchain_mutex}); + +        CopyToSwapchain(frame); + +        // Free the frame for reuse +        std::scoped_lock fl{free_mutex}; +        free_queue.push(frame); +        free_cv.notify_one(); +    } +} + +void PresentManager::CopyToSwapchain(Frame* frame) { +    MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain); + +    const auto recreate_swapchain = [&] { +        swapchain.Create(frame->width, frame->height, frame->is_srgb); +        image_count = swapchain.GetImageCount(); +    }; + +    // If the size or colorspace of the incoming frames has changed, recreate the swapchain +    // to account for that. +    const bool srgb_changed = swapchain.NeedsRecreation(frame->is_srgb); +    const bool size_changed = +        swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height; +    if (srgb_changed || size_changed) { +        recreate_swapchain(); +    } + +    while (swapchain.AcquireNextImage()) { +        recreate_swapchain(); +    } + +    const vk::CommandBuffer cmdbuf{frame->cmdbuf}; +    cmdbuf.Begin({ +        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, +        .pNext = nullptr, +        .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, +        .pInheritanceInfo = nullptr, +    }); + +    const VkImage image{swapchain.CurrentImage()}; +    const VkExtent2D extent = swapchain.GetExtent(); +    const std::array pre_barriers{ +        VkImageMemoryBarrier{ +            .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, +            .pNext = nullptr, +            .srcAccessMask = 0, +            .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, +            .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, +            .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, +            .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, +            .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, +            .image = image, +            .subresourceRange{ +                .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, +                .baseMipLevel = 0, +                .levelCount = 1, +                .baseArrayLayer = 0, +                .layerCount = VK_REMAINING_ARRAY_LAYERS, +            }, +        }, +        VkImageMemoryBarrier{ +            .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, +            .pNext = nullptr, +            .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, +            .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, +            .oldLayout = VK_IMAGE_LAYOUT_GENERAL, +            .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, +            .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, +            .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, +            .image = *frame->image, +            .subresourceRange{ +                .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, +                .baseMipLevel = 0, +                .levelCount = 1, +                .baseArrayLayer = 0, +                .layerCount = VK_REMAINING_ARRAY_LAYERS, +            }, +        }, +    }; +    const std::array post_barriers{ +        VkImageMemoryBarrier{ +            .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, +            .pNext = nullptr, +            .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, +            .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, +            .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, +            .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, +            .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, +            .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, +            .image = image, +            .subresourceRange{ +                .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, +                .baseMipLevel = 0, +                .levelCount = 1, +                .baseArrayLayer = 0, +                .layerCount = VK_REMAINING_ARRAY_LAYERS, +            }, +        }, +        VkImageMemoryBarrier{ +            .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, +            .pNext = nullptr, +            .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, +            .dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, +            .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, +            .newLayout = VK_IMAGE_LAYOUT_GENERAL, +            .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, +            .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, +            .image = *frame->image, +            .subresourceRange{ +                .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, +                .baseMipLevel = 0, +                .levelCount = 1, +                .baseArrayLayer = 0, +                .layerCount = VK_REMAINING_ARRAY_LAYERS, +            }, +        }, +    }; + +    cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, {}, +                           {}, {}, pre_barriers); + +    if (blit_supported) { +        cmdbuf.BlitImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, +                         VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, +                         MakeImageBlit(frame->width, frame->height, extent.width, extent.height), +                         VK_FILTER_LINEAR); +    } else { +        cmdbuf.CopyImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, +                         VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, +                         MakeImageCopy(frame->width, frame->height, extent.width, extent.height)); +    } + +    cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, {}, +                           {}, {}, post_barriers); + +    cmdbuf.End(); + +    const VkSemaphore present_semaphore = swapchain.CurrentPresentSemaphore(); +    const VkSemaphore render_semaphore = swapchain.CurrentRenderSemaphore(); +    const std::array wait_semaphores = {present_semaphore, *frame->render_ready}; + +    static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{ +        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, +        VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, +    }; + +    const VkSubmitInfo submit_info{ +        .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, +        .pNext = nullptr, +        .waitSemaphoreCount = 2U, +        .pWaitSemaphores = wait_semaphores.data(), +        .pWaitDstStageMask = wait_stage_masks.data(), +        .commandBufferCount = 1, +        .pCommandBuffers = cmdbuf.address(), +        .signalSemaphoreCount = 1U, +        .pSignalSemaphores = &render_semaphore, +    }; + +    // Submit the image copy/blit to the swapchain +    { +        std::scoped_lock lock{scheduler.submit_mutex}; +        switch (const VkResult result = +                    device.GetGraphicsQueue().Submit(submit_info, *frame->present_done)) { +        case VK_SUCCESS: +            break; +        case VK_ERROR_DEVICE_LOST: +            device.ReportLoss(); +            [[fallthrough]]; +        default: +            vk::Check(result); +            break; +        } +    } + +    // Present +    swapchain.Present(render_semaphore); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h new file mode 100644 index 000000000..9885fd7c6 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_present_manager.h @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <condition_variable> +#include <mutex> +#include <queue> + +#include "common/common_types.h" +#include "common/polyfill_thread.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Core::Frontend { +class EmuWindow; +} // namespace Core::Frontend + +namespace Vulkan { + +class Device; +class Scheduler; +class Swapchain; + +struct Frame { +    u32 width; +    u32 height; +    bool is_srgb; +    vk::Image image; +    vk::ImageView image_view; +    vk::Framebuffer framebuffer; +    MemoryCommit image_commit; +    vk::CommandBuffer cmdbuf; +    vk::Semaphore render_ready; +    vk::Fence present_done; +}; + +class PresentManager { +public: +    PresentManager(Core::Frontend::EmuWindow& render_window, const Device& device, +                   MemoryAllocator& memory_allocator, Scheduler& scheduler, Swapchain& swapchain); +    ~PresentManager(); + +    /// Returns the last used presentation frame +    Frame* GetRenderFrame(); + +    /// Pushes a frame for presentation +    void PushFrame(Frame* frame); + +    /// Recreates the present frame to match the provided parameters +    void RecreateFrame(Frame* frame, u32 width, u32 height, bool is_srgb, +                       VkFormat image_view_format, VkRenderPass rd); + +    /// Waits for the present thread to finish presenting all queued frames. +    void WaitPresent(); + +private: +    void PresentThread(std::stop_token token); + +    void CopyToSwapchain(Frame* frame); + +private: +    Core::Frontend::EmuWindow& render_window; +    const Device& device; +    MemoryAllocator& memory_allocator; +    Scheduler& scheduler; +    Swapchain& swapchain; +    vk::CommandPool cmdpool; +    std::vector<Frame> frames; +    std::queue<Frame*> present_queue; +    std::queue<Frame*> free_queue; +    std::condition_variable_any frame_cv; +    std::condition_variable free_cv; +    std::mutex swapchain_mutex; +    std::mutex queue_mutex; +    std::mutex free_mutex; +    std::jthread present_thread; +    bool blit_supported; +    bool use_present_thread; +    std::size_t image_count; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 057e16967..80455ec08 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -46,10 +46,11 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_)  Scheduler::~Scheduler() = default; -void Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { +u64 Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {      // When flushing, we only send data to the worker thread; no waiting is necessary. -    SubmitExecution(signal_semaphore, wait_semaphore); +    const u64 signal_value = SubmitExecution(signal_semaphore, wait_semaphore);      AllocateNewContext(); +    return signal_value;  }  void Scheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { @@ -205,7 +206,7 @@ void Scheduler::AllocateWorkerCommandBuffer() {      });  } -void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { +u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {      EndPendingOperations();      InvalidateState(); @@ -217,6 +218,7 @@ void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_s              on_submit();          } +        std::scoped_lock lock{submit_mutex};          switch (const VkResult result = master_semaphore->SubmitQueue(                      cmdbuf, signal_semaphore, wait_semaphore, signal_value)) {          case VK_SUCCESS: @@ -231,6 +233,7 @@ void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_s      });      chunk->MarkSubmit();      DispatchWork(); +    return signal_value;  }  void Scheduler::AllocateNewContext() { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 8d75ce987..475c682eb 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -34,7 +34,7 @@ public:      ~Scheduler();      /// Sends the current execution context to the GPU. -    void Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); +    u64 Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);      /// Sends the current execution context to the GPU and waits for it to complete.      void Finish(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); @@ -106,6 +106,8 @@ public:          return *master_semaphore;      } +    std::mutex submit_mutex; +  private:      class Command {      public: @@ -201,7 +203,7 @@ private:      void AllocateWorkerCommandBuffer(); -    void SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore); +    u64 SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore);      void AllocateNewContext(); diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index b1465e35c..23bbea7f1 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -99,18 +99,16 @@ void Swapchain::Create(u32 width_, u32 height_, bool srgb) {          return;      } -    device.GetLogical().WaitIdle();      Destroy();      CreateSwapchain(capabilities, srgb);      CreateSemaphores(); -    CreateImageViews();      resource_ticks.clear();      resource_ticks.resize(image_count);  } -void Swapchain::AcquireNextImage() { +bool Swapchain::AcquireNextImage() {      const VkResult result = device.GetLogical().AcquireNextImageKHR(          *swapchain, std::numeric_limits<u64>::max(), *present_semaphores[frame_index],          VK_NULL_HANDLE, &image_index); @@ -127,8 +125,11 @@ void Swapchain::AcquireNextImage() {          LOG_ERROR(Render_Vulkan, "vkAcquireNextImageKHR returned {}", vk::ToString(result));          break;      } +      scheduler.Wait(resource_ticks[image_index]);      resource_ticks[image_index] = scheduler.CurrentTick(); + +    return is_suboptimal || is_outdated;  }  void Swapchain::Present(VkSemaphore render_semaphore) { @@ -143,6 +144,7 @@ void Swapchain::Present(VkSemaphore render_semaphore) {          .pImageIndices = &image_index,          .pResults = nullptr,      }; +    std::scoped_lock lock{scheduler.submit_mutex};      switch (const VkResult result = present_queue.Present(present_info)) {      case VK_SUCCESS:          break; @@ -168,7 +170,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo      const auto present_modes{physical_device.GetSurfacePresentModesKHR(surface)};      const VkCompositeAlphaFlagBitsKHR alpha_flags{ChooseAlphaFlags(capabilities)}; -    const VkSurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats)}; +    surface_format = ChooseSwapSurfaceFormat(formats);      present_mode = ChooseSwapPresentMode(present_modes);      u32 requested_image_count{capabilities.minImageCount + 1}; @@ -193,7 +195,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo          .imageColorSpace = surface_format.colorSpace,          .imageExtent = {},          .imageArrayLayers = 1, -        .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, +        .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,          .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,          .queueFamilyIndexCount = 0,          .pQueueFamilyIndices = nullptr, @@ -241,45 +243,14 @@ void Swapchain::CreateSemaphores() {      present_semaphores.resize(image_count);      std::ranges::generate(present_semaphores,                            [this] { return device.GetLogical().CreateSemaphore(); }); -} - -void Swapchain::CreateImageViews() { -    VkImageViewCreateInfo ci{ -        .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, -        .pNext = nullptr, -        .flags = 0, -        .image = {}, -        .viewType = VK_IMAGE_VIEW_TYPE_2D, -        .format = image_view_format, -        .components = -            { -                .r = VK_COMPONENT_SWIZZLE_IDENTITY, -                .g = VK_COMPONENT_SWIZZLE_IDENTITY, -                .b = VK_COMPONENT_SWIZZLE_IDENTITY, -                .a = VK_COMPONENT_SWIZZLE_IDENTITY, -            }, -        .subresourceRange = -            { -                .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, -                .baseMipLevel = 0, -                .levelCount = 1, -                .baseArrayLayer = 0, -                .layerCount = 1, -            }, -    }; - -    image_views.resize(image_count); -    for (std::size_t i = 0; i < image_count; i++) { -        ci.image = images[i]; -        image_views[i] = device.GetLogical().CreateImageView(ci); -    } +    render_semaphores.resize(image_count); +    std::ranges::generate(render_semaphores, +                          [this] { return device.GetLogical().CreateSemaphore(); });  }  void Swapchain::Destroy() {      frame_index = 0;      present_semaphores.clear(); -    framebuffers.clear(); -    image_views.clear();      swapchain.reset();  } diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index caf1ff32b..419742586 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -27,7 +27,7 @@ public:      void Create(u32 width, u32 height, bool srgb);      /// Acquires the next image in the swapchain, waits as needed. -    void AcquireNextImage(); +    bool AcquireNextImage();      /// Presents the rendered image to the swapchain.      void Present(VkSemaphore render_semaphore); @@ -52,6 +52,11 @@ public:          return is_suboptimal;      } +    /// Returns true when the swapchain format is in the srgb color space +    bool IsSrgb() const { +        return current_srgb; +    } +      VkExtent2D GetSize() const {          return extent;      } @@ -64,22 +69,34 @@ public:          return image_index;      } +    std::size_t GetFrameIndex() const { +        return frame_index; +    } +      VkImage GetImageIndex(std::size_t index) const {          return images[index];      } -    VkImageView GetImageViewIndex(std::size_t index) const { -        return *image_views[index]; +    VkImage CurrentImage() const { +        return images[image_index];      }      VkFormat GetImageViewFormat() const {          return image_view_format;      } +    VkFormat GetImageFormat() const { +        return surface_format.format; +    } +      VkSemaphore CurrentPresentSemaphore() const {          return *present_semaphores[frame_index];      } +    VkSemaphore CurrentRenderSemaphore() const { +        return *render_semaphores[frame_index]; +    } +      u32 GetWidth() const {          return width;      } @@ -88,6 +105,10 @@ public:          return height;      } +    VkExtent2D GetExtent() const { +        return extent; +    } +  private:      void CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bool srgb);      void CreateSemaphores(); @@ -107,10 +128,9 @@ private:      std::size_t image_count{};      std::vector<VkImage> images; -    std::vector<vk::ImageView> image_views; -    std::vector<vk::Framebuffer> framebuffers;      std::vector<u64> resource_ticks;      std::vector<vk::Semaphore> present_semaphores; +    std::vector<vk::Semaphore> render_semaphores;      u32 width;      u32 height; @@ -121,6 +141,7 @@ private:      VkFormat image_view_format{};      VkExtent2D extent{};      VkPresentModeKHR present_mode{}; +    VkSurfaceFormatKHR surface_format{};      bool current_srgb{};      bool current_fps_unlocked{}; diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp index 009dab0b6..0630ebda5 100644 --- a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp @@ -14,13 +14,18 @@ namespace Vulkan {  UpdateDescriptorQueue::UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_)      : device{device_}, scheduler{scheduler_} { +    payload_start = payload.data();      payload_cursor = payload.data();  }  UpdateDescriptorQueue::~UpdateDescriptorQueue() = default;  void UpdateDescriptorQueue::TickFrame() { -    payload_cursor = payload.data(); +    if (++frame_index >= FRAMES_IN_FLIGHT) { +        frame_index = 0; +    } +    payload_start = payload.data() + frame_index * FRAME_PAYLOAD_SIZE; +    payload_cursor = payload_start;  }  void UpdateDescriptorQueue::Acquire() { @@ -28,10 +33,10 @@ void UpdateDescriptorQueue::Acquire() {      // This is the maximum number of entries a single draw call might use.      static constexpr size_t MIN_ENTRIES = 0x400; -    if (std::distance(payload.data(), payload_cursor) + MIN_ENTRIES >= payload.max_size()) { +    if (std::distance(payload_start, payload_cursor) + MIN_ENTRIES >= FRAME_PAYLOAD_SIZE) {          LOG_WARNING(Render_Vulkan, "Payload overflow, waiting for worker thread");          scheduler.WaitWorker(); -        payload_cursor = payload.data(); +        payload_cursor = payload_start;      }      upload_start = payload_cursor;  } diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.h b/src/video_core/renderer_vulkan/vk_update_descriptor.h index 625bcc809..1c1a7020b 100644 --- a/src/video_core/renderer_vulkan/vk_update_descriptor.h +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.h @@ -29,6 +29,12 @@ struct DescriptorUpdateEntry {  };  class UpdateDescriptorQueue final { +    // This should be plenty for the vast majority of cases. Most desktop platforms only +    // provide up to 3 swapchain images. +    static constexpr size_t FRAMES_IN_FLIGHT = 5; +    static constexpr size_t FRAME_PAYLOAD_SIZE = 0x10000; +    static constexpr size_t PAYLOAD_SIZE = FRAME_PAYLOAD_SIZE * FRAMES_IN_FLIGHT; +  public:      explicit UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_);      ~UpdateDescriptorQueue(); @@ -73,9 +79,11 @@ private:      const Device& device;      Scheduler& scheduler; +    size_t frame_index{0};      DescriptorUpdateEntry* payload_cursor = nullptr; +    DescriptorUpdateEntry* payload_start = nullptr;      const DescriptorUpdateEntry* upload_start = nullptr; -    std::array<DescriptorUpdateEntry, 0x10000> payload; +    std::array<DescriptorUpdateEntry, PAYLOAD_SIZE> payload;  };  } // namespace Vulkan diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index bb731276e..be33e4d79 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -497,7 +497,7 @@ void Config::ReadCoreValues() {      qt_config->beginGroup(QStringLiteral("Core"));      ReadGlobalSetting(Settings::values.use_multi_core); -    ReadGlobalSetting(Settings::values.use_extended_memory_layout); +    ReadGlobalSetting(Settings::values.use_unsafe_extended_memory_layout);      qt_config->endGroup();  } @@ -692,6 +692,7 @@ void Config::ReadRendererValues() {      qt_config->beginGroup(QStringLiteral("Renderer"));      ReadGlobalSetting(Settings::values.renderer_backend); +    ReadGlobalSetting(Settings::values.async_presentation);      ReadGlobalSetting(Settings::values.renderer_force_max_clock);      ReadGlobalSetting(Settings::values.vulkan_device);      ReadGlobalSetting(Settings::values.fullscreen_mode); @@ -1161,7 +1162,7 @@ void Config::SaveCoreValues() {      qt_config->beginGroup(QStringLiteral("Core"));      WriteGlobalSetting(Settings::values.use_multi_core); -    WriteGlobalSetting(Settings::values.use_extended_memory_layout); +    WriteGlobalSetting(Settings::values.use_unsafe_extended_memory_layout);      qt_config->endGroup();  } @@ -1313,6 +1314,7 @@ void Config::SaveRendererValues() {                   static_cast<u32>(Settings::values.renderer_backend.GetValue(global)),                   static_cast<u32>(Settings::values.renderer_backend.GetDefault()),                   Settings::values.renderer_backend.UsingGlobal()); +    WriteGlobalSetting(Settings::values.async_presentation);      WriteGlobalSetting(Settings::values.renderer_force_max_clock);      WriteGlobalSetting(Settings::values.vulkan_device);      WriteSetting(QString::fromStdString(Settings::values.fullscreen_mode.GetLabel()), diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 207bcdc4d..26258d744 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -35,9 +35,6 @@ void ConfigureGeneral::SetConfiguration() {      ui->use_multi_core->setEnabled(runtime_lock);      ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue()); -    ui->use_extended_memory_layout->setEnabled(runtime_lock); -    ui->use_extended_memory_layout->setChecked( -        Settings::values.use_extended_memory_layout.GetValue());      ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing.GetValue());      ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot.GetValue()); @@ -79,9 +76,6 @@ void ConfigureGeneral::ResetDefaults() {  void ConfigureGeneral::ApplyConfiguration() {      ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, ui->use_multi_core,                                               use_multi_core); -    ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_extended_memory_layout, -                                             ui->use_extended_memory_layout, -                                             use_extended_memory_layout);      if (Settings::IsConfiguringGlobal()) {          UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); @@ -141,9 +135,6 @@ void ConfigureGeneral::SetupPerGameUI() {                                              Settings::values.use_speed_limit, use_speed_limit);      ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core,                                              use_multi_core); -    ConfigurationShared::SetColoredTristate(ui->use_extended_memory_layout, -                                            Settings::values.use_extended_memory_layout, -                                            use_extended_memory_layout);      connect(ui->toggle_speed_limit, &QCheckBox::clicked, ui->speed_limit, [this]() {          ui->speed_limit->setEnabled(ui->toggle_speed_limit->isChecked() && diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index a090c1a3f..7ff63f425 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -47,7 +47,6 @@ private:      ConfigurationShared::CheckState use_speed_limit;      ConfigurationShared::CheckState use_multi_core; -    ConfigurationShared::CheckState use_extended_memory_layout;      const Core::System& system;  }; diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index add110bb0..986a1625b 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -62,13 +62,6 @@             </widget>            </item>            <item> -           <widget class="QCheckBox" name="use_extended_memory_layout"> -            <property name="text"> -             <string>Extended memory layout (8GB DRAM)</string> -            </property> -           </widget> -          </item> -          <item>             <widget class="QCheckBox" name="toggle_check_exit">              <property name="text">               <string>Confirm exit while emulation is running</string> diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 59fb1b334..7f7bf0e4d 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -22,11 +22,13 @@ ConfigureGraphicsAdvanced::~ConfigureGraphicsAdvanced() = default;  void ConfigureGraphicsAdvanced::SetConfiguration() {      const bool runtime_lock = !system.IsPoweredOn();      ui->use_vsync->setEnabled(runtime_lock); +    ui->async_present->setEnabled(runtime_lock);      ui->renderer_force_max_clock->setEnabled(runtime_lock);      ui->async_astc->setEnabled(runtime_lock);      ui->use_asynchronous_shaders->setEnabled(runtime_lock);      ui->anisotropic_filtering_combobox->setEnabled(runtime_lock); +    ui->async_present->setChecked(Settings::values.async_presentation.GetValue());      ui->renderer_force_max_clock->setChecked(Settings::values.renderer_force_max_clock.GetValue());      ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue());      ui->async_astc->setChecked(Settings::values.async_astc.GetValue()); @@ -54,6 +56,8 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {  void ConfigureGraphicsAdvanced::ApplyConfiguration() {      ConfigurationShared::ApplyPerGameSetting(&Settings::values.gpu_accuracy, ui->gpu_accuracy); +    ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_presentation, +                                             ui->async_present, async_present);      ConfigurationShared::ApplyPerGameSetting(&Settings::values.renderer_force_max_clock,                                               ui->renderer_force_max_clock,                                               renderer_force_max_clock); @@ -90,6 +94,7 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {      // Disable if not global (only happens during game)      if (Settings::IsConfiguringGlobal()) {          ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal()); +        ui->async_present->setEnabled(Settings::values.async_presentation.UsingGlobal());          ui->renderer_force_max_clock->setEnabled(              Settings::values.renderer_force_max_clock.UsingGlobal());          ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal()); @@ -107,6 +112,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {          return;      } +    ConfigurationShared::SetColoredTristate(ui->async_present, Settings::values.async_presentation, +                                            async_present);      ConfigurationShared::SetColoredTristate(ui->renderer_force_max_clock,                                              Settings::values.renderer_force_max_clock,                                              renderer_force_max_clock); diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index bf1b04749..5394ed40a 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -36,6 +36,7 @@ private:      std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui; +    ConfigurationShared::CheckState async_present;      ConfigurationShared::CheckState renderer_force_max_clock;      ConfigurationShared::CheckState use_vsync;      ConfigurationShared::CheckState async_astc; diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index a7dbdc18c..d7ec18939 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui @@ -7,7 +7,7 @@      <x>0</x>      <y>0</y>      <width>404</width> -    <height>321</height> +    <height>376</height>     </rect>    </property>    <property name="windowTitle"> @@ -70,6 +70,13 @@           </widget>          </item>          <item> +         <widget class="QCheckBox" name="async_present"> +          <property name="text"> +           <string>Enable asynchronous presentation (Vulkan only)</string> +          </property> +         </widget> +        </item> +        <item>           <widget class="QCheckBox" name="renderer_force_max_clock">            <property name="toolTip">             <string>Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed.</string> @@ -112,7 +119,7 @@          <item>           <widget class="QCheckBox" name="use_fast_gpu_time">            <property name="toolTip"> -            <string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string> +           <string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string>            </property>            <property name="text">             <string>Use Fast GPU Time (Hack)</string> @@ -122,7 +129,7 @@          <item>           <widget class="QCheckBox" name="use_pessimistic_flushes">            <property name="toolTip"> -            <string>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</string> +           <string>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</string>            </property>            <property name="text">             <string>Use pessimistic buffer flushes (Hack)</string> @@ -132,7 +139,7 @@          <item>           <widget class="QCheckBox" name="use_vulkan_driver_pipeline_cache">            <property name="toolTip"> -            <string>Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally.</string> +           <string>Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally.</string>            </property>            <property name="text">             <string>Use Vulkan pipeline cache</string> diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 6af34f793..286ccc5cd 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -111,6 +111,9 @@ void ConfigureSystem::SetConfiguration() {      ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time));      ui->device_name_edit->setText(          QString::fromUtf8(Settings::values.device_name.GetValue().c_str())); +    ui->use_unsafe_extended_memory_layout->setEnabled(enabled); +    ui->use_unsafe_extended_memory_layout->setChecked( +        Settings::values.use_unsafe_extended_memory_layout.GetValue());      if (Settings::IsConfiguringGlobal()) {          ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue()); @@ -160,6 +163,9 @@ void ConfigureSystem::ApplyConfiguration() {      ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region);      ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index,                                               ui->combo_time_zone); +    ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_unsafe_extended_memory_layout, +                                             ui->use_unsafe_extended_memory_layout, +                                             use_unsafe_extended_memory_layout);      if (Settings::IsConfiguringGlobal()) {          // Guard if during game and set to game-specific value @@ -215,6 +221,10 @@ void ConfigureSystem::SetupPerGameUI() {          Settings::values.rng_seed.GetValue().has_value(),          Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed); +    ConfigurationShared::SetColoredTristate(ui->use_unsafe_extended_memory_layout, +                                            Settings::values.use_unsafe_extended_memory_layout, +                                            use_unsafe_extended_memory_layout); +      ui->custom_rtc_checkbox->setVisible(false);      ui->custom_rtc_edit->setVisible(false);  } diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index ec28724a1..ce1a91601 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -41,6 +41,7 @@ private:      bool enabled = false;      ConfigurationShared::CheckState use_rng_seed; +    ConfigurationShared::CheckState use_unsafe_extended_memory_layout;      Core::System& system;  }; diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index 9e7bc3b93..e0caecd5e 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -478,6 +478,13 @@              </property>             </widget>            </item> +          <item row="7" column="0"> +           <widget class="QCheckBox" name="use_unsafe_extended_memory_layout"> +            <property name="text"> +             <string>Unsafe extended memory layout (8GB DRAM)</string> +            </property> +           </widget> +          </item>           </layout>          </item>         </layout> diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index b79409a68..ba9eece1d 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -27,6 +27,7 @@  #include "configuration/configure_input.h"  #include "configuration/configure_per_game.h"  #include "configuration/configure_tas.h" +#include "core/file_sys/romfs_factory.h"  #include "core/file_sys/vfs.h"  #include "core/file_sys/vfs_real.h"  #include "core/frontend/applets/cabinet.h" @@ -4171,6 +4172,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {      }      Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); +    bool all_keys_present{true}; +      if (keys.BaseDeriveNecessary()) {          Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory("", FileSys::Mode::Read)}; @@ -4195,6 +4198,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {              errors += tr(" - Missing PRODINFO");          }          if (!errors.isEmpty()) { +            all_keys_present = false;              QMessageBox::warning(                  this, tr("Derivation Components Missing"),                  tr("Encryption keys are missing. " @@ -4222,11 +4226,40 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {      system->GetFileSystemController().CreateFactories(*vfs); +    if (all_keys_present && !this->CheckSystemArchiveDecryption()) { +        LOG_WARNING(Frontend, "Mii model decryption failed"); +        QMessageBox::warning( +            this, tr("System Archive Decryption Failed"), +            tr("Encryption keys failed to decrypt firmware. " +               "<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu " +               "quickstart guide</a> to get all your keys, firmware and " +               "games.")); +    } +      if (behavior == ReinitializeKeyBehavior::Warning) {          game_list->PopulateAsync(UISettings::values.game_dirs);      }  } +bool GMainWindow::CheckSystemArchiveDecryption() { +    constexpr u64 MiiModelId = 0x0100000000000802; + +    auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); +    if (!bis_system) { +        // Not having system BIS files is not an error. +        return true; +    } + +    auto mii_nca = bis_system->GetEntry(MiiModelId, FileSys::ContentRecordType::Data); +    if (!mii_nca) { +        // Not having the Mii model is not an error. +        return true; +    } + +    // Return whether we are able to decrypt the RomFS of the Mii model. +    return mii_nca->GetRomFS().get() != nullptr; +} +  std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed,                                                        u64 program_id) {      const auto dlc_entries = diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 8b5c1d747..3bbc31ada 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -392,6 +392,7 @@ private:      void LoadTranslation();      void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);      bool CheckDarkMode(); +    bool CheckSystemArchiveDecryption();      QString GetTasStateDescription() const;      bool CreateShortcut(const std::string& shortcut_path, const std::string& title, diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 464da3231..e4f91d07c 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -274,7 +274,7 @@ void Config::ReadValues() {      // Core      ReadSetting("Core", Settings::values.use_multi_core); -    ReadSetting("Core", Settings::values.use_extended_memory_layout); +    ReadSetting("Core", Settings::values.use_unsafe_extended_memory_layout);      // Cpu      ReadSetting("Cpu", Settings::values.cpu_accuracy); @@ -300,6 +300,7 @@ void Config::ReadValues() {      // Renderer      ReadSetting("Renderer", Settings::values.renderer_backend); +    ReadSetting("Renderer", Settings::values.async_presentation);      ReadSetting("Renderer", Settings::values.renderer_force_max_clock);      ReadSetting("Renderer", Settings::values.renderer_debug);      ReadSetting("Renderer", Settings::values.renderer_shader_feedback); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 209cfc28a..f714eae17 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -163,9 +163,9 @@ keyboard_enabled =  # 0: Disabled, 1 (default): Enabled  use_multi_core = -# Enable extended guest system memory layout (8GB DRAM) +# Enable unsafe extended guest system memory layout (8GB DRAM)  # 0 (default): Disabled, 1: Enabled -use_extended_memory_layout = +use_unsafe_extended_memory_layout =  [Cpu]  # Adjusts various optimizations. @@ -264,6 +264,10 @@ cpuopt_unsafe_ignore_global_monitor =  # 0: OpenGL, 1 (default): Vulkan  backend = +# Whether to enable asynchronous presentation (Vulkan only) +# 0 (default): Off, 1: On +async_presentation = +  # Enable graphics API debugging mode.  # 0 (default): Disabled, 1: Enabled  debug = | 
