diff options
| -rw-r--r-- | src/video_core/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/renderer_vulkan.cpp | 51 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/renderer_vulkan.h | 2 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_blit_screen.cpp | 224 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_blit_screen.h | 34 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_present_manager.cpp | 440 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_present_manager.h | 82 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_scheduler.cpp | 9 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_scheduler.h | 6 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_swapchain.cpp | 49 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_swapchain.h | 31 | 
11 files changed, 712 insertions, 218 deletions
| 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..4e8ce3ec7 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) { +        scheduler.Finish(); +        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..0b8e8ad27 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -0,0 +1,440 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/microprofile.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())}, +      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); +    } + +    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) { +    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() { +    // 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..f5d9fc96d --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_present_manager.h @@ -0,0 +1,82 @@ +// 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{}; +    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{}; | 
