summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp51
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp224
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.h34
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.cpp440
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.h82
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp9
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h6
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp49
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.h31
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
179 renderer_vulkan/vk_master_semaphore.h 179 renderer_vulkan/vk_master_semaphore.h
180 renderer_vulkan/vk_pipeline_cache.cpp 180 renderer_vulkan/vk_pipeline_cache.cpp
181 renderer_vulkan/vk_pipeline_cache.h 181 renderer_vulkan/vk_pipeline_cache.h
182 renderer_vulkan/vk_present_manager.cpp
183 renderer_vulkan/vk_present_manager.h
182 renderer_vulkan/vk_query_cache.cpp 184 renderer_vulkan/vk_query_cache.cpp
183 renderer_vulkan/vk_query_cache.h 185 renderer_vulkan/vk_query_cache.h
184 renderer_vulkan/vk_rasterizer.cpp 186 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_,
93 state_tracker(), scheduler(device, state_tracker), 93 state_tracker(), scheduler(device, state_tracker),
94 swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width, 94 swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width,
95 render_window.GetFramebufferLayout().height, false), 95 render_window.GetFramebufferLayout().height, false),
96 blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, scheduler, 96 present_manager(render_window, device, memory_allocator, scheduler, swapchain),
97 screen_info), 97 blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, present_manager,
98 scheduler, screen_info),
98 rasterizer(render_window, gpu, cpu_memory, screen_info, device, memory_allocator, 99 rasterizer(render_window, gpu, cpu_memory, screen_info, device, memory_allocator,
99 state_tracker, scheduler) { 100 state_tracker, scheduler) {
100 if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) { 101 if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
@@ -121,46 +122,19 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
121 return; 122 return;
122 } 123 }
123 // Update screen info if the framebuffer size has changed. 124 // Update screen info if the framebuffer size has changed.
124 if (screen_info.width != framebuffer->width || screen_info.height != framebuffer->height) { 125 screen_info.width = framebuffer->width;
125 screen_info.width = framebuffer->width; 126 screen_info.height = framebuffer->height;
126 screen_info.height = framebuffer->height; 127
127 }
128 const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset; 128 const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset;
129 const bool use_accelerated = 129 const bool use_accelerated =
130 rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); 130 rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
131 const bool is_srgb = use_accelerated && screen_info.is_srgb; 131 const bool is_srgb = use_accelerated && screen_info.is_srgb;
132 RenderScreenshot(*framebuffer, use_accelerated); 132 RenderScreenshot(*framebuffer, use_accelerated);
133 133
134 bool has_been_recreated = false; 134 Frame* frame = present_manager.GetRenderFrame();
135 const auto recreate_swapchain = [&](u32 width, u32 height) { 135 blit_screen.DrawToSwapchain(frame, *framebuffer, use_accelerated, is_srgb);
136 if (!has_been_recreated) { 136 scheduler.Flush(*frame->render_ready);
137 has_been_recreated = true; 137 scheduler.Record([this, frame](vk::CommandBuffer) { present_manager.PushFrame(frame); });
138 scheduler.Finish();
139 }
140 swapchain.Create(width, height, is_srgb);
141 };
142
143 const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
144 if (swapchain.NeedsRecreation(is_srgb) || swapchain.GetWidth() != layout.width ||
145 swapchain.GetHeight() != layout.height) {
146 recreate_swapchain(layout.width, layout.height);
147 }
148 bool is_outdated;
149 do {
150 swapchain.AcquireNextImage();
151 is_outdated = swapchain.IsOutDated();
152 if (is_outdated) {
153 recreate_swapchain(layout.width, layout.height);
154 }
155 } while (is_outdated);
156 if (has_been_recreated) {
157 blit_screen.Recreate();
158 }
159 const VkSemaphore render_semaphore = blit_screen.DrawToSwapchain(*framebuffer, use_accelerated);
160 const VkSemaphore present_semaphore = swapchain.CurrentPresentSemaphore();
161 scheduler.Flush(render_semaphore, present_semaphore);
162 scheduler.WaitWorker();
163 swapchain.Present(render_semaphore);
164 138
165 gpu.RendererFrameEndNotify(); 139 gpu.RendererFrameEndNotify();
166 rasterizer.TickFrame(); 140 rasterizer.TickFrame();
@@ -246,8 +220,7 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr
246 }); 220 });
247 const VkExtent2D render_area{.width = layout.width, .height = layout.height}; 221 const VkExtent2D render_area{.width = layout.width, .height = layout.height};
248 const vk::Framebuffer screenshot_fb = blit_screen.CreateFramebuffer(*dst_view, render_area); 222 const vk::Framebuffer screenshot_fb = blit_screen.CreateFramebuffer(*dst_view, render_area);
249 // Since we're not rendering to the screen, ignore the render semaphore. 223 blit_screen.Draw(framebuffer, *screenshot_fb, layout, render_area, use_accelerated);
250 void(blit_screen.Draw(framebuffer, *screenshot_fb, layout, render_area, use_accelerated));
251 224
252 const auto buffer_size = static_cast<VkDeviceSize>(layout.width * layout.height * 4); 225 const auto buffer_size = static_cast<VkDeviceSize>(layout.width * layout.height * 4);
253 const VkBufferCreateInfo dst_buffer_info{ 226 const VkBufferCreateInfo dst_buffer_info{
@@ -270,7 +243,7 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr
270 .pNext = nullptr, 243 .pNext = nullptr,
271 .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, 244 .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
272 .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, 245 .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
273 .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, 246 .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
274 .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, 247 .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
275 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 248 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
276 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 249 .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 @@
9#include "common/dynamic_library.h" 9#include "common/dynamic_library.h"
10#include "video_core/renderer_base.h" 10#include "video_core/renderer_base.h"
11#include "video_core/renderer_vulkan/vk_blit_screen.h" 11#include "video_core/renderer_vulkan/vk_blit_screen.h"
12#include "video_core/renderer_vulkan/vk_present_manager.h"
12#include "video_core/renderer_vulkan/vk_rasterizer.h" 13#include "video_core/renderer_vulkan/vk_rasterizer.h"
13#include "video_core/renderer_vulkan/vk_scheduler.h" 14#include "video_core/renderer_vulkan/vk_scheduler.h"
14#include "video_core/renderer_vulkan/vk_state_tracker.h" 15#include "video_core/renderer_vulkan/vk_state_tracker.h"
@@ -76,6 +77,7 @@ private:
76 StateTracker state_tracker; 77 StateTracker state_tracker;
77 Scheduler scheduler; 78 Scheduler scheduler;
78 Swapchain swapchain; 79 Swapchain swapchain;
80 PresentManager present_manager;
79 BlitScreen blit_screen; 81 BlitScreen blit_screen;
80 RasterizerVulkan rasterizer; 82 RasterizerVulkan rasterizer;
81 std::optional<TurboMode> turbo_mode; 83 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 {
122 122
123BlitScreen::BlitScreen(Core::Memory::Memory& cpu_memory_, Core::Frontend::EmuWindow& render_window_, 123BlitScreen::BlitScreen(Core::Memory::Memory& cpu_memory_, Core::Frontend::EmuWindow& render_window_,
124 const Device& device_, MemoryAllocator& memory_allocator_, 124 const Device& device_, MemoryAllocator& memory_allocator_,
125 Swapchain& swapchain_, Scheduler& scheduler_, const ScreenInfo& screen_info_) 125 Swapchain& swapchain_, PresentManager& present_manager_,
126 Scheduler& scheduler_, const ScreenInfo& screen_info_)
126 : cpu_memory{cpu_memory_}, render_window{render_window_}, device{device_}, 127 : cpu_memory{cpu_memory_}, render_window{render_window_}, device{device_},
127 memory_allocator{memory_allocator_}, swapchain{swapchain_}, scheduler{scheduler_}, 128 memory_allocator{memory_allocator_}, swapchain{swapchain_}, present_manager{present_manager_},
128 image_count{swapchain.GetImageCount()}, screen_info{screen_info_} { 129 scheduler{scheduler_}, image_count{swapchain.GetImageCount()}, screen_info{screen_info_},
130 current_srgb{swapchain.IsSrgb()}, image_view_format{swapchain.GetImageViewFormat()} {
129 resource_ticks.resize(image_count); 131 resource_ticks.resize(image_count);
130 132
131 CreateStaticResources(); 133 CreateStaticResources();
@@ -135,25 +137,20 @@ BlitScreen::BlitScreen(Core::Memory::Memory& cpu_memory_, Core::Frontend::EmuWin
135BlitScreen::~BlitScreen() = default; 137BlitScreen::~BlitScreen() = default;
136 138
137void BlitScreen::Recreate() { 139void BlitScreen::Recreate() {
140 present_manager.WaitPresent();
141 scheduler.Finish();
142 device.GetLogical().WaitIdle();
138 CreateDynamicResources(); 143 CreateDynamicResources();
139} 144}
140 145
141VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, 146void BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
142 const VkFramebuffer& host_framebuffer, 147 const VkFramebuffer& host_framebuffer, const Layout::FramebufferLayout layout,
143 const Layout::FramebufferLayout layout, VkExtent2D render_area, 148 VkExtent2D render_area, bool use_accelerated) {
144 bool use_accelerated) {
145 RefreshResources(framebuffer); 149 RefreshResources(framebuffer);
146 150
147 // Finish any pending renderpass 151 // Finish any pending renderpass
148 scheduler.RequestOutsideRenderPassOperationContext(); 152 scheduler.RequestOutsideRenderPassOperationContext();
149 153
150 if (const auto swapchain_images = swapchain.GetImageCount(); swapchain_images != image_count) {
151 image_count = swapchain_images;
152 Recreate();
153 }
154
155 const std::size_t image_index = swapchain.GetImageIndex();
156
157 scheduler.Wait(resource_ticks[image_index]); 154 scheduler.Wait(resource_ticks[image_index]);
158 resource_ticks[image_index] = scheduler.CurrentTick(); 155 resource_ticks[image_index] = scheduler.CurrentTick();
159 156
@@ -169,7 +166,7 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
169 std::memcpy(mapped_span.data(), &data, sizeof(data)); 166 std::memcpy(mapped_span.data(), &data, sizeof(data));
170 167
171 if (!use_accelerated) { 168 if (!use_accelerated) {
172 const u64 image_offset = GetRawImageOffset(framebuffer, image_index); 169 const u64 image_offset = GetRawImageOffset(framebuffer);
173 170
174 const VAddr framebuffer_addr = framebuffer.address + framebuffer.offset; 171 const VAddr framebuffer_addr = framebuffer.address + framebuffer.offset;
175 const u8* const host_ptr = cpu_memory.GetPointer(framebuffer_addr); 172 const u8* const host_ptr = cpu_memory.GetPointer(framebuffer_addr);
@@ -204,8 +201,8 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
204 .depth = 1, 201 .depth = 1,
205 }, 202 },
206 }; 203 };
207 scheduler.Record([this, copy, image_index](vk::CommandBuffer cmdbuf) { 204 scheduler.Record([this, copy, index = image_index](vk::CommandBuffer cmdbuf) {
208 const VkImage image = *raw_images[image_index]; 205 const VkImage image = *raw_images[index];
209 const VkImageMemoryBarrier base_barrier{ 206 const VkImageMemoryBarrier base_barrier{
210 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, 207 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
211 .pNext = nullptr, 208 .pNext = nullptr,
@@ -245,14 +242,15 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
245 242
246 const auto anti_alias_pass = Settings::values.anti_aliasing.GetValue(); 243 const auto anti_alias_pass = Settings::values.anti_aliasing.GetValue();
247 if (use_accelerated && anti_alias_pass == Settings::AntiAliasing::Fxaa) { 244 if (use_accelerated && anti_alias_pass == Settings::AntiAliasing::Fxaa) {
248 UpdateAADescriptorSet(image_index, source_image_view, false); 245 UpdateAADescriptorSet(source_image_view, false);
249 const u32 up_scale = Settings::values.resolution_info.up_scale; 246 const u32 up_scale = Settings::values.resolution_info.up_scale;
250 const u32 down_shift = Settings::values.resolution_info.down_shift; 247 const u32 down_shift = Settings::values.resolution_info.down_shift;
251 VkExtent2D size{ 248 VkExtent2D size{
252 .width = (up_scale * framebuffer.width) >> down_shift, 249 .width = (up_scale * framebuffer.width) >> down_shift,
253 .height = (up_scale * framebuffer.height) >> down_shift, 250 .height = (up_scale * framebuffer.height) >> down_shift,
254 }; 251 };
255 scheduler.Record([this, image_index, size, anti_alias_pass](vk::CommandBuffer cmdbuf) { 252 scheduler.Record([this, index = image_index, size,
253 anti_alias_pass](vk::CommandBuffer cmdbuf) {
256 const VkImageMemoryBarrier base_barrier{ 254 const VkImageMemoryBarrier base_barrier{
257 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, 255 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
258 .pNext = nullptr, 256 .pNext = nullptr,
@@ -326,7 +324,7 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
326 324
327 cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices)); 325 cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices));
328 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *aa_pipeline_layout, 0, 326 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *aa_pipeline_layout, 0,
329 aa_descriptor_sets[image_index], {}); 327 aa_descriptor_sets[index], {});
330 cmdbuf.Draw(4, 1, 0, 0); 328 cmdbuf.Draw(4, 1, 0, 0);
331 cmdbuf.EndRenderPass(); 329 cmdbuf.EndRenderPass();
332 330
@@ -369,81 +367,99 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
369 }; 367 };
370 VkImageView fsr_image_view = 368 VkImageView fsr_image_view =
371 fsr->Draw(scheduler, image_index, source_image_view, fsr_input_size, crop_rect); 369 fsr->Draw(scheduler, image_index, source_image_view, fsr_input_size, crop_rect);
372 UpdateDescriptorSet(image_index, fsr_image_view, true); 370 UpdateDescriptorSet(fsr_image_view, true);
373 } else { 371 } else {
374 const bool is_nn = 372 const bool is_nn =
375 Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::NearestNeighbor; 373 Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::NearestNeighbor;
376 UpdateDescriptorSet(image_index, source_image_view, is_nn); 374 UpdateDescriptorSet(source_image_view, is_nn);
377 } 375 }
378 376
379 scheduler.Record( 377 scheduler.Record([this, host_framebuffer, index = image_index,
380 [this, host_framebuffer, image_index, size = render_area](vk::CommandBuffer cmdbuf) { 378 size = render_area](vk::CommandBuffer cmdbuf) {
381 const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f; 379 const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f;
382 const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f; 380 const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f;
383 const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f; 381 const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f;
384 const VkClearValue clear_color{ 382 const VkClearValue clear_color{
385 .color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}}, 383 .color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}},
386 }; 384 };
387 const VkRenderPassBeginInfo renderpass_bi{ 385 const VkRenderPassBeginInfo renderpass_bi{
388 .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, 386 .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
389 .pNext = nullptr, 387 .pNext = nullptr,
390 .renderPass = *renderpass, 388 .renderPass = *renderpass,
391 .framebuffer = host_framebuffer, 389 .framebuffer = host_framebuffer,
392 .renderArea = 390 .renderArea =
393 { 391 {
394 .offset = {0, 0}, 392 .offset = {0, 0},
395 .extent = size, 393 .extent = size,
396 }, 394 },
397 .clearValueCount = 1, 395 .clearValueCount = 1,
398 .pClearValues = &clear_color, 396 .pClearValues = &clear_color,
399 }; 397 };
400 const VkViewport viewport{ 398 const VkViewport viewport{
401 .x = 0.0f, 399 .x = 0.0f,
402 .y = 0.0f, 400 .y = 0.0f,
403 .width = static_cast<float>(size.width), 401 .width = static_cast<float>(size.width),
404 .height = static_cast<float>(size.height), 402 .height = static_cast<float>(size.height),
405 .minDepth = 0.0f, 403 .minDepth = 0.0f,
406 .maxDepth = 1.0f, 404 .maxDepth = 1.0f,
407 }; 405 };
408 const VkRect2D scissor{ 406 const VkRect2D scissor{
409 .offset = {0, 0}, 407 .offset = {0, 0},
410 .extent = size, 408 .extent = size,
411 }; 409 };
412 cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); 410 cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
413 auto graphics_pipeline = [this]() { 411 auto graphics_pipeline = [this]() {
414 switch (Settings::values.scaling_filter.GetValue()) { 412 switch (Settings::values.scaling_filter.GetValue()) {
415 case Settings::ScalingFilter::NearestNeighbor: 413 case Settings::ScalingFilter::NearestNeighbor:
416 case Settings::ScalingFilter::Bilinear: 414 case Settings::ScalingFilter::Bilinear:
417 return *bilinear_pipeline; 415 return *bilinear_pipeline;
418 case Settings::ScalingFilter::Bicubic: 416 case Settings::ScalingFilter::Bicubic:
419 return *bicubic_pipeline; 417 return *bicubic_pipeline;
420 case Settings::ScalingFilter::Gaussian: 418 case Settings::ScalingFilter::Gaussian:
421 return *gaussian_pipeline; 419 return *gaussian_pipeline;
422 case Settings::ScalingFilter::ScaleForce: 420 case Settings::ScalingFilter::ScaleForce:
423 return *scaleforce_pipeline; 421 return *scaleforce_pipeline;
424 default: 422 default:
425 return *bilinear_pipeline; 423 return *bilinear_pipeline;
426 } 424 }
427 }(); 425 }();
428 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline); 426 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline);
429 cmdbuf.SetViewport(0, viewport); 427 cmdbuf.SetViewport(0, viewport);
430 cmdbuf.SetScissor(0, scissor); 428 cmdbuf.SetScissor(0, scissor);
431 429
432 cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices)); 430 cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices));
433 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0, 431 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0,
434 descriptor_sets[image_index], {}); 432 descriptor_sets[index], {});
435 cmdbuf.Draw(4, 1, 0, 0); 433 cmdbuf.Draw(4, 1, 0, 0);
436 cmdbuf.EndRenderPass(); 434 cmdbuf.EndRenderPass();
437 }); 435 });
438 return *semaphores[image_index];
439} 436}
440 437
441VkSemaphore BlitScreen::DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer, 438void BlitScreen::DrawToSwapchain(Frame* frame, const Tegra::FramebufferConfig& framebuffer,
442 bool use_accelerated) { 439 bool use_accelerated, bool is_srgb) {
443 const std::size_t image_index = swapchain.GetImageIndex(); 440 // Recreate dynamic resources if the the image count or colorspace changed
444 const VkExtent2D render_area = swapchain.GetSize(); 441 if (const std::size_t swapchain_images = swapchain.GetImageCount();
442 swapchain_images != image_count || current_srgb != is_srgb) {
443 current_srgb = is_srgb;
444 image_view_format = current_srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
445 image_count = swapchain_images;
446 Recreate();
447 }
448
449 // Recreate the presentation frame if the dimensions of the window changed
445 const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout(); 450 const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
446 return Draw(framebuffer, *framebuffers[image_index], layout, render_area, use_accelerated); 451 if (layout.width != frame->width || layout.height != frame->height ||
452 is_srgb != frame->is_srgb) {
453 scheduler.Finish();
454 present_manager.RecreateFrame(frame, layout.width, layout.height, is_srgb,
455 image_view_format, *renderpass);
456 }
457
458 const VkExtent2D render_area{frame->width, frame->height};
459 Draw(framebuffer, *frame->framebuffer, layout, render_area, use_accelerated);
460 if (++image_index >= image_count) {
461 image_index = 0;
462 }
447} 463}
448 464
449vk::Framebuffer BlitScreen::CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent) { 465vk::Framebuffer BlitScreen::CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent) {
@@ -471,13 +487,11 @@ void BlitScreen::CreateStaticResources() {
471} 487}
472 488
473void BlitScreen::CreateDynamicResources() { 489void BlitScreen::CreateDynamicResources() {
474 CreateSemaphores();
475 CreateDescriptorPool(); 490 CreateDescriptorPool();
476 CreateDescriptorSetLayout(); 491 CreateDescriptorSetLayout();
477 CreateDescriptorSets(); 492 CreateDescriptorSets();
478 CreatePipelineLayout(); 493 CreatePipelineLayout();
479 CreateRenderPass(); 494 CreateRenderPass();
480 CreateFramebuffers();
481 CreateGraphicsPipeline(); 495 CreateGraphicsPipeline();
482 fsr.reset(); 496 fsr.reset();
483 smaa.reset(); 497 smaa.reset();
@@ -525,11 +539,6 @@ void BlitScreen::CreateShaders() {
525 } 539 }
526} 540}
527 541
528void BlitScreen::CreateSemaphores() {
529 semaphores.resize(image_count);
530 std::ranges::generate(semaphores, [this] { return device.GetLogical().CreateSemaphore(); });
531}
532
533void BlitScreen::CreateDescriptorPool() { 542void BlitScreen::CreateDescriptorPool() {
534 const std::array<VkDescriptorPoolSize, 2> pool_sizes{{ 543 const std::array<VkDescriptorPoolSize, 2> pool_sizes{{
535 { 544 {
@@ -571,10 +580,10 @@ void BlitScreen::CreateDescriptorPool() {
571} 580}
572 581
573void BlitScreen::CreateRenderPass() { 582void BlitScreen::CreateRenderPass() {
574 renderpass = CreateRenderPassImpl(swapchain.GetImageViewFormat()); 583 renderpass = CreateRenderPassImpl(image_view_format);
575} 584}
576 585
577vk::RenderPass BlitScreen::CreateRenderPassImpl(VkFormat format, bool is_present) { 586vk::RenderPass BlitScreen::CreateRenderPassImpl(VkFormat format) {
578 const VkAttachmentDescription color_attachment{ 587 const VkAttachmentDescription color_attachment{
579 .flags = 0, 588 .flags = 0,
580 .format = format, 589 .format = format,
@@ -584,7 +593,7 @@ vk::RenderPass BlitScreen::CreateRenderPassImpl(VkFormat format, bool is_present
584 .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, 593 .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
585 .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, 594 .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
586 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, 595 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
587 .finalLayout = is_present ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_GENERAL, 596 .finalLayout = VK_IMAGE_LAYOUT_GENERAL,
588 }; 597 };
589 598
590 const VkAttachmentReference color_attachment_ref{ 599 const VkAttachmentReference color_attachment_ref{
@@ -1052,16 +1061,6 @@ void BlitScreen::CreateSampler() {
1052 nn_sampler = device.GetLogical().CreateSampler(ci_nn); 1061 nn_sampler = device.GetLogical().CreateSampler(ci_nn);
1053} 1062}
1054 1063
1055void BlitScreen::CreateFramebuffers() {
1056 const VkExtent2D size{swapchain.GetSize()};
1057 framebuffers.resize(image_count);
1058
1059 for (std::size_t i = 0; i < image_count; ++i) {
1060 const VkImageView image_view{swapchain.GetImageViewIndex(i)};
1061 framebuffers[i] = CreateFramebuffer(image_view, size, renderpass);
1062 }
1063}
1064
1065void BlitScreen::ReleaseRawImages() { 1064void BlitScreen::ReleaseRawImages() {
1066 for (const u64 tick : resource_ticks) { 1065 for (const u64 tick : resource_ticks) {
1067 scheduler.Wait(tick); 1066 scheduler.Wait(tick);
@@ -1175,7 +1174,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1175 aa_framebuffer = CreateFramebuffer(*aa_image_view, size, aa_renderpass); 1174 aa_framebuffer = CreateFramebuffer(*aa_image_view, size, aa_renderpass);
1176 return; 1175 return;
1177 } 1176 }
1178 aa_renderpass = CreateRenderPassImpl(GetFormat(framebuffer), false); 1177 aa_renderpass = CreateRenderPassImpl(GetFormat(framebuffer));
1179 aa_framebuffer = CreateFramebuffer(*aa_image_view, size, aa_renderpass); 1178 aa_framebuffer = CreateFramebuffer(*aa_image_view, size, aa_renderpass);
1180 1179
1181 const std::array<VkPipelineShaderStageCreateInfo, 2> fxaa_shader_stages{{ 1180 const std::array<VkPipelineShaderStageCreateInfo, 2> fxaa_shader_stages{{
@@ -1319,8 +1318,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
1319 aa_pipeline = device.GetLogical().CreateGraphicsPipeline(fxaa_pipeline_ci); 1318 aa_pipeline = device.GetLogical().CreateGraphicsPipeline(fxaa_pipeline_ci);
1320} 1319}
1321 1320
1322void BlitScreen::UpdateAADescriptorSet(std::size_t image_index, VkImageView image_view, 1321void BlitScreen::UpdateAADescriptorSet(VkImageView image_view, bool nn) const {
1323 bool nn) const {
1324 const VkDescriptorImageInfo image_info{ 1322 const VkDescriptorImageInfo image_info{
1325 .sampler = nn ? *nn_sampler : *sampler, 1323 .sampler = nn ? *nn_sampler : *sampler,
1326 .imageView = image_view, 1324 .imageView = image_view,
@@ -1356,8 +1354,7 @@ void BlitScreen::UpdateAADescriptorSet(std::size_t image_index, VkImageView imag
1356 device.GetLogical().UpdateDescriptorSets(std::array{sampler_write, sampler_write_2}, {}); 1354 device.GetLogical().UpdateDescriptorSets(std::array{sampler_write, sampler_write_2}, {});
1357} 1355}
1358 1356
1359void BlitScreen::UpdateDescriptorSet(std::size_t image_index, VkImageView image_view, 1357void BlitScreen::UpdateDescriptorSet(VkImageView image_view, bool nn) const {
1360 bool nn) const {
1361 const VkDescriptorBufferInfo buffer_info{ 1358 const VkDescriptorBufferInfo buffer_info{
1362 .buffer = *buffer, 1359 .buffer = *buffer,
1363 .offset = offsetof(BufferData, uniform), 1360 .offset = offsetof(BufferData, uniform),
@@ -1480,8 +1477,7 @@ u64 BlitScreen::CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer)
1480 return sizeof(BufferData) + GetSizeInBytes(framebuffer) * image_count; 1477 return sizeof(BufferData) + GetSizeInBytes(framebuffer) * image_count;
1481} 1478}
1482 1479
1483u64 BlitScreen::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, 1480u64 BlitScreen::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer) const {
1484 std::size_t image_index) const {
1485 constexpr auto first_image_offset = static_cast<u64>(sizeof(BufferData)); 1481 constexpr auto first_image_offset = static_cast<u64>(sizeof(BufferData));
1486 return first_image_offset + GetSizeInBytes(framebuffer) * image_index; 1482 return first_image_offset + GetSizeInBytes(framebuffer) * image_index;
1487} 1483}
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 @@
5 5
6#include <memory> 6#include <memory>
7 7
8#include "core/frontend/framebuffer_layout.h"
8#include "video_core/vulkan_common/vulkan_memory_allocator.h" 9#include "video_core/vulkan_common/vulkan_memory_allocator.h"
9#include "video_core/vulkan_common/vulkan_wrapper.h" 10#include "video_core/vulkan_common/vulkan_wrapper.h"
10 11
@@ -42,6 +43,9 @@ class RasterizerVulkan;
42class Scheduler; 43class Scheduler;
43class SMAA; 44class SMAA;
44class Swapchain; 45class Swapchain;
46class PresentManager;
47
48struct Frame;
45 49
46struct ScreenInfo { 50struct ScreenInfo {
47 VkImage image{}; 51 VkImage image{};
@@ -55,18 +59,17 @@ class BlitScreen {
55public: 59public:
56 explicit BlitScreen(Core::Memory::Memory& cpu_memory, Core::Frontend::EmuWindow& render_window, 60 explicit BlitScreen(Core::Memory::Memory& cpu_memory, Core::Frontend::EmuWindow& render_window,
57 const Device& device, MemoryAllocator& memory_manager, Swapchain& swapchain, 61 const Device& device, MemoryAllocator& memory_manager, Swapchain& swapchain,
58 Scheduler& scheduler, const ScreenInfo& screen_info); 62 PresentManager& present_manager, Scheduler& scheduler,
63 const ScreenInfo& screen_info);
59 ~BlitScreen(); 64 ~BlitScreen();
60 65
61 void Recreate(); 66 void Recreate();
62 67
63 [[nodiscard]] VkSemaphore Draw(const Tegra::FramebufferConfig& framebuffer, 68 void Draw(const Tegra::FramebufferConfig& framebuffer, const VkFramebuffer& host_framebuffer,
64 const VkFramebuffer& host_framebuffer, 69 const Layout::FramebufferLayout layout, VkExtent2D render_area, bool use_accelerated);
65 const Layout::FramebufferLayout layout, VkExtent2D render_area,
66 bool use_accelerated);
67 70
68 [[nodiscard]] VkSemaphore DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer, 71 void DrawToSwapchain(Frame* frame, const Tegra::FramebufferConfig& framebuffer,
69 bool use_accelerated); 72 bool use_accelerated, bool is_srgb);
70 73
71 [[nodiscard]] vk::Framebuffer CreateFramebuffer(const VkImageView& image_view, 74 [[nodiscard]] vk::Framebuffer CreateFramebuffer(const VkImageView& image_view,
72 VkExtent2D extent); 75 VkExtent2D extent);
@@ -79,10 +82,9 @@ private:
79 82
80 void CreateStaticResources(); 83 void CreateStaticResources();
81 void CreateShaders(); 84 void CreateShaders();
82 void CreateSemaphores();
83 void CreateDescriptorPool(); 85 void CreateDescriptorPool();
84 void CreateRenderPass(); 86 void CreateRenderPass();
85 vk::RenderPass CreateRenderPassImpl(VkFormat, bool is_present = true); 87 vk::RenderPass CreateRenderPassImpl(VkFormat format);
86 void CreateDescriptorSetLayout(); 88 void CreateDescriptorSetLayout();
87 void CreateDescriptorSets(); 89 void CreateDescriptorSets();
88 void CreatePipelineLayout(); 90 void CreatePipelineLayout();
@@ -90,15 +92,14 @@ private:
90 void CreateSampler(); 92 void CreateSampler();
91 93
92 void CreateDynamicResources(); 94 void CreateDynamicResources();
93 void CreateFramebuffers();
94 95
95 void RefreshResources(const Tegra::FramebufferConfig& framebuffer); 96 void RefreshResources(const Tegra::FramebufferConfig& framebuffer);
96 void ReleaseRawImages(); 97 void ReleaseRawImages();
97 void CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer); 98 void CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer);
98 void CreateRawImages(const Tegra::FramebufferConfig& framebuffer); 99 void CreateRawImages(const Tegra::FramebufferConfig& framebuffer);
99 100
100 void UpdateDescriptorSet(std::size_t image_index, VkImageView image_view, bool nn) const; 101 void UpdateDescriptorSet(VkImageView image_view, bool nn) const;
101 void UpdateAADescriptorSet(std::size_t image_index, VkImageView image_view, bool nn) const; 102 void UpdateAADescriptorSet(VkImageView image_view, bool nn) const;
102 void SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const; 103 void SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const;
103 void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer, 104 void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer,
104 const Layout::FramebufferLayout layout) const; 105 const Layout::FramebufferLayout layout) const;
@@ -107,16 +108,17 @@ private:
107 void CreateFSR(); 108 void CreateFSR();
108 109
109 u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const; 110 u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const;
110 u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, 111 u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer) const;
111 std::size_t image_index) const;
112 112
113 Core::Memory::Memory& cpu_memory; 113 Core::Memory::Memory& cpu_memory;
114 Core::Frontend::EmuWindow& render_window; 114 Core::Frontend::EmuWindow& render_window;
115 const Device& device; 115 const Device& device;
116 MemoryAllocator& memory_allocator; 116 MemoryAllocator& memory_allocator;
117 Swapchain& swapchain; 117 Swapchain& swapchain;
118 PresentManager& present_manager;
118 Scheduler& scheduler; 119 Scheduler& scheduler;
119 std::size_t image_count; 120 std::size_t image_count;
121 std::size_t image_index{};
120 const ScreenInfo& screen_info; 122 const ScreenInfo& screen_info;
121 123
122 vk::ShaderModule vertex_shader; 124 vk::ShaderModule vertex_shader;
@@ -135,7 +137,6 @@ private:
135 vk::Pipeline gaussian_pipeline; 137 vk::Pipeline gaussian_pipeline;
136 vk::Pipeline scaleforce_pipeline; 138 vk::Pipeline scaleforce_pipeline;
137 vk::RenderPass renderpass; 139 vk::RenderPass renderpass;
138 std::vector<vk::Framebuffer> framebuffers;
139 vk::DescriptorSets descriptor_sets; 140 vk::DescriptorSets descriptor_sets;
140 vk::Sampler nn_sampler; 141 vk::Sampler nn_sampler;
141 vk::Sampler sampler; 142 vk::Sampler sampler;
@@ -145,7 +146,6 @@ private:
145 146
146 std::vector<u64> resource_ticks; 147 std::vector<u64> resource_ticks;
147 148
148 std::vector<vk::Semaphore> semaphores;
149 std::vector<vk::Image> raw_images; 149 std::vector<vk::Image> raw_images;
150 std::vector<vk::ImageView> raw_image_views; 150 std::vector<vk::ImageView> raw_image_views;
151 std::vector<MemoryCommit> raw_buffer_commits; 151 std::vector<MemoryCommit> raw_buffer_commits;
@@ -164,6 +164,8 @@ private:
164 u32 raw_width = 0; 164 u32 raw_width = 0;
165 u32 raw_height = 0; 165 u32 raw_height = 0;
166 Service::android::PixelFormat pixel_format{}; 166 Service::android::PixelFormat pixel_format{};
167 bool current_srgb;
168 VkFormat image_view_format;
167 169
168 std::unique_ptr<FSR> fsr; 170 std::unique_ptr<FSR> fsr;
169 std::unique_ptr<SMAA> smaa; 171 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 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/microprofile.h"
5#include "common/thread.h"
6#include "video_core/renderer_vulkan/vk_present_manager.h"
7#include "video_core/renderer_vulkan/vk_scheduler.h"
8#include "video_core/renderer_vulkan/vk_swapchain.h"
9#include "video_core/vulkan_common/vulkan_device.h"
10
11namespace Vulkan {
12
13MICROPROFILE_DEFINE(Vulkan_WaitPresent, "Vulkan", "Wait For Present", MP_RGB(128, 128, 128));
14MICROPROFILE_DEFINE(Vulkan_CopyToSwapchain, "Vulkan", "Copy to swapchain", MP_RGB(192, 255, 192));
15
16namespace {
17
18bool CanBlitToSwapchain(const vk::PhysicalDevice& physical_device, VkFormat format) {
19 const VkFormatProperties props{physical_device.GetFormatProperties(format)};
20 return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT);
21}
22
23[[nodiscard]] VkImageSubresourceLayers MakeImageSubresourceLayers() {
24 return VkImageSubresourceLayers{
25 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
26 .mipLevel = 0,
27 .baseArrayLayer = 0,
28 .layerCount = 1,
29 };
30}
31
32[[nodiscard]] VkImageBlit MakeImageBlit(s32 frame_width, s32 frame_height, s32 swapchain_width,
33 s32 swapchain_height) {
34 return VkImageBlit{
35 .srcSubresource = MakeImageSubresourceLayers(),
36 .srcOffsets =
37 {
38 {
39 .x = 0,
40 .y = 0,
41 .z = 0,
42 },
43 {
44 .x = frame_width,
45 .y = frame_height,
46 .z = 1,
47 },
48 },
49 .dstSubresource = MakeImageSubresourceLayers(),
50 .dstOffsets =
51 {
52 {
53 .x = 0,
54 .y = 0,
55 .z = 0,
56 },
57 {
58 .x = swapchain_width,
59 .y = swapchain_height,
60 .z = 1,
61 },
62 },
63 };
64}
65
66[[nodiscard]] VkImageCopy MakeImageCopy(u32 frame_width, u32 frame_height, u32 swapchain_width,
67 u32 swapchain_height) {
68 return VkImageCopy{
69 .srcSubresource = MakeImageSubresourceLayers(),
70 .srcOffset =
71 {
72 .x = 0,
73 .y = 0,
74 .z = 0,
75 },
76 .dstSubresource = MakeImageSubresourceLayers(),
77 .dstOffset =
78 {
79 .x = 0,
80 .y = 0,
81 .z = 0,
82 },
83 .extent =
84 {
85 .width = std::min(frame_width, swapchain_width),
86 .height = std::min(frame_height, swapchain_height),
87 .depth = 1,
88 },
89 };
90}
91
92} // Anonymous namespace
93
94PresentManager::PresentManager(Core::Frontend::EmuWindow& render_window_, const Device& device_,
95 MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
96 Swapchain& swapchain_)
97 : render_window{render_window_}, device{device_},
98 memory_allocator{memory_allocator_}, scheduler{scheduler_}, swapchain{swapchain_},
99 blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())},
100 image_count{swapchain.GetImageCount()} {
101
102 auto& dld = device.GetLogical();
103 cmdpool = dld.CreateCommandPool({
104 .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
105 .pNext = nullptr,
106 .flags =
107 VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
108 .queueFamilyIndex = device.GetGraphicsFamily(),
109 });
110 auto cmdbuffers = cmdpool.Allocate(image_count);
111
112 frames.resize(image_count);
113 for (u32 i = 0; i < frames.size(); i++) {
114 Frame& frame = frames[i];
115 frame.cmdbuf = vk::CommandBuffer{cmdbuffers[i], device.GetDispatchLoader()};
116 frame.render_ready = dld.CreateSemaphore({
117 .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
118 .pNext = nullptr,
119 .flags = 0,
120 });
121 frame.present_done = dld.CreateFence({
122 .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
123 .pNext = nullptr,
124 .flags = VK_FENCE_CREATE_SIGNALED_BIT,
125 });
126 free_queue.push(&frame);
127 }
128
129 present_thread = std::jthread([this](std::stop_token token) { PresentThread(token); });
130}
131
132PresentManager::~PresentManager() = default;
133
134Frame* PresentManager::GetRenderFrame() {
135 MICROPROFILE_SCOPE(Vulkan_WaitPresent);
136
137 // Wait for free presentation frames
138 std::unique_lock lock{free_mutex};
139 free_cv.wait(lock, [this] { return !free_queue.empty(); });
140
141 // Take the frame from the queue
142 Frame* frame = free_queue.front();
143 free_queue.pop();
144
145 // Wait for the presentation to be finished so all frame resources are free
146 frame->present_done.Wait();
147 frame->present_done.Reset();
148
149 return frame;
150}
151
152void PresentManager::PushFrame(Frame* frame) {
153 std::unique_lock lock{queue_mutex};
154 present_queue.push(frame);
155 frame_cv.notify_one();
156}
157
158void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, bool is_srgb,
159 VkFormat image_view_format, VkRenderPass rd) {
160 auto& dld = device.GetLogical();
161
162 frame->width = width;
163 frame->height = height;
164 frame->is_srgb = is_srgb;
165
166 frame->image = dld.CreateImage({
167 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
168 .pNext = nullptr,
169 .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
170 .imageType = VK_IMAGE_TYPE_2D,
171 .format = swapchain.GetImageFormat(),
172 .extent =
173 {
174 .width = width,
175 .height = height,
176 .depth = 1,
177 },
178 .mipLevels = 1,
179 .arrayLayers = 1,
180 .samples = VK_SAMPLE_COUNT_1_BIT,
181 .tiling = VK_IMAGE_TILING_OPTIMAL,
182 .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
183 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
184 .queueFamilyIndexCount = 0,
185 .pQueueFamilyIndices = nullptr,
186 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
187 });
188
189 frame->image_commit = memory_allocator.Commit(frame->image, MemoryUsage::DeviceLocal);
190
191 frame->image_view = dld.CreateImageView({
192 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
193 .pNext = nullptr,
194 .flags = 0,
195 .image = *frame->image,
196 .viewType = VK_IMAGE_VIEW_TYPE_2D,
197 .format = image_view_format,
198 .components =
199 {
200 .r = VK_COMPONENT_SWIZZLE_IDENTITY,
201 .g = VK_COMPONENT_SWIZZLE_IDENTITY,
202 .b = VK_COMPONENT_SWIZZLE_IDENTITY,
203 .a = VK_COMPONENT_SWIZZLE_IDENTITY,
204 },
205 .subresourceRange =
206 {
207 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
208 .baseMipLevel = 0,
209 .levelCount = 1,
210 .baseArrayLayer = 0,
211 .layerCount = 1,
212 },
213 });
214
215 const VkImageView image_view{*frame->image_view};
216 frame->framebuffer = dld.CreateFramebuffer({
217 .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
218 .pNext = nullptr,
219 .flags = 0,
220 .renderPass = rd,
221 .attachmentCount = 1,
222 .pAttachments = &image_view,
223 .width = width,
224 .height = height,
225 .layers = 1,
226 });
227}
228
229void PresentManager::WaitPresent() {
230 // Wait for the present queue to be empty
231 {
232 std::unique_lock queue_lock{queue_mutex};
233 frame_cv.wait(queue_lock, [this] { return present_queue.empty(); });
234 }
235
236 // The above condition will be satisfied when the last frame is taken from the queue.
237 // To ensure that frame has been presented as well take hold of the swapchain
238 // mutex.
239 std::scoped_lock swapchain_lock{swapchain_mutex};
240}
241
242void PresentManager::PresentThread(std::stop_token token) {
243 Common::SetCurrentThreadName("VulkanPresent");
244 while (!token.stop_requested()) {
245 std::unique_lock lock{queue_mutex};
246
247 // Wait for presentation frames
248 Common::CondvarWait(frame_cv, lock, token, [this] { return !present_queue.empty(); });
249 if (token.stop_requested()) {
250 return;
251 }
252
253 // Take the frame and notify anyone waiting
254 Frame* frame = present_queue.front();
255 present_queue.pop();
256 frame_cv.notify_one();
257
258 // By exchanging the lock ownership we take the swapchain lock
259 // before the queue lock goes out of scope. This way the swapchain
260 // lock in WaitPresent is guaranteed to occur after here.
261 std::exchange(lock, std::unique_lock{swapchain_mutex});
262
263 CopyToSwapchain(frame);
264
265 // Free the frame for reuse
266 std::scoped_lock fl{free_mutex};
267 free_queue.push(frame);
268 free_cv.notify_one();
269 }
270}
271
272void PresentManager::CopyToSwapchain(Frame* frame) {
273 MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
274
275 const auto recreate_swapchain = [&] {
276 swapchain.Create(frame->width, frame->height, frame->is_srgb);
277 image_count = swapchain.GetImageCount();
278 };
279
280 // If the size or colorspace of the incoming frames has changed, recreate the swapchain
281 // to account for that.
282 const bool srgb_changed = swapchain.NeedsRecreation(frame->is_srgb);
283 const bool size_changed =
284 swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height;
285 if (srgb_changed || size_changed) {
286 recreate_swapchain();
287 }
288
289 while (swapchain.AcquireNextImage()) {
290 recreate_swapchain();
291 }
292
293 const vk::CommandBuffer cmdbuf{frame->cmdbuf};
294 cmdbuf.Begin({
295 .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
296 .pNext = nullptr,
297 .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
298 .pInheritanceInfo = nullptr,
299 });
300
301 const VkImage image{swapchain.CurrentImage()};
302 const VkExtent2D extent = swapchain.GetExtent();
303 const std::array pre_barriers{
304 VkImageMemoryBarrier{
305 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
306 .pNext = nullptr,
307 .srcAccessMask = 0,
308 .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
309 .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
310 .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
311 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
312 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
313 .image = image,
314 .subresourceRange{
315 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
316 .baseMipLevel = 0,
317 .levelCount = 1,
318 .baseArrayLayer = 0,
319 .layerCount = VK_REMAINING_ARRAY_LAYERS,
320 },
321 },
322 VkImageMemoryBarrier{
323 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
324 .pNext = nullptr,
325 .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
326 .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
327 .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
328 .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
329 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
330 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
331 .image = *frame->image,
332 .subresourceRange{
333 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
334 .baseMipLevel = 0,
335 .levelCount = 1,
336 .baseArrayLayer = 0,
337 .layerCount = VK_REMAINING_ARRAY_LAYERS,
338 },
339 },
340 };
341 const std::array post_barriers{
342 VkImageMemoryBarrier{
343 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
344 .pNext = nullptr,
345 .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
346 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
347 .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
348 .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
349 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
350 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
351 .image = image,
352 .subresourceRange{
353 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
354 .baseMipLevel = 0,
355 .levelCount = 1,
356 .baseArrayLayer = 0,
357 .layerCount = VK_REMAINING_ARRAY_LAYERS,
358 },
359 },
360 VkImageMemoryBarrier{
361 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
362 .pNext = nullptr,
363 .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
364 .dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
365 .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
366 .newLayout = VK_IMAGE_LAYOUT_GENERAL,
367 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
368 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
369 .image = *frame->image,
370 .subresourceRange{
371 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
372 .baseMipLevel = 0,
373 .levelCount = 1,
374 .baseArrayLayer = 0,
375 .layerCount = VK_REMAINING_ARRAY_LAYERS,
376 },
377 },
378 };
379
380 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, {},
381 {}, {}, pre_barriers);
382
383 if (blit_supported) {
384 cmdbuf.BlitImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image,
385 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
386 MakeImageBlit(frame->width, frame->height, extent.width, extent.height),
387 VK_FILTER_LINEAR);
388 } else {
389 cmdbuf.CopyImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image,
390 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
391 MakeImageCopy(frame->width, frame->height, extent.width, extent.height));
392 }
393
394 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, {},
395 {}, {}, post_barriers);
396
397 cmdbuf.End();
398
399 const VkSemaphore present_semaphore = swapchain.CurrentPresentSemaphore();
400 const VkSemaphore render_semaphore = swapchain.CurrentRenderSemaphore();
401 const std::array wait_semaphores = {present_semaphore, *frame->render_ready};
402
403 static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{
404 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
405 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
406 };
407
408 const VkSubmitInfo submit_info{
409 .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
410 .pNext = nullptr,
411 .waitSemaphoreCount = 2U,
412 .pWaitSemaphores = wait_semaphores.data(),
413 .pWaitDstStageMask = wait_stage_masks.data(),
414 .commandBufferCount = 1,
415 .pCommandBuffers = cmdbuf.address(),
416 .signalSemaphoreCount = 1U,
417 .pSignalSemaphores = &render_semaphore,
418 };
419
420 // Submit the image copy/blit to the swapchain
421 {
422 std::scoped_lock lock{scheduler.submit_mutex};
423 switch (const VkResult result =
424 device.GetGraphicsQueue().Submit(submit_info, *frame->present_done)) {
425 case VK_SUCCESS:
426 break;
427 case VK_ERROR_DEVICE_LOST:
428 device.ReportLoss();
429 [[fallthrough]];
430 default:
431 vk::Check(result);
432 break;
433 }
434 }
435
436 // Present
437 swapchain.Present(render_semaphore);
438}
439
440} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <condition_variable>
7#include <mutex>
8#include <queue>
9
10#include "common/common_types.h"
11#include "common/polyfill_thread.h"
12#include "video_core/vulkan_common/vulkan_memory_allocator.h"
13#include "video_core/vulkan_common/vulkan_wrapper.h"
14
15namespace Core::Frontend {
16class EmuWindow;
17} // namespace Core::Frontend
18
19namespace Vulkan {
20
21class Device;
22class Scheduler;
23class Swapchain;
24
25struct Frame {
26 u32 width;
27 u32 height;
28 bool is_srgb;
29 vk::Image image;
30 vk::ImageView image_view;
31 vk::Framebuffer framebuffer;
32 MemoryCommit image_commit;
33 vk::CommandBuffer cmdbuf;
34 vk::Semaphore render_ready;
35 vk::Fence present_done;
36};
37
38class PresentManager {
39public:
40 PresentManager(Core::Frontend::EmuWindow& render_window, const Device& device,
41 MemoryAllocator& memory_allocator, Scheduler& scheduler, Swapchain& swapchain);
42 ~PresentManager();
43
44 /// Returns the last used presentation frame
45 Frame* GetRenderFrame();
46
47 /// Pushes a frame for presentation
48 void PushFrame(Frame* frame);
49
50 /// Recreates the present frame to match the provided parameters
51 void RecreateFrame(Frame* frame, u32 width, u32 height, bool is_srgb,
52 VkFormat image_view_format, VkRenderPass rd);
53
54 /// Waits for the present thread to finish presenting all queued frames.
55 void WaitPresent();
56
57private:
58 void PresentThread(std::stop_token token);
59
60 void CopyToSwapchain(Frame* frame);
61
62private:
63 Core::Frontend::EmuWindow& render_window;
64 const Device& device;
65 MemoryAllocator& memory_allocator;
66 Scheduler& scheduler;
67 Swapchain& swapchain;
68 vk::CommandPool cmdpool;
69 std::vector<Frame> frames;
70 std::queue<Frame*> present_queue;
71 std::queue<Frame*> free_queue;
72 std::condition_variable_any frame_cv;
73 std::condition_variable free_cv;
74 std::mutex swapchain_mutex;
75 std::mutex queue_mutex;
76 std::mutex free_mutex;
77 std::jthread present_thread;
78 bool blit_supported{};
79 std::size_t image_count;
80};
81
82} // 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_)
46 46
47Scheduler::~Scheduler() = default; 47Scheduler::~Scheduler() = default;
48 48
49void Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { 49u64 Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
50 // When flushing, we only send data to the worker thread; no waiting is necessary. 50 // When flushing, we only send data to the worker thread; no waiting is necessary.
51 SubmitExecution(signal_semaphore, wait_semaphore); 51 const u64 signal_value = SubmitExecution(signal_semaphore, wait_semaphore);
52 AllocateNewContext(); 52 AllocateNewContext();
53 return signal_value;
53} 54}
54 55
55void Scheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { 56void Scheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
@@ -205,7 +206,7 @@ void Scheduler::AllocateWorkerCommandBuffer() {
205 }); 206 });
206} 207}
207 208
208void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { 209u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
209 EndPendingOperations(); 210 EndPendingOperations();
210 InvalidateState(); 211 InvalidateState();
211 212
@@ -217,6 +218,7 @@ void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_s
217 on_submit(); 218 on_submit();
218 } 219 }
219 220
221 std::scoped_lock lock{submit_mutex};
220 switch (const VkResult result = master_semaphore->SubmitQueue( 222 switch (const VkResult result = master_semaphore->SubmitQueue(
221 cmdbuf, signal_semaphore, wait_semaphore, signal_value)) { 223 cmdbuf, signal_semaphore, wait_semaphore, signal_value)) {
222 case VK_SUCCESS: 224 case VK_SUCCESS:
@@ -231,6 +233,7 @@ void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_s
231 }); 233 });
232 chunk->MarkSubmit(); 234 chunk->MarkSubmit();
233 DispatchWork(); 235 DispatchWork();
236 return signal_value;
234} 237}
235 238
236void Scheduler::AllocateNewContext() { 239void 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:
34 ~Scheduler(); 34 ~Scheduler();
35 35
36 /// Sends the current execution context to the GPU. 36 /// Sends the current execution context to the GPU.
37 void Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); 37 u64 Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
38 38
39 /// Sends the current execution context to the GPU and waits for it to complete. 39 /// Sends the current execution context to the GPU and waits for it to complete.
40 void Finish(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); 40 void Finish(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
@@ -106,6 +106,8 @@ public:
106 return *master_semaphore; 106 return *master_semaphore;
107 } 107 }
108 108
109 std::mutex submit_mutex;
110
109private: 111private:
110 class Command { 112 class Command {
111 public: 113 public:
@@ -201,7 +203,7 @@ private:
201 203
202 void AllocateWorkerCommandBuffer(); 204 void AllocateWorkerCommandBuffer();
203 205
204 void SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore); 206 u64 SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore);
205 207
206 void AllocateNewContext(); 208 void AllocateNewContext();
207 209
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) {
99 return; 99 return;
100 } 100 }
101 101
102 device.GetLogical().WaitIdle();
103 Destroy(); 102 Destroy();
104 103
105 CreateSwapchain(capabilities, srgb); 104 CreateSwapchain(capabilities, srgb);
106 CreateSemaphores(); 105 CreateSemaphores();
107 CreateImageViews();
108 106
109 resource_ticks.clear(); 107 resource_ticks.clear();
110 resource_ticks.resize(image_count); 108 resource_ticks.resize(image_count);
111} 109}
112 110
113void Swapchain::AcquireNextImage() { 111bool Swapchain::AcquireNextImage() {
114 const VkResult result = device.GetLogical().AcquireNextImageKHR( 112 const VkResult result = device.GetLogical().AcquireNextImageKHR(
115 *swapchain, std::numeric_limits<u64>::max(), *present_semaphores[frame_index], 113 *swapchain, std::numeric_limits<u64>::max(), *present_semaphores[frame_index],
116 VK_NULL_HANDLE, &image_index); 114 VK_NULL_HANDLE, &image_index);
@@ -127,8 +125,11 @@ void Swapchain::AcquireNextImage() {
127 LOG_ERROR(Render_Vulkan, "vkAcquireNextImageKHR returned {}", vk::ToString(result)); 125 LOG_ERROR(Render_Vulkan, "vkAcquireNextImageKHR returned {}", vk::ToString(result));
128 break; 126 break;
129 } 127 }
128
130 scheduler.Wait(resource_ticks[image_index]); 129 scheduler.Wait(resource_ticks[image_index]);
131 resource_ticks[image_index] = scheduler.CurrentTick(); 130 resource_ticks[image_index] = scheduler.CurrentTick();
131
132 return is_suboptimal || is_outdated;
132} 133}
133 134
134void Swapchain::Present(VkSemaphore render_semaphore) { 135void Swapchain::Present(VkSemaphore render_semaphore) {
@@ -143,6 +144,7 @@ void Swapchain::Present(VkSemaphore render_semaphore) {
143 .pImageIndices = &image_index, 144 .pImageIndices = &image_index,
144 .pResults = nullptr, 145 .pResults = nullptr,
145 }; 146 };
147 std::scoped_lock lock{scheduler.submit_mutex};
146 switch (const VkResult result = present_queue.Present(present_info)) { 148 switch (const VkResult result = present_queue.Present(present_info)) {
147 case VK_SUCCESS: 149 case VK_SUCCESS:
148 break; 150 break;
@@ -168,7 +170,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo
168 const auto present_modes{physical_device.GetSurfacePresentModesKHR(surface)}; 170 const auto present_modes{physical_device.GetSurfacePresentModesKHR(surface)};
169 171
170 const VkCompositeAlphaFlagBitsKHR alpha_flags{ChooseAlphaFlags(capabilities)}; 172 const VkCompositeAlphaFlagBitsKHR alpha_flags{ChooseAlphaFlags(capabilities)};
171 const VkSurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats)}; 173 surface_format = ChooseSwapSurfaceFormat(formats);
172 present_mode = ChooseSwapPresentMode(present_modes); 174 present_mode = ChooseSwapPresentMode(present_modes);
173 175
174 u32 requested_image_count{capabilities.minImageCount + 1}; 176 u32 requested_image_count{capabilities.minImageCount + 1};
@@ -193,7 +195,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo
193 .imageColorSpace = surface_format.colorSpace, 195 .imageColorSpace = surface_format.colorSpace,
194 .imageExtent = {}, 196 .imageExtent = {},
195 .imageArrayLayers = 1, 197 .imageArrayLayers = 1,
196 .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, 198 .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
197 .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, 199 .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
198 .queueFamilyIndexCount = 0, 200 .queueFamilyIndexCount = 0,
199 .pQueueFamilyIndices = nullptr, 201 .pQueueFamilyIndices = nullptr,
@@ -241,45 +243,14 @@ void Swapchain::CreateSemaphores() {
241 present_semaphores.resize(image_count); 243 present_semaphores.resize(image_count);
242 std::ranges::generate(present_semaphores, 244 std::ranges::generate(present_semaphores,
243 [this] { return device.GetLogical().CreateSemaphore(); }); 245 [this] { return device.GetLogical().CreateSemaphore(); });
244} 246 render_semaphores.resize(image_count);
245 247 std::ranges::generate(render_semaphores,
246void Swapchain::CreateImageViews() { 248 [this] { return device.GetLogical().CreateSemaphore(); });
247 VkImageViewCreateInfo ci{
248 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
249 .pNext = nullptr,
250 .flags = 0,
251 .image = {},
252 .viewType = VK_IMAGE_VIEW_TYPE_2D,
253 .format = image_view_format,
254 .components =
255 {
256 .r = VK_COMPONENT_SWIZZLE_IDENTITY,
257 .g = VK_COMPONENT_SWIZZLE_IDENTITY,
258 .b = VK_COMPONENT_SWIZZLE_IDENTITY,
259 .a = VK_COMPONENT_SWIZZLE_IDENTITY,
260 },
261 .subresourceRange =
262 {
263 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
264 .baseMipLevel = 0,
265 .levelCount = 1,
266 .baseArrayLayer = 0,
267 .layerCount = 1,
268 },
269 };
270
271 image_views.resize(image_count);
272 for (std::size_t i = 0; i < image_count; i++) {
273 ci.image = images[i];
274 image_views[i] = device.GetLogical().CreateImageView(ci);
275 }
276} 249}
277 250
278void Swapchain::Destroy() { 251void Swapchain::Destroy() {
279 frame_index = 0; 252 frame_index = 0;
280 present_semaphores.clear(); 253 present_semaphores.clear();
281 framebuffers.clear();
282 image_views.clear();
283 swapchain.reset(); 254 swapchain.reset();
284} 255}
285 256
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:
27 void Create(u32 width, u32 height, bool srgb); 27 void Create(u32 width, u32 height, bool srgb);
28 28
29 /// Acquires the next image in the swapchain, waits as needed. 29 /// Acquires the next image in the swapchain, waits as needed.
30 void AcquireNextImage(); 30 bool AcquireNextImage();
31 31
32 /// Presents the rendered image to the swapchain. 32 /// Presents the rendered image to the swapchain.
33 void Present(VkSemaphore render_semaphore); 33 void Present(VkSemaphore render_semaphore);
@@ -52,6 +52,11 @@ public:
52 return is_suboptimal; 52 return is_suboptimal;
53 } 53 }
54 54
55 /// Returns true when the swapchain format is in the srgb color space
56 bool IsSrgb() const {
57 return current_srgb;
58 }
59
55 VkExtent2D GetSize() const { 60 VkExtent2D GetSize() const {
56 return extent; 61 return extent;
57 } 62 }
@@ -64,22 +69,34 @@ public:
64 return image_index; 69 return image_index;
65 } 70 }
66 71
72 std::size_t GetFrameIndex() const {
73 return frame_index;
74 }
75
67 VkImage GetImageIndex(std::size_t index) const { 76 VkImage GetImageIndex(std::size_t index) const {
68 return images[index]; 77 return images[index];
69 } 78 }
70 79
71 VkImageView GetImageViewIndex(std::size_t index) const { 80 VkImage CurrentImage() const {
72 return *image_views[index]; 81 return images[image_index];
73 } 82 }
74 83
75 VkFormat GetImageViewFormat() const { 84 VkFormat GetImageViewFormat() const {
76 return image_view_format; 85 return image_view_format;
77 } 86 }
78 87
88 VkFormat GetImageFormat() const {
89 return surface_format.format;
90 }
91
79 VkSemaphore CurrentPresentSemaphore() const { 92 VkSemaphore CurrentPresentSemaphore() const {
80 return *present_semaphores[frame_index]; 93 return *present_semaphores[frame_index];
81 } 94 }
82 95
96 VkSemaphore CurrentRenderSemaphore() const {
97 return *render_semaphores[frame_index];
98 }
99
83 u32 GetWidth() const { 100 u32 GetWidth() const {
84 return width; 101 return width;
85 } 102 }
@@ -88,6 +105,10 @@ public:
88 return height; 105 return height;
89 } 106 }
90 107
108 VkExtent2D GetExtent() const {
109 return extent;
110 }
111
91private: 112private:
92 void CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bool srgb); 113 void CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bool srgb);
93 void CreateSemaphores(); 114 void CreateSemaphores();
@@ -107,10 +128,9 @@ private:
107 128
108 std::size_t image_count{}; 129 std::size_t image_count{};
109 std::vector<VkImage> images; 130 std::vector<VkImage> images;
110 std::vector<vk::ImageView> image_views;
111 std::vector<vk::Framebuffer> framebuffers;
112 std::vector<u64> resource_ticks; 131 std::vector<u64> resource_ticks;
113 std::vector<vk::Semaphore> present_semaphores; 132 std::vector<vk::Semaphore> present_semaphores;
133 std::vector<vk::Semaphore> render_semaphores;
114 134
115 u32 width; 135 u32 width;
116 u32 height; 136 u32 height;
@@ -121,6 +141,7 @@ private:
121 VkFormat image_view_format{}; 141 VkFormat image_view_format{};
122 VkExtent2D extent{}; 142 VkExtent2D extent{};
123 VkPresentModeKHR present_mode{}; 143 VkPresentModeKHR present_mode{};
144 VkSurfaceFormatKHR surface_format{};
124 145
125 bool current_srgb{}; 146 bool current_srgb{};
126 bool current_fps_unlocked{}; 147 bool current_fps_unlocked{};