diff options
Diffstat (limited to '')
| -rw-r--r-- | src/video_core/renderer_vulkan/renderer_vulkan.cpp | 75 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_blit_screen.cpp | 94 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_query_cache.cpp | 9 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_rasterizer.cpp | 7 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_scheduler.cpp | 120 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_scheduler.h | 15 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_swapchain.cpp | 39 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_swapchain.h | 23 |
8 files changed, 200 insertions, 182 deletions
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index bec3a81d9..7e39b65bd 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp | |||
| @@ -97,19 +97,14 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_, | |||
| 97 | Core::Frontend::EmuWindow& emu_window, | 97 | Core::Frontend::EmuWindow& emu_window, |
| 98 | Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_, | 98 | Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_, |
| 99 | std::unique_ptr<Core::Frontend::GraphicsContext> context_) try | 99 | std::unique_ptr<Core::Frontend::GraphicsContext> context_) try |
| 100 | : RendererBase(emu_window, std::move(context_)), | 100 | : RendererBase(emu_window, std::move(context_)), telemetry_session(telemetry_session_), |
| 101 | telemetry_session(telemetry_session_), | 101 | cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary()), |
| 102 | cpu_memory(cpu_memory_), | ||
| 103 | gpu(gpu_), | ||
| 104 | library(OpenLibrary()), | ||
| 105 | instance(CreateInstance(library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, | 102 | instance(CreateInstance(library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, |
| 106 | true, Settings::values.renderer_debug.GetValue())), | 103 | true, Settings::values.renderer_debug.GetValue())), |
| 107 | debug_callback(Settings::values.renderer_debug ? CreateDebugCallback(instance) : nullptr), | 104 | debug_callback(Settings::values.renderer_debug ? CreateDebugCallback(instance) : nullptr), |
| 108 | surface(CreateSurface(instance, render_window)), | 105 | surface(CreateSurface(instance, render_window)), |
| 109 | device(CreateDevice(instance, dld, *surface)), | 106 | device(CreateDevice(instance, dld, *surface)), memory_allocator(device, false), |
| 110 | memory_allocator(device, false), | 107 | state_tracker(gpu), scheduler(device, state_tracker), |
| 111 | state_tracker(gpu), | ||
| 112 | scheduler(device, state_tracker), | ||
| 113 | swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width, | 108 | swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width, |
| 114 | render_window.GetFramebufferLayout().height, false), | 109 | render_window.GetFramebufferLayout().height, false), |
| 115 | blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, scheduler, | 110 | blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, scheduler, |
| @@ -130,35 +125,47 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { | |||
| 130 | if (!framebuffer) { | 125 | if (!framebuffer) { |
| 131 | return; | 126 | return; |
| 132 | } | 127 | } |
| 133 | const auto& layout = render_window.GetFramebufferLayout(); | 128 | SCOPE_EXIT({ render_window.OnFrameDisplayed(); }); |
| 134 | if (layout.width > 0 && layout.height > 0 && render_window.IsShown()) { | 129 | if (!render_window.IsShown()) { |
| 135 | const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset; | 130 | return; |
| 136 | const bool use_accelerated = | 131 | } |
| 137 | rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); | 132 | const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset; |
| 138 | const bool is_srgb = use_accelerated && screen_info.is_srgb; | 133 | const bool use_accelerated = |
| 139 | if (swapchain.HasFramebufferChanged(layout) || swapchain.GetSrgbState() != is_srgb) { | 134 | rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); |
| 140 | swapchain.Create(layout.width, layout.height, is_srgb); | 135 | const bool is_srgb = use_accelerated && screen_info.is_srgb; |
| 141 | blit_screen.Recreate(); | 136 | |
| 142 | } | 137 | const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout(); |
| 143 | 138 | bool has_been_recreated = false; | |
| 144 | scheduler.WaitWorker(); | 139 | const auto recreate_swapchain = [&] { |
| 145 | 140 | if (!has_been_recreated) { | |
| 146 | while (!swapchain.AcquireNextImage()) { | 141 | has_been_recreated = true; |
| 147 | swapchain.Create(layout.width, layout.height, is_srgb); | 142 | scheduler.WaitWorker(); |
| 148 | blit_screen.Recreate(); | ||
| 149 | } | 143 | } |
| 150 | const VkSemaphore render_semaphore = blit_screen.Draw(*framebuffer, use_accelerated); | 144 | swapchain.Create(layout.width, layout.height, is_srgb); |
| 151 | 145 | }; | |
| 152 | scheduler.Flush(render_semaphore); | 146 | if (swapchain.NeedsRecreate() || |
| 153 | 147 | swapchain.HasDifferentLayout(layout.width, layout.height, is_srgb)) { | |
| 154 | if (swapchain.Present(render_semaphore)) { | 148 | recreate_swapchain(); |
| 155 | blit_screen.Recreate(); | 149 | } |
| 150 | bool needs_recreate; | ||
| 151 | do { | ||
| 152 | needs_recreate = false; | ||
| 153 | swapchain.AcquireNextImage(); | ||
| 154 | if (swapchain.NeedsRecreate()) { | ||
| 155 | recreate_swapchain(); | ||
| 156 | needs_recreate = true; | ||
| 156 | } | 157 | } |
| 157 | gpu.RendererFrameEndNotify(); | 158 | } while (needs_recreate); |
| 158 | rasterizer.TickFrame(); | 159 | if (has_been_recreated) { |
| 160 | blit_screen.Recreate(); | ||
| 159 | } | 161 | } |
| 162 | const VkSemaphore render_semaphore = blit_screen.Draw(*framebuffer, use_accelerated); | ||
| 163 | scheduler.Flush(render_semaphore); | ||
| 164 | scheduler.WaitWorker(); | ||
| 165 | swapchain.Present(render_semaphore); | ||
| 160 | 166 | ||
| 161 | render_window.OnFrameDisplayed(); | 167 | gpu.RendererFrameEndNotify(); |
| 168 | rasterizer.TickFrame(); | ||
| 162 | } | 169 | } |
| 163 | 170 | ||
| 164 | void RendererVulkan::Report() const { | 171 | void RendererVulkan::Report() const { |
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 363134129..516f428e7 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp | |||
| @@ -184,47 +184,43 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool | |||
| 184 | .depth = 1, | 184 | .depth = 1, |
| 185 | }, | 185 | }, |
| 186 | }; | 186 | }; |
| 187 | scheduler.Record( | 187 | scheduler.Record([this, copy, image_index](vk::CommandBuffer cmdbuf) { |
| 188 | [buffer = *buffer, image = *raw_images[image_index], copy](vk::CommandBuffer cmdbuf) { | 188 | const VkImage image = *raw_images[image_index]; |
| 189 | const VkImageMemoryBarrier base_barrier{ | 189 | const VkImageMemoryBarrier base_barrier{ |
| 190 | .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, | 190 | .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, |
| 191 | .pNext = nullptr, | 191 | .pNext = nullptr, |
| 192 | .srcAccessMask = 0, | 192 | .srcAccessMask = 0, |
| 193 | .dstAccessMask = 0, | 193 | .dstAccessMask = 0, |
| 194 | .oldLayout = VK_IMAGE_LAYOUT_GENERAL, | 194 | .oldLayout = VK_IMAGE_LAYOUT_GENERAL, |
| 195 | .newLayout = VK_IMAGE_LAYOUT_GENERAL, | 195 | .newLayout = VK_IMAGE_LAYOUT_GENERAL, |
| 196 | .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | 196 | .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
| 197 | .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | 197 | .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
| 198 | .image = image, | 198 | .image = image, |
| 199 | .subresourceRange = | 199 | .subresourceRange{ |
| 200 | { | 200 | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, |
| 201 | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, | 201 | .baseMipLevel = 0, |
| 202 | .baseMipLevel = 0, | 202 | .levelCount = 1, |
| 203 | .levelCount = 1, | 203 | .baseArrayLayer = 0, |
| 204 | .baseArrayLayer = 0, | 204 | .layerCount = 1, |
| 205 | .layerCount = 1, | 205 | }, |
| 206 | }, | 206 | }; |
| 207 | }; | 207 | VkImageMemoryBarrier read_barrier = base_barrier; |
| 208 | VkImageMemoryBarrier read_barrier = base_barrier; | 208 | read_barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; |
| 209 | read_barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; | 209 | read_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; |
| 210 | read_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; | 210 | read_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| 211 | read_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; | 211 | |
| 212 | 212 | VkImageMemoryBarrier write_barrier = base_barrier; | |
| 213 | VkImageMemoryBarrier write_barrier = base_barrier; | 213 | write_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; |
| 214 | write_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; | 214 | write_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; |
| 215 | write_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; | 215 | |
| 216 | 216 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, | |
| 217 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, | 217 | read_barrier); |
| 218 | 0, read_barrier); | 218 | cmdbuf.CopyBufferToImage(*buffer, image, VK_IMAGE_LAYOUT_GENERAL, copy); |
| 219 | cmdbuf.CopyBufferToImage(buffer, image, VK_IMAGE_LAYOUT_GENERAL, copy); | 219 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, |
| 220 | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, | 220 | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier); |
| 221 | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier); | 221 | }); |
| 222 | }); | ||
| 223 | } | 222 | } |
| 224 | scheduler.Record([renderpass = *renderpass, framebuffer = *framebuffers[image_index], | 223 | scheduler.Record([this, image_index, size = swapchain.GetSize()](vk::CommandBuffer cmdbuf) { |
| 225 | descriptor_set = descriptor_sets[image_index], buffer = *buffer, | ||
| 226 | size = swapchain.GetSize(), pipeline = *pipeline, | ||
| 227 | layout = *pipeline_layout](vk::CommandBuffer cmdbuf) { | ||
| 228 | const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f; | 224 | const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f; |
| 229 | const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f; | 225 | const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f; |
| 230 | const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f; | 226 | const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f; |
| @@ -234,8 +230,8 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool | |||
| 234 | const VkRenderPassBeginInfo renderpass_bi{ | 230 | const VkRenderPassBeginInfo renderpass_bi{ |
| 235 | .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, | 231 | .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, |
| 236 | .pNext = nullptr, | 232 | .pNext = nullptr, |
| 237 | .renderPass = renderpass, | 233 | .renderPass = *renderpass, |
| 238 | .framebuffer = framebuffer, | 234 | .framebuffer = *framebuffers[image_index], |
| 239 | .renderArea = | 235 | .renderArea = |
| 240 | { | 236 | { |
| 241 | .offset = {0, 0}, | 237 | .offset = {0, 0}, |
| @@ -257,12 +253,13 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool | |||
| 257 | .extent = size, | 253 | .extent = size, |
| 258 | }; | 254 | }; |
| 259 | cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); | 255 | cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); |
| 260 | cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); | 256 | cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); |
| 261 | cmdbuf.SetViewport(0, viewport); | 257 | cmdbuf.SetViewport(0, viewport); |
| 262 | cmdbuf.SetScissor(0, scissor); | 258 | cmdbuf.SetScissor(0, scissor); |
| 263 | 259 | ||
| 264 | cmdbuf.BindVertexBuffer(0, buffer, offsetof(BufferData, vertices)); | 260 | cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices)); |
| 265 | cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, {}); | 261 | cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0, |
| 262 | descriptor_sets[image_index], {}); | ||
| 266 | cmdbuf.Draw(4, 1, 0, 0); | 263 | cmdbuf.Draw(4, 1, 0, 0); |
| 267 | cmdbuf.EndRenderPass(); | 264 | cmdbuf.EndRenderPass(); |
| 268 | }); | 265 | }); |
| @@ -304,8 +301,7 @@ void VKBlitScreen::CreateShaders() { | |||
| 304 | 301 | ||
| 305 | void VKBlitScreen::CreateSemaphores() { | 302 | void VKBlitScreen::CreateSemaphores() { |
| 306 | semaphores.resize(image_count); | 303 | semaphores.resize(image_count); |
| 307 | std::generate(semaphores.begin(), semaphores.end(), | 304 | std::ranges::generate(semaphores, [this] { return device.GetLogical().CreateSemaphore(); }); |
| 308 | [this] { return device.GetLogical().CreateSemaphore(); }); | ||
| 309 | } | 305 | } |
| 310 | 306 | ||
| 311 | void VKBlitScreen::CreateDescriptorPool() { | 307 | void VKBlitScreen::CreateDescriptorPool() { |
| @@ -633,8 +629,8 @@ void VKBlitScreen::CreateFramebuffers() { | |||
| 633 | } | 629 | } |
| 634 | 630 | ||
| 635 | void VKBlitScreen::ReleaseRawImages() { | 631 | void VKBlitScreen::ReleaseRawImages() { |
| 636 | for (std::size_t i = 0; i < raw_images.size(); ++i) { | 632 | for (const u64 tick : resource_ticks) { |
| 637 | scheduler.Wait(resource_ticks.at(i)); | 633 | scheduler.Wait(tick); |
| 638 | } | 634 | } |
| 639 | raw_images.clear(); | 635 | raw_images.clear(); |
| 640 | raw_buffer_commits.clear(); | 636 | raw_buffer_commits.clear(); |
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 7cadd5147..1dd78328c 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp | |||
| @@ -114,10 +114,13 @@ void HostCounter::EndQuery() { | |||
| 114 | } | 114 | } |
| 115 | 115 | ||
| 116 | u64 HostCounter::BlockingQuery() const { | 116 | u64 HostCounter::BlockingQuery() const { |
| 117 | if (tick >= cache.GetScheduler().CurrentTick()) { | 117 | auto& scheduler{cache.GetScheduler()}; |
| 118 | cache.GetScheduler().Flush(); | 118 | if (tick >= scheduler.CurrentTick()) { |
| 119 | scheduler.Flush(); | ||
| 120 | // This may not be necessary, but it's better to play it safe and assume drivers don't | ||
| 121 | // support wait before signal on vkGetQueryPoolResults | ||
| 122 | scheduler.WaitWorker(); | ||
| 119 | } | 123 | } |
| 120 | |||
| 121 | u64 data; | 124 | u64 data; |
| 122 | const VkResult query_result = cache.GetDevice().GetLogical().GetQueryResults( | 125 | const VkResult query_result = cache.GetDevice().GetLogical().GetQueryResults( |
| 123 | query.first, query.second, 1, sizeof(data), &data, sizeof(data), | 126 | query.first, query.second, 1, sizeof(data), &data, sizeof(data), |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index fa6daeb3a..0f15ad2f7 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp | |||
| @@ -452,10 +452,11 @@ void RasterizerVulkan::TiledCacheBarrier() { | |||
| 452 | } | 452 | } |
| 453 | 453 | ||
| 454 | void RasterizerVulkan::FlushCommands() { | 454 | void RasterizerVulkan::FlushCommands() { |
| 455 | if (draw_counter > 0) { | 455 | if (draw_counter == 0) { |
| 456 | draw_counter = 0; | 456 | return; |
| 457 | scheduler.Flush(); | ||
| 458 | } | 457 | } |
| 458 | draw_counter = 0; | ||
| 459 | scheduler.Flush(); | ||
| 459 | } | 460 | } |
| 460 | 461 | ||
| 461 | void RasterizerVulkan::TickFrame() { | 462 | void RasterizerVulkan::TickFrame() { |
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 25a4933e5..81cb330d9 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp | |||
| @@ -31,7 +31,7 @@ void VKScheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) { | |||
| 31 | command->~Command(); | 31 | command->~Command(); |
| 32 | command = next; | 32 | command = next; |
| 33 | } | 33 | } |
| 34 | 34 | submit = false; | |
| 35 | command_offset = 0; | 35 | command_offset = 0; |
| 36 | first = nullptr; | 36 | first = nullptr; |
| 37 | last = nullptr; | 37 | last = nullptr; |
| @@ -42,7 +42,7 @@ VKScheduler::VKScheduler(const Device& device_, StateTracker& state_tracker_) | |||
| 42 | master_semaphore{std::make_unique<MasterSemaphore>(device)}, | 42 | master_semaphore{std::make_unique<MasterSemaphore>(device)}, |
| 43 | command_pool{std::make_unique<CommandPool>(*master_semaphore, device)} { | 43 | command_pool{std::make_unique<CommandPool>(*master_semaphore, device)} { |
| 44 | AcquireNewChunk(); | 44 | AcquireNewChunk(); |
| 45 | AllocateNewContext(); | 45 | AllocateWorkerCommandBuffer(); |
| 46 | worker_thread = std::thread(&VKScheduler::WorkerThread, this); | 46 | worker_thread = std::thread(&VKScheduler::WorkerThread, this); |
| 47 | } | 47 | } |
| 48 | 48 | ||
| @@ -60,6 +60,7 @@ void VKScheduler::Flush(VkSemaphore semaphore) { | |||
| 60 | void VKScheduler::Finish(VkSemaphore semaphore) { | 60 | void VKScheduler::Finish(VkSemaphore semaphore) { |
| 61 | const u64 presubmit_tick = CurrentTick(); | 61 | const u64 presubmit_tick = CurrentTick(); |
| 62 | SubmitExecution(semaphore); | 62 | SubmitExecution(semaphore); |
| 63 | WaitWorker(); | ||
| 63 | Wait(presubmit_tick); | 64 | Wait(presubmit_tick); |
| 64 | AllocateNewContext(); | 65 | AllocateNewContext(); |
| 65 | } | 66 | } |
| @@ -140,75 +141,82 @@ void VKScheduler::WorkerThread() { | |||
| 140 | if (quit) { | 141 | if (quit) { |
| 141 | continue; | 142 | continue; |
| 142 | } | 143 | } |
| 143 | auto extracted_chunk = std::move(chunk_queue.Front()); | 144 | while (!chunk_queue.Empty()) { |
| 144 | chunk_queue.Pop(); | 145 | auto extracted_chunk = std::move(chunk_queue.Front()); |
| 145 | extracted_chunk->ExecuteAll(current_cmdbuf); | 146 | chunk_queue.Pop(); |
| 146 | chunk_reserve.Push(std::move(extracted_chunk)); | 147 | const bool has_submit = extracted_chunk->HasSubmit(); |
| 148 | extracted_chunk->ExecuteAll(current_cmdbuf); | ||
| 149 | if (has_submit) { | ||
| 150 | AllocateWorkerCommandBuffer(); | ||
| 151 | } | ||
| 152 | chunk_reserve.Push(std::move(extracted_chunk)); | ||
| 153 | } | ||
| 147 | } while (!quit); | 154 | } while (!quit); |
| 148 | } | 155 | } |
| 149 | 156 | ||
| 157 | void VKScheduler::AllocateWorkerCommandBuffer() { | ||
| 158 | current_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader()); | ||
| 159 | current_cmdbuf.Begin({ | ||
| 160 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, | ||
| 161 | .pNext = nullptr, | ||
| 162 | .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, | ||
| 163 | .pInheritanceInfo = nullptr, | ||
| 164 | }); | ||
| 165 | } | ||
| 166 | |||
| 150 | void VKScheduler::SubmitExecution(VkSemaphore semaphore) { | 167 | void VKScheduler::SubmitExecution(VkSemaphore semaphore) { |
| 151 | EndPendingOperations(); | 168 | EndPendingOperations(); |
| 152 | InvalidateState(); | 169 | InvalidateState(); |
| 153 | WaitWorker(); | ||
| 154 | |||
| 155 | std::unique_lock lock{mutex}; | ||
| 156 | |||
| 157 | current_cmdbuf.End(); | ||
| 158 | |||
| 159 | const VkSemaphore timeline_semaphore = master_semaphore->Handle(); | ||
| 160 | const u32 num_signal_semaphores = semaphore ? 2U : 1U; | ||
| 161 | 170 | ||
| 162 | const u64 signal_value = master_semaphore->CurrentTick(); | 171 | const u64 signal_value = master_semaphore->CurrentTick(); |
| 163 | const u64 wait_value = signal_value - 1; | ||
| 164 | const VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; | ||
| 165 | |||
| 166 | master_semaphore->NextTick(); | 172 | master_semaphore->NextTick(); |
| 167 | 173 | ||
| 168 | const std::array signal_values{signal_value, u64(0)}; | 174 | Record([semaphore, signal_value, this](vk::CommandBuffer cmdbuf) { |
| 169 | const std::array signal_semaphores{timeline_semaphore, semaphore}; | 175 | cmdbuf.End(); |
| 170 | 176 | ||
| 171 | const VkTimelineSemaphoreSubmitInfoKHR timeline_si{ | 177 | const u32 num_signal_semaphores = semaphore ? 2U : 1U; |
| 172 | .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR, | ||
| 173 | .pNext = nullptr, | ||
| 174 | .waitSemaphoreValueCount = 1, | ||
| 175 | .pWaitSemaphoreValues = &wait_value, | ||
| 176 | .signalSemaphoreValueCount = num_signal_semaphores, | ||
| 177 | .pSignalSemaphoreValues = signal_values.data(), | ||
| 178 | }; | ||
| 179 | const VkSubmitInfo submit_info{ | ||
| 180 | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, | ||
| 181 | .pNext = &timeline_si, | ||
| 182 | .waitSemaphoreCount = 1, | ||
| 183 | .pWaitSemaphores = &timeline_semaphore, | ||
| 184 | .pWaitDstStageMask = &wait_stage_mask, | ||
| 185 | .commandBufferCount = 1, | ||
| 186 | .pCommandBuffers = current_cmdbuf.address(), | ||
| 187 | .signalSemaphoreCount = num_signal_semaphores, | ||
| 188 | .pSignalSemaphores = signal_semaphores.data(), | ||
| 189 | }; | ||
| 190 | switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info)) { | ||
| 191 | case VK_SUCCESS: | ||
| 192 | break; | ||
| 193 | case VK_ERROR_DEVICE_LOST: | ||
| 194 | device.ReportLoss(); | ||
| 195 | [[fallthrough]]; | ||
| 196 | default: | ||
| 197 | vk::Check(result); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | 178 | ||
| 201 | void VKScheduler::AllocateNewContext() { | 179 | const u64 wait_value = signal_value - 1; |
| 202 | std::unique_lock lock{mutex}; | 180 | const VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; |
| 203 | 181 | ||
| 204 | current_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader()); | 182 | const VkSemaphore timeline_semaphore = master_semaphore->Handle(); |
| 205 | current_cmdbuf.Begin({ | 183 | const std::array signal_values{signal_value, u64(0)}; |
| 206 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, | 184 | const std::array signal_semaphores{timeline_semaphore, semaphore}; |
| 207 | .pNext = nullptr, | 185 | |
| 208 | .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, | 186 | const VkTimelineSemaphoreSubmitInfoKHR timeline_si{ |
| 209 | .pInheritanceInfo = nullptr, | 187 | .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR, |
| 188 | .pNext = nullptr, | ||
| 189 | .waitSemaphoreValueCount = 1, | ||
| 190 | .pWaitSemaphoreValues = &wait_value, | ||
| 191 | .signalSemaphoreValueCount = num_signal_semaphores, | ||
| 192 | .pSignalSemaphoreValues = signal_values.data(), | ||
| 193 | }; | ||
| 194 | const VkSubmitInfo submit_info{ | ||
| 195 | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, | ||
| 196 | .pNext = &timeline_si, | ||
| 197 | .waitSemaphoreCount = 1, | ||
| 198 | .pWaitSemaphores = &timeline_semaphore, | ||
| 199 | .pWaitDstStageMask = &wait_stage_mask, | ||
| 200 | .commandBufferCount = 1, | ||
| 201 | .pCommandBuffers = cmdbuf.address(), | ||
| 202 | .signalSemaphoreCount = num_signal_semaphores, | ||
| 203 | .pSignalSemaphores = signal_semaphores.data(), | ||
| 204 | }; | ||
| 205 | switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info)) { | ||
| 206 | case VK_SUCCESS: | ||
| 207 | break; | ||
| 208 | case VK_ERROR_DEVICE_LOST: | ||
| 209 | device.ReportLoss(); | ||
| 210 | [[fallthrough]]; | ||
| 211 | default: | ||
| 212 | vk::Check(result); | ||
| 213 | } | ||
| 210 | }); | 214 | }); |
| 215 | chunk->MarkSubmit(); | ||
| 216 | DispatchWork(); | ||
| 217 | } | ||
| 211 | 218 | ||
| 219 | void VKScheduler::AllocateNewContext() { | ||
| 212 | // Enable counters once again. These are disabled when a command buffer is finished. | 220 | // Enable counters once again. These are disabled when a command buffer is finished. |
| 213 | if (query_cache) { | 221 | if (query_cache) { |
| 214 | query_cache->UpdateCounters(); | 222 | query_cache->UpdateCounters(); |
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index a40bb8bcd..40215c4c5 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h | |||
| @@ -86,6 +86,10 @@ public: | |||
| 86 | 86 | ||
| 87 | /// Waits for the given tick to trigger on the GPU. | 87 | /// Waits for the given tick to trigger on the GPU. |
| 88 | void Wait(u64 tick) { | 88 | void Wait(u64 tick) { |
| 89 | if (tick >= master_semaphore->CurrentTick()) { | ||
| 90 | // Make sure we are not waiting for the current tick without signalling | ||
| 91 | Flush(); | ||
| 92 | } | ||
| 89 | master_semaphore->Wait(tick); | 93 | master_semaphore->Wait(tick); |
| 90 | } | 94 | } |
| 91 | 95 | ||
| @@ -155,15 +159,24 @@ private: | |||
| 155 | return true; | 159 | return true; |
| 156 | } | 160 | } |
| 157 | 161 | ||
| 162 | void MarkSubmit() { | ||
| 163 | submit = true; | ||
| 164 | } | ||
| 165 | |||
| 158 | bool Empty() const { | 166 | bool Empty() const { |
| 159 | return command_offset == 0; | 167 | return command_offset == 0; |
| 160 | } | 168 | } |
| 161 | 169 | ||
| 170 | bool HasSubmit() const { | ||
| 171 | return submit; | ||
| 172 | } | ||
| 173 | |||
| 162 | private: | 174 | private: |
| 163 | Command* first = nullptr; | 175 | Command* first = nullptr; |
| 164 | Command* last = nullptr; | 176 | Command* last = nullptr; |
| 165 | 177 | ||
| 166 | size_t command_offset = 0; | 178 | size_t command_offset = 0; |
| 179 | bool submit = false; | ||
| 167 | alignas(std::max_align_t) std::array<u8, 0x8000> data{}; | 180 | alignas(std::max_align_t) std::array<u8, 0x8000> data{}; |
| 168 | }; | 181 | }; |
| 169 | 182 | ||
| @@ -176,6 +189,8 @@ private: | |||
| 176 | 189 | ||
| 177 | void WorkerThread(); | 190 | void WorkerThread(); |
| 178 | 191 | ||
| 192 | void AllocateWorkerCommandBuffer(); | ||
| 193 | |||
| 179 | void SubmitExecution(VkSemaphore semaphore); | 194 | void SubmitExecution(VkSemaphore semaphore); |
| 180 | 195 | ||
| 181 | void AllocateNewContext(); | 196 | void AllocateNewContext(); |
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index dfd5c65ba..a71b0b01e 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp | |||
| @@ -65,6 +65,8 @@ VKSwapchain::VKSwapchain(VkSurfaceKHR surface_, const Device& device_, VKSchedul | |||
| 65 | VKSwapchain::~VKSwapchain() = default; | 65 | VKSwapchain::~VKSwapchain() = default; |
| 66 | 66 | ||
| 67 | void VKSwapchain::Create(u32 width, u32 height, bool srgb) { | 67 | void VKSwapchain::Create(u32 width, u32 height, bool srgb) { |
| 68 | needs_recreate = false; | ||
| 69 | |||
| 68 | const auto physical_device = device.GetPhysical(); | 70 | const auto physical_device = device.GetPhysical(); |
| 69 | const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface)}; | 71 | const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface)}; |
| 70 | if (capabilities.maxImageExtent.width == 0 || capabilities.maxImageExtent.height == 0) { | 72 | if (capabilities.maxImageExtent.width == 0 || capabilities.maxImageExtent.height == 0) { |
| @@ -82,21 +84,20 @@ void VKSwapchain::Create(u32 width, u32 height, bool srgb) { | |||
| 82 | resource_ticks.resize(image_count); | 84 | resource_ticks.resize(image_count); |
| 83 | } | 85 | } |
| 84 | 86 | ||
| 85 | bool VKSwapchain::AcquireNextImage() { | 87 | void VKSwapchain::AcquireNextImage() { |
| 86 | const VkResult result = | 88 | const VkResult result = |
| 87 | device.GetLogical().AcquireNextImageKHR(*swapchain, std::numeric_limits<u64>::max(), | 89 | device.GetLogical().AcquireNextImageKHR(*swapchain, std::numeric_limits<u64>::max(), |
| 88 | *present_semaphores[frame_index], {}, &image_index); | 90 | *present_semaphores[frame_index], {}, &image_index); |
| 91 | needs_recreate |= result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR; | ||
| 89 | 92 | ||
| 90 | scheduler.Wait(resource_ticks[image_index]); | 93 | scheduler.Wait(resource_ticks[image_index]); |
| 91 | return result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR; | 94 | resource_ticks[image_index] = scheduler.CurrentTick(); |
| 92 | } | 95 | } |
| 93 | 96 | ||
| 94 | bool VKSwapchain::Present(VkSemaphore render_semaphore) { | 97 | void VKSwapchain::Present(VkSemaphore render_semaphore) { |
| 95 | const VkSemaphore present_semaphore{*present_semaphores[frame_index]}; | 98 | const VkSemaphore present_semaphore{*present_semaphores[frame_index]}; |
| 96 | const std::array<VkSemaphore, 2> semaphores{present_semaphore, render_semaphore}; | 99 | const std::array<VkSemaphore, 2> semaphores{present_semaphore, render_semaphore}; |
| 97 | const auto present_queue{device.GetPresentQueue()}; | 100 | const auto present_queue{device.GetPresentQueue()}; |
| 98 | bool recreated = false; | ||
| 99 | |||
| 100 | const VkPresentInfoKHR present_info{ | 101 | const VkPresentInfoKHR present_info{ |
| 101 | .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, | 102 | .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, |
| 102 | .pNext = nullptr, | 103 | .pNext = nullptr, |
| @@ -107,7 +108,6 @@ bool VKSwapchain::Present(VkSemaphore render_semaphore) { | |||
| 107 | .pImageIndices = &image_index, | 108 | .pImageIndices = &image_index, |
| 108 | .pResults = nullptr, | 109 | .pResults = nullptr, |
| 109 | }; | 110 | }; |
| 110 | |||
| 111 | switch (const VkResult result = present_queue.Present(present_info)) { | 111 | switch (const VkResult result = present_queue.Present(present_info)) { |
| 112 | case VK_SUCCESS: | 112 | case VK_SUCCESS: |
| 113 | break; | 113 | break; |
| @@ -115,24 +115,16 @@ bool VKSwapchain::Present(VkSemaphore render_semaphore) { | |||
| 115 | LOG_DEBUG(Render_Vulkan, "Suboptimal swapchain"); | 115 | LOG_DEBUG(Render_Vulkan, "Suboptimal swapchain"); |
| 116 | break; | 116 | break; |
| 117 | case VK_ERROR_OUT_OF_DATE_KHR: | 117 | case VK_ERROR_OUT_OF_DATE_KHR: |
| 118 | if (current_width > 0 && current_height > 0) { | 118 | needs_recreate = true; |
| 119 | Create(current_width, current_height, current_srgb); | ||
| 120 | recreated = true; | ||
| 121 | } | ||
| 122 | break; | 119 | break; |
| 123 | default: | 120 | default: |
| 124 | LOG_CRITICAL(Render_Vulkan, "Failed to present with error {}", vk::ToString(result)); | 121 | LOG_CRITICAL(Render_Vulkan, "Failed to present with error {}", vk::ToString(result)); |
| 125 | break; | 122 | break; |
| 126 | } | 123 | } |
| 127 | 124 | ++frame_index; | |
| 128 | resource_ticks[image_index] = scheduler.CurrentTick(); | 125 | if (frame_index >= image_count) { |
| 129 | frame_index = (frame_index + 1) % static_cast<u32>(image_count); | 126 | frame_index = 0; |
| 130 | return recreated; | 127 | } |
| 131 | } | ||
| 132 | |||
| 133 | bool VKSwapchain::HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const { | ||
| 134 | // TODO(Rodrigo): Handle framebuffer pixel format changes | ||
| 135 | return framebuffer.width != current_width || framebuffer.height != current_height; | ||
| 136 | } | 128 | } |
| 137 | 129 | ||
| 138 | void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, u32 width, | 130 | void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, u32 width, |
| @@ -148,7 +140,6 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, | |||
| 148 | if (capabilities.maxImageCount > 0 && requested_image_count > capabilities.maxImageCount) { | 140 | if (capabilities.maxImageCount > 0 && requested_image_count > capabilities.maxImageCount) { |
| 149 | requested_image_count = capabilities.maxImageCount; | 141 | requested_image_count = capabilities.maxImageCount; |
| 150 | } | 142 | } |
| 151 | |||
| 152 | VkSwapchainCreateInfoKHR swapchain_ci{ | 143 | VkSwapchainCreateInfoKHR swapchain_ci{ |
| 153 | .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, | 144 | .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, |
| 154 | .pNext = nullptr, | 145 | .pNext = nullptr, |
| @@ -169,7 +160,6 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, | |||
| 169 | .clipped = VK_FALSE, | 160 | .clipped = VK_FALSE, |
| 170 | .oldSwapchain = nullptr, | 161 | .oldSwapchain = nullptr, |
| 171 | }; | 162 | }; |
| 172 | |||
| 173 | const u32 graphics_family{device.GetGraphicsFamily()}; | 163 | const u32 graphics_family{device.GetGraphicsFamily()}; |
| 174 | const u32 present_family{device.GetPresentFamily()}; | 164 | const u32 present_family{device.GetPresentFamily()}; |
| 175 | const std::array<u32, 2> queue_indices{graphics_family, present_family}; | 165 | const std::array<u32, 2> queue_indices{graphics_family, present_family}; |
| @@ -178,7 +168,6 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, | |||
| 178 | swapchain_ci.queueFamilyIndexCount = static_cast<u32>(queue_indices.size()); | 168 | swapchain_ci.queueFamilyIndexCount = static_cast<u32>(queue_indices.size()); |
| 179 | swapchain_ci.pQueueFamilyIndices = queue_indices.data(); | 169 | swapchain_ci.pQueueFamilyIndices = queue_indices.data(); |
| 180 | } | 170 | } |
| 181 | |||
| 182 | // Request the size again to reduce the possibility of a TOCTOU race condition. | 171 | // Request the size again to reduce the possibility of a TOCTOU race condition. |
| 183 | const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface); | 172 | const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface); |
| 184 | swapchain_ci.imageExtent = ChooseSwapExtent(updated_capabilities, width, height); | 173 | swapchain_ci.imageExtent = ChooseSwapExtent(updated_capabilities, width, height); |
| @@ -186,8 +175,6 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, | |||
| 186 | swapchain = device.GetLogical().CreateSwapchainKHR(swapchain_ci); | 175 | swapchain = device.GetLogical().CreateSwapchainKHR(swapchain_ci); |
| 187 | 176 | ||
| 188 | extent = swapchain_ci.imageExtent; | 177 | extent = swapchain_ci.imageExtent; |
| 189 | current_width = extent.width; | ||
| 190 | current_height = extent.height; | ||
| 191 | current_srgb = srgb; | 178 | current_srgb = srgb; |
| 192 | 179 | ||
| 193 | images = swapchain.GetImages(); | 180 | images = swapchain.GetImages(); |
| @@ -197,8 +184,8 @@ void VKSwapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, | |||
| 197 | 184 | ||
| 198 | void VKSwapchain::CreateSemaphores() { | 185 | void VKSwapchain::CreateSemaphores() { |
| 199 | present_semaphores.resize(image_count); | 186 | present_semaphores.resize(image_count); |
| 200 | std::generate(present_semaphores.begin(), present_semaphores.end(), | 187 | std::ranges::generate(present_semaphores, |
| 201 | [this] { return device.GetLogical().CreateSemaphore(); }); | 188 | [this] { return device.GetLogical().CreateSemaphore(); }); |
| 202 | } | 189 | } |
| 203 | 190 | ||
| 204 | void VKSwapchain::CreateImageViews() { | 191 | void VKSwapchain::CreateImageViews() { |
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index adc8d27cf..b38fd9dc2 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h | |||
| @@ -28,14 +28,20 @@ public: | |||
| 28 | void Create(u32 width, u32 height, bool srgb); | 28 | void Create(u32 width, u32 height, bool srgb); |
| 29 | 29 | ||
| 30 | /// Acquires the next image in the swapchain, waits as needed. | 30 | /// Acquires the next image in the swapchain, waits as needed. |
| 31 | bool AcquireNextImage(); | 31 | void AcquireNextImage(); |
| 32 | 32 | ||
| 33 | /// Presents the rendered image to the swapchain. Returns true when the swapchains had to be | 33 | /// Presents the rendered image to the swapchain. |
| 34 | /// recreated. Takes responsability for the ownership of fence. | 34 | void Present(VkSemaphore render_semaphore); |
| 35 | bool Present(VkSemaphore render_semaphore); | ||
| 36 | 35 | ||
| 37 | /// Returns true when the framebuffer layout has changed. | 36 | /// Returns true when the framebuffer layout has changed. |
| 38 | bool HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const; | 37 | bool HasDifferentLayout(u32 width, u32 height, bool is_srgb) const { |
| 38 | return extent.width != width || extent.height != height || current_srgb != is_srgb; | ||
| 39 | } | ||
| 40 | |||
| 41 | /// Returns true when the image has to be recreated. | ||
| 42 | bool NeedsRecreate() const { | ||
| 43 | return needs_recreate; | ||
| 44 | } | ||
| 39 | 45 | ||
| 40 | VkExtent2D GetSize() const { | 46 | VkExtent2D GetSize() const { |
| 41 | return extent; | 47 | return extent; |
| @@ -61,10 +67,6 @@ public: | |||
| 61 | return image_format; | 67 | return image_format; |
| 62 | } | 68 | } |
| 63 | 69 | ||
| 64 | bool GetSrgbState() const { | ||
| 65 | return current_srgb; | ||
| 66 | } | ||
| 67 | |||
| 68 | private: | 70 | private: |
| 69 | void CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, u32 width, u32 height, | 71 | void CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, u32 width, u32 height, |
| 70 | bool srgb); | 72 | bool srgb); |
| @@ -92,9 +94,8 @@ private: | |||
| 92 | VkFormat image_format{}; | 94 | VkFormat image_format{}; |
| 93 | VkExtent2D extent{}; | 95 | VkExtent2D extent{}; |
| 94 | 96 | ||
| 95 | u32 current_width{}; | ||
| 96 | u32 current_height{}; | ||
| 97 | bool current_srgb{}; | 97 | bool current_srgb{}; |
| 98 | bool needs_recreate{}; | ||
| 98 | }; | 99 | }; |
| 99 | 100 | ||
| 100 | } // namespace Vulkan | 101 | } // namespace Vulkan |