diff options
| author | 2023-11-26 21:08:53 -0500 | |
|---|---|---|
| committer | 2023-11-26 21:08:53 -0500 | |
| commit | 1d11fe00a3000efbf6a0a4bb690e0d544a1b7b4a (patch) | |
| tree | c219aacab776c0a1e3956614b60a01fa2f6164cb /src/video_core/renderer_vulkan | |
| parent | shader_recompiler: Align SSBO offsets in GlobalMemory functions (diff) | |
| parent | Merge pull request #11535 from GPUCode/upload_cmdbuf (diff) | |
| download | yuzu-1d11fe00a3000efbf6a0a4bb690e0d544a1b7b4a.tar.gz yuzu-1d11fe00a3000efbf6a0a4bb690e0d544a1b7b4a.tar.xz yuzu-1d11fe00a3000efbf6a0a4bb690e0d544a1b7b4a.zip | |
Merge branch 'master' into ssbo-align
Diffstat (limited to 'src/video_core/renderer_vulkan')
18 files changed, 282 insertions, 130 deletions
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 7e7a80740..c4c30d807 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp | |||
| @@ -132,16 +132,12 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { | |||
| 132 | const bool use_accelerated = | 132 | const bool use_accelerated = |
| 133 | rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); | 133 | rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); |
| 134 | const bool is_srgb = use_accelerated && screen_info.is_srgb; | 134 | const bool is_srgb = use_accelerated && screen_info.is_srgb; |
| 135 | RenderScreenshot(*framebuffer, use_accelerated); | ||
| 135 | 136 | ||
| 136 | { | 137 | Frame* frame = present_manager.GetRenderFrame(); |
| 137 | std::scoped_lock lock{rasterizer.LockCaches()}; | 138 | blit_screen.DrawToSwapchain(frame, *framebuffer, use_accelerated, is_srgb); |
| 138 | RenderScreenshot(*framebuffer, use_accelerated); | 139 | scheduler.Flush(*frame->render_ready); |
| 139 | 140 | present_manager.Present(frame); | |
| 140 | Frame* frame = present_manager.GetRenderFrame(); | ||
| 141 | blit_screen.DrawToSwapchain(frame, *framebuffer, use_accelerated, is_srgb); | ||
| 142 | scheduler.Flush(*frame->render_ready); | ||
| 143 | present_manager.Present(frame); | ||
| 144 | } | ||
| 145 | 141 | ||
| 146 | gpu.RendererFrameEndNotify(); | 142 | gpu.RendererFrameEndNotify(); |
| 147 | rasterizer.TickFrame(); | 143 | rasterizer.TickFrame(); |
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 52fc142d1..66483a900 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp | |||
| @@ -137,6 +137,56 @@ BlitScreen::BlitScreen(Core::Memory::Memory& cpu_memory_, Core::Frontend::EmuWin | |||
| 137 | 137 | ||
| 138 | BlitScreen::~BlitScreen() = default; | 138 | BlitScreen::~BlitScreen() = default; |
| 139 | 139 | ||
| 140 | static Common::Rectangle<f32> NormalizeCrop(const Tegra::FramebufferConfig& framebuffer, | ||
| 141 | const ScreenInfo& screen_info) { | ||
| 142 | f32 left, top, right, bottom; | ||
| 143 | |||
| 144 | if (!framebuffer.crop_rect.IsEmpty()) { | ||
| 145 | // If crop rectangle is not empty, apply properties from rectangle. | ||
| 146 | left = static_cast<f32>(framebuffer.crop_rect.left); | ||
| 147 | top = static_cast<f32>(framebuffer.crop_rect.top); | ||
| 148 | right = static_cast<f32>(framebuffer.crop_rect.right); | ||
| 149 | bottom = static_cast<f32>(framebuffer.crop_rect.bottom); | ||
| 150 | } else { | ||
| 151 | // Otherwise, fall back to framebuffer dimensions. | ||
| 152 | left = 0; | ||
| 153 | top = 0; | ||
| 154 | right = static_cast<f32>(framebuffer.width); | ||
| 155 | bottom = static_cast<f32>(framebuffer.height); | ||
| 156 | } | ||
| 157 | |||
| 158 | // Apply transformation flags. | ||
| 159 | auto framebuffer_transform_flags = framebuffer.transform_flags; | ||
| 160 | |||
| 161 | if (True(framebuffer_transform_flags & Service::android::BufferTransformFlags::FlipH)) { | ||
| 162 | // Switch left and right. | ||
| 163 | std::swap(left, right); | ||
| 164 | } | ||
| 165 | if (True(framebuffer_transform_flags & Service::android::BufferTransformFlags::FlipV)) { | ||
| 166 | // Switch top and bottom. | ||
| 167 | std::swap(top, bottom); | ||
| 168 | } | ||
| 169 | |||
| 170 | framebuffer_transform_flags &= ~Service::android::BufferTransformFlags::FlipH; | ||
| 171 | framebuffer_transform_flags &= ~Service::android::BufferTransformFlags::FlipV; | ||
| 172 | if (True(framebuffer_transform_flags)) { | ||
| 173 | UNIMPLEMENTED_MSG("Unsupported framebuffer_transform_flags={}", | ||
| 174 | static_cast<u32>(framebuffer_transform_flags)); | ||
| 175 | } | ||
| 176 | |||
| 177 | // Get the screen properties. | ||
| 178 | const f32 screen_width = static_cast<f32>(screen_info.width); | ||
| 179 | const f32 screen_height = static_cast<f32>(screen_info.height); | ||
| 180 | |||
| 181 | // Normalize coordinate space. | ||
| 182 | left /= screen_width; | ||
| 183 | top /= screen_height; | ||
| 184 | right /= screen_width; | ||
| 185 | bottom /= screen_height; | ||
| 186 | |||
| 187 | return Common::Rectangle<f32>(left, top, right, bottom); | ||
| 188 | } | ||
| 189 | |||
| 140 | void BlitScreen::Recreate() { | 190 | void BlitScreen::Recreate() { |
| 141 | present_manager.WaitPresent(); | 191 | present_manager.WaitPresent(); |
| 142 | scheduler.Finish(); | 192 | scheduler.Finish(); |
| @@ -354,17 +404,10 @@ void BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, | |||
| 354 | source_image_view = smaa->Draw(scheduler, image_index, source_image, source_image_view); | 404 | source_image_view = smaa->Draw(scheduler, image_index, source_image, source_image_view); |
| 355 | } | 405 | } |
| 356 | if (fsr) { | 406 | if (fsr) { |
| 357 | auto crop_rect = framebuffer.crop_rect; | 407 | const auto crop_rect = NormalizeCrop(framebuffer, screen_info); |
| 358 | if (crop_rect.GetWidth() == 0) { | 408 | const VkExtent2D fsr_input_size{ |
| 359 | crop_rect.right = framebuffer.width; | 409 | .width = Settings::values.resolution_info.ScaleUp(screen_info.width), |
| 360 | } | 410 | .height = Settings::values.resolution_info.ScaleUp(screen_info.height), |
| 361 | if (crop_rect.GetHeight() == 0) { | ||
| 362 | crop_rect.bottom = framebuffer.height; | ||
| 363 | } | ||
| 364 | crop_rect = crop_rect.Scale(Settings::values.resolution_info.up_factor); | ||
| 365 | VkExtent2D fsr_input_size{ | ||
| 366 | .width = Settings::values.resolution_info.ScaleUp(framebuffer.width), | ||
| 367 | .height = Settings::values.resolution_info.ScaleUp(framebuffer.height), | ||
| 368 | }; | 411 | }; |
| 369 | VkImageView fsr_image_view = | 412 | VkImageView fsr_image_view = |
| 370 | fsr->Draw(scheduler, image_index, source_image_view, fsr_input_size, crop_rect); | 413 | fsr->Draw(scheduler, image_index, source_image_view, fsr_input_size, crop_rect); |
| @@ -1397,61 +1440,37 @@ void BlitScreen::SetUniformData(BufferData& data, const Layout::FramebufferLayou | |||
| 1397 | 1440 | ||
| 1398 | void BlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer, | 1441 | void BlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer, |
| 1399 | const Layout::FramebufferLayout layout) const { | 1442 | const Layout::FramebufferLayout layout) const { |
| 1400 | const auto& framebuffer_transform_flags = framebuffer.transform_flags; | 1443 | f32 left, top, right, bottom; |
| 1401 | const auto& framebuffer_crop_rect = framebuffer.crop_rect; | ||
| 1402 | |||
| 1403 | static constexpr Common::Rectangle<f32> texcoords{0.f, 0.f, 1.f, 1.f}; | ||
| 1404 | auto left = texcoords.left; | ||
| 1405 | auto right = texcoords.right; | ||
| 1406 | |||
| 1407 | switch (framebuffer_transform_flags) { | ||
| 1408 | case Service::android::BufferTransformFlags::Unset: | ||
| 1409 | break; | ||
| 1410 | case Service::android::BufferTransformFlags::FlipV: | ||
| 1411 | // Flip the framebuffer vertically | ||
| 1412 | left = texcoords.right; | ||
| 1413 | right = texcoords.left; | ||
| 1414 | break; | ||
| 1415 | default: | ||
| 1416 | UNIMPLEMENTED_MSG("Unsupported framebuffer_transform_flags={}", | ||
| 1417 | static_cast<u32>(framebuffer_transform_flags)); | ||
| 1418 | break; | ||
| 1419 | } | ||
| 1420 | 1444 | ||
| 1421 | UNIMPLEMENTED_IF(framebuffer_crop_rect.left != 0); | 1445 | if (fsr) { |
| 1422 | 1446 | // FSR has already applied the crop, so we just want to render the image | |
| 1423 | f32 left_start{}; | 1447 | // it has produced. |
| 1424 | if (framebuffer_crop_rect.Top() > 0) { | 1448 | left = 0; |
| 1425 | left_start = static_cast<f32>(framebuffer_crop_rect.Top()) / | 1449 | top = 0; |
| 1426 | static_cast<f32>(framebuffer_crop_rect.Bottom()); | 1450 | right = 1; |
| 1427 | } | 1451 | bottom = 1; |
| 1428 | f32 scale_u = static_cast<f32>(framebuffer.width) / static_cast<f32>(screen_info.width); | 1452 | } else { |
| 1429 | f32 scale_v = static_cast<f32>(framebuffer.height) / static_cast<f32>(screen_info.height); | 1453 | // Get the normalized crop rectangle. |
| 1430 | // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering | 1454 | const auto crop = NormalizeCrop(framebuffer, screen_info); |
| 1431 | // (e.g. handheld mode) on a 1920x1080 framebuffer. | 1455 | |
| 1432 | if (!fsr) { | 1456 | // Apply the crop. |
| 1433 | if (framebuffer_crop_rect.GetWidth() > 0) { | 1457 | left = crop.left; |
| 1434 | scale_u = static_cast<f32>(framebuffer_crop_rect.GetWidth()) / | 1458 | top = crop.top; |
| 1435 | static_cast<f32>(screen_info.width); | 1459 | right = crop.right; |
| 1436 | } | 1460 | bottom = crop.bottom; |
| 1437 | if (framebuffer_crop_rect.GetHeight() > 0) { | ||
| 1438 | scale_v = static_cast<f32>(framebuffer_crop_rect.GetHeight()) / | ||
| 1439 | static_cast<f32>(screen_info.height); | ||
| 1440 | } | ||
| 1441 | } | 1461 | } |
| 1442 | 1462 | ||
| 1463 | // Map the coordinates to the screen. | ||
| 1443 | const auto& screen = layout.screen; | 1464 | const auto& screen = layout.screen; |
| 1444 | const auto x = static_cast<f32>(screen.left); | 1465 | const auto x = static_cast<f32>(screen.left); |
| 1445 | const auto y = static_cast<f32>(screen.top); | 1466 | const auto y = static_cast<f32>(screen.top); |
| 1446 | const auto w = static_cast<f32>(screen.GetWidth()); | 1467 | const auto w = static_cast<f32>(screen.GetWidth()); |
| 1447 | const auto h = static_cast<f32>(screen.GetHeight()); | 1468 | const auto h = static_cast<f32>(screen.GetHeight()); |
| 1448 | data.vertices[0] = ScreenRectVertex(x, y, texcoords.top * scale_u, left_start + left * scale_v); | 1469 | |
| 1449 | data.vertices[1] = | 1470 | data.vertices[0] = ScreenRectVertex(x, y, left, top); |
| 1450 | ScreenRectVertex(x + w, y, texcoords.bottom * scale_u, left_start + left * scale_v); | 1471 | data.vertices[1] = ScreenRectVertex(x + w, y, right, top); |
| 1451 | data.vertices[2] = | 1472 | data.vertices[2] = ScreenRectVertex(x, y + h, left, bottom); |
| 1452 | ScreenRectVertex(x, y + h, texcoords.top * scale_u, left_start + right * scale_v); | 1473 | data.vertices[3] = ScreenRectVertex(x + w, y + h, right, bottom); |
| 1453 | data.vertices[3] = | ||
| 1454 | ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, left_start + right * scale_v); | ||
| 1455 | } | 1474 | } |
| 1456 | 1475 | ||
| 1457 | void BlitScreen::CreateSMAA(VkExtent2D smaa_size) { | 1476 | void BlitScreen::CreateSMAA(VkExtent2D smaa_size) { |
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 976c3f6a6..5958f52f7 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp | |||
| @@ -79,13 +79,13 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo | |||
| 79 | } // Anonymous namespace | 79 | } // Anonymous namespace |
| 80 | 80 | ||
| 81 | Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params) | 81 | Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params) |
| 82 | : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params) {} | 82 | : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params), tracker{4096} {} |
| 83 | 83 | ||
| 84 | Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_, | 84 | Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_, |
| 85 | VAddr cpu_addr_, u64 size_bytes_) | 85 | VAddr cpu_addr_, u64 size_bytes_) |
| 86 | : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_), | 86 | : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_), |
| 87 | device{&runtime.device}, buffer{ | 87 | device{&runtime.device}, buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())}, |
| 88 | CreateBuffer(*device, runtime.memory_allocator, SizeBytes())} { | 88 | tracker{SizeBytes()} { |
| 89 | if (runtime.device.HasDebuggingToolAttached()) { | 89 | if (runtime.device.HasDebuggingToolAttached()) { |
| 90 | buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str()); | 90 | buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str()); |
| 91 | } | 91 | } |
| @@ -359,12 +359,31 @@ u32 BufferCacheRuntime::GetStorageBufferAlignment() const { | |||
| 359 | return static_cast<u32>(device.GetStorageBufferAlignment()); | 359 | return static_cast<u32>(device.GetStorageBufferAlignment()); |
| 360 | } | 360 | } |
| 361 | 361 | ||
| 362 | void BufferCacheRuntime::TickFrame(VideoCommon::SlotVector<Buffer>& slot_buffers) noexcept { | ||
| 363 | for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) { | ||
| 364 | it->ResetUsageTracking(); | ||
| 365 | } | ||
| 366 | } | ||
| 367 | |||
| 362 | void BufferCacheRuntime::Finish() { | 368 | void BufferCacheRuntime::Finish() { |
| 363 | scheduler.Finish(); | 369 | scheduler.Finish(); |
| 364 | } | 370 | } |
| 365 | 371 | ||
| 372 | bool BufferCacheRuntime::CanReorderUpload(const Buffer& buffer, | ||
| 373 | std::span<const VideoCommon::BufferCopy> copies) { | ||
| 374 | if (Settings::values.disable_buffer_reorder) { | ||
| 375 | return false; | ||
| 376 | } | ||
| 377 | const bool can_use_upload_cmdbuf = | ||
| 378 | std::ranges::all_of(copies, [&](const VideoCommon::BufferCopy& copy) { | ||
| 379 | return !buffer.IsRegionUsed(copy.dst_offset, copy.size); | ||
| 380 | }); | ||
| 381 | return can_use_upload_cmdbuf; | ||
| 382 | } | ||
| 383 | |||
| 366 | void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer, | 384 | void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer, |
| 367 | std::span<const VideoCommon::BufferCopy> copies, bool barrier) { | 385 | std::span<const VideoCommon::BufferCopy> copies, bool barrier, |
| 386 | bool can_reorder_upload) { | ||
| 368 | if (dst_buffer == VK_NULL_HANDLE || src_buffer == VK_NULL_HANDLE) { | 387 | if (dst_buffer == VK_NULL_HANDLE || src_buffer == VK_NULL_HANDLE) { |
| 369 | return; | 388 | return; |
| 370 | } | 389 | } |
| @@ -380,9 +399,18 @@ void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer, | |||
| 380 | .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, | 399 | .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, |
| 381 | .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, | 400 | .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, |
| 382 | }; | 401 | }; |
| 402 | |||
| 383 | // Measuring a popular game, this number never exceeds the specified size once data is warmed up | 403 | // Measuring a popular game, this number never exceeds the specified size once data is warmed up |
| 384 | boost::container::small_vector<VkBufferCopy, 8> vk_copies(copies.size()); | 404 | boost::container::small_vector<VkBufferCopy, 8> vk_copies(copies.size()); |
| 385 | std::ranges::transform(copies, vk_copies.begin(), MakeBufferCopy); | 405 | std::ranges::transform(copies, vk_copies.begin(), MakeBufferCopy); |
| 406 | if (src_buffer == staging_pool.StreamBuf() && can_reorder_upload) { | ||
| 407 | scheduler.RecordWithUploadBuffer([src_buffer, dst_buffer, vk_copies]( | ||
| 408 | vk::CommandBuffer, vk::CommandBuffer upload_cmdbuf) { | ||
| 409 | upload_cmdbuf.CopyBuffer(src_buffer, dst_buffer, vk_copies); | ||
| 410 | }); | ||
| 411 | return; | ||
| 412 | } | ||
| 413 | |||
| 386 | scheduler.RequestOutsideRenderPassOperationContext(); | 414 | scheduler.RequestOutsideRenderPassOperationContext(); |
| 387 | scheduler.Record([src_buffer, dst_buffer, vk_copies, barrier](vk::CommandBuffer cmdbuf) { | 415 | scheduler.Record([src_buffer, dst_buffer, vk_copies, barrier](vk::CommandBuffer cmdbuf) { |
| 388 | if (barrier) { | 416 | if (barrier) { |
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 833dfac45..0b3fbd6d0 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | #include "video_core/buffer_cache/buffer_cache_base.h" | 6 | #include "video_core/buffer_cache/buffer_cache_base.h" |
| 7 | #include "video_core/buffer_cache/memory_tracker_base.h" | 7 | #include "video_core/buffer_cache/memory_tracker_base.h" |
| 8 | #include "video_core/buffer_cache/usage_tracker.h" | ||
| 8 | #include "video_core/engines/maxwell_3d.h" | 9 | #include "video_core/engines/maxwell_3d.h" |
| 9 | #include "video_core/renderer_vulkan/vk_compute_pass.h" | 10 | #include "video_core/renderer_vulkan/vk_compute_pass.h" |
| 10 | #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" | 11 | #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" |
| @@ -34,6 +35,18 @@ public: | |||
| 34 | return *buffer; | 35 | return *buffer; |
| 35 | } | 36 | } |
| 36 | 37 | ||
| 38 | [[nodiscard]] bool IsRegionUsed(u64 offset, u64 size) const noexcept { | ||
| 39 | return tracker.IsUsed(offset, size); | ||
| 40 | } | ||
| 41 | |||
| 42 | void MarkUsage(u64 offset, u64 size) noexcept { | ||
| 43 | tracker.Track(offset, size); | ||
| 44 | } | ||
| 45 | |||
| 46 | void ResetUsageTracking() noexcept { | ||
| 47 | tracker.Reset(); | ||
| 48 | } | ||
| 49 | |||
| 37 | operator VkBuffer() const noexcept { | 50 | operator VkBuffer() const noexcept { |
| 38 | return *buffer; | 51 | return *buffer; |
| 39 | } | 52 | } |
| @@ -49,6 +62,7 @@ private: | |||
| 49 | const Device* device{}; | 62 | const Device* device{}; |
| 50 | vk::Buffer buffer; | 63 | vk::Buffer buffer; |
| 51 | std::vector<BufferView> views; | 64 | std::vector<BufferView> views; |
| 65 | VideoCommon::UsageTracker tracker; | ||
| 52 | }; | 66 | }; |
| 53 | 67 | ||
| 54 | class QuadArrayIndexBuffer; | 68 | class QuadArrayIndexBuffer; |
| @@ -67,6 +81,8 @@ public: | |||
| 67 | ComputePassDescriptorQueue& compute_pass_descriptor_queue, | 81 | ComputePassDescriptorQueue& compute_pass_descriptor_queue, |
| 68 | DescriptorPool& descriptor_pool); | 82 | DescriptorPool& descriptor_pool); |
| 69 | 83 | ||
| 84 | void TickFrame(VideoCommon::SlotVector<Buffer>& slot_buffers) noexcept; | ||
| 85 | |||
| 70 | void Finish(); | 86 | void Finish(); |
| 71 | 87 | ||
| 72 | u64 GetDeviceLocalMemory() const; | 88 | u64 GetDeviceLocalMemory() const; |
| @@ -81,12 +97,15 @@ public: | |||
| 81 | 97 | ||
| 82 | [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size, bool deferred = false); | 98 | [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size, bool deferred = false); |
| 83 | 99 | ||
| 100 | bool CanReorderUpload(const Buffer& buffer, std::span<const VideoCommon::BufferCopy> copies); | ||
| 101 | |||
| 84 | void FreeDeferredStagingBuffer(StagingBufferRef& ref); | 102 | void FreeDeferredStagingBuffer(StagingBufferRef& ref); |
| 85 | 103 | ||
| 86 | void PreCopyBarrier(); | 104 | void PreCopyBarrier(); |
| 87 | 105 | ||
| 88 | void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer, | 106 | void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer, |
| 89 | std::span<const VideoCommon::BufferCopy> copies, bool barrier = true); | 107 | std::span<const VideoCommon::BufferCopy> copies, bool barrier, |
| 108 | bool can_reorder_upload = false); | ||
| 90 | 109 | ||
| 91 | void PostCopyBarrier(); | 110 | void PostCopyBarrier(); |
| 92 | 111 | ||
diff --git a/src/video_core/renderer_vulkan/vk_fsr.cpp b/src/video_core/renderer_vulkan/vk_fsr.cpp index ce8f3f3c2..f7a05fbc0 100644 --- a/src/video_core/renderer_vulkan/vk_fsr.cpp +++ b/src/video_core/renderer_vulkan/vk_fsr.cpp | |||
| @@ -34,7 +34,7 @@ FSR::FSR(const Device& device_, MemoryAllocator& memory_allocator_, size_t image | |||
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImageView image_view, | 36 | VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImageView image_view, |
| 37 | VkExtent2D input_image_extent, const Common::Rectangle<int>& crop_rect) { | 37 | VkExtent2D input_image_extent, const Common::Rectangle<f32>& crop_rect) { |
| 38 | 38 | ||
| 39 | UpdateDescriptorSet(image_index, image_view); | 39 | UpdateDescriptorSet(image_index, image_view); |
| 40 | 40 | ||
| @@ -61,15 +61,21 @@ VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImageView imag | |||
| 61 | 61 | ||
| 62 | cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *easu_pipeline); | 62 | cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *easu_pipeline); |
| 63 | 63 | ||
| 64 | const f32 input_image_width = static_cast<f32>(input_image_extent.width); | ||
| 65 | const f32 input_image_height = static_cast<f32>(input_image_extent.height); | ||
| 66 | const f32 output_image_width = static_cast<f32>(output_size.width); | ||
| 67 | const f32 output_image_height = static_cast<f32>(output_size.height); | ||
| 68 | const f32 viewport_width = (crop_rect.right - crop_rect.left) * input_image_width; | ||
| 69 | const f32 viewport_x = crop_rect.left * input_image_width; | ||
| 70 | const f32 viewport_height = (crop_rect.bottom - crop_rect.top) * input_image_height; | ||
| 71 | const f32 viewport_y = crop_rect.top * input_image_height; | ||
| 72 | |||
| 64 | std::array<u32, 4 * 4> push_constants; | 73 | std::array<u32, 4 * 4> push_constants; |
| 65 | FsrEasuConOffset( | 74 | FsrEasuConOffset(push_constants.data() + 0, push_constants.data() + 4, |
| 66 | push_constants.data() + 0, push_constants.data() + 4, push_constants.data() + 8, | 75 | push_constants.data() + 8, push_constants.data() + 12, |
| 67 | push_constants.data() + 12, | 76 | |
| 68 | 77 | viewport_width, viewport_height, input_image_width, input_image_height, | |
| 69 | static_cast<f32>(crop_rect.GetWidth()), static_cast<f32>(crop_rect.GetHeight()), | 78 | output_image_width, output_image_height, viewport_x, viewport_y); |
| 70 | static_cast<f32>(input_image_extent.width), static_cast<f32>(input_image_extent.height), | ||
| 71 | static_cast<f32>(output_size.width), static_cast<f32>(output_size.height), | ||
| 72 | static_cast<f32>(crop_rect.left), static_cast<f32>(crop_rect.top)); | ||
| 73 | cmdbuf.PushConstants(*pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, push_constants); | 79 | cmdbuf.PushConstants(*pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, push_constants); |
| 74 | 80 | ||
| 75 | { | 81 | { |
diff --git a/src/video_core/renderer_vulkan/vk_fsr.h b/src/video_core/renderer_vulkan/vk_fsr.h index 8bb9fc23a..3505c1416 100644 --- a/src/video_core/renderer_vulkan/vk_fsr.h +++ b/src/video_core/renderer_vulkan/vk_fsr.h | |||
| @@ -17,7 +17,7 @@ public: | |||
| 17 | explicit FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, | 17 | explicit FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, |
| 18 | VkExtent2D output_size); | 18 | VkExtent2D output_size); |
| 19 | VkImageView Draw(Scheduler& scheduler, size_t image_index, VkImageView image_view, | 19 | VkImageView Draw(Scheduler& scheduler, size_t image_index, VkImageView image_view, |
| 20 | VkExtent2D input_image_extent, const Common::Rectangle<int>& crop_rect); | 20 | VkExtent2D input_image_extent, const Common::Rectangle<f32>& crop_rect); |
| 21 | 21 | ||
| 22 | private: | 22 | private: |
| 23 | void CreateDescriptorPool(); | 23 | void CreateDescriptorPool(); |
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index 6b288b994..ac8b6e838 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp | |||
| @@ -100,12 +100,14 @@ void MasterSemaphore::Wait(u64 tick) { | |||
| 100 | Refresh(); | 100 | Refresh(); |
| 101 | } | 101 | } |
| 102 | 102 | ||
| 103 | VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, | 103 | VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf, |
| 104 | VkSemaphore wait_semaphore, u64 host_tick) { | 104 | VkSemaphore signal_semaphore, VkSemaphore wait_semaphore, |
| 105 | u64 host_tick) { | ||
| 105 | if (semaphore) { | 106 | if (semaphore) { |
| 106 | return SubmitQueueTimeline(cmdbuf, signal_semaphore, wait_semaphore, host_tick); | 107 | return SubmitQueueTimeline(cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore, |
| 108 | host_tick); | ||
| 107 | } else { | 109 | } else { |
| 108 | return SubmitQueueFence(cmdbuf, signal_semaphore, wait_semaphore, host_tick); | 110 | return SubmitQueueFence(cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore, host_tick); |
| 109 | } | 111 | } |
| 110 | } | 112 | } |
| 111 | 113 | ||
| @@ -115,6 +117,7 @@ static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{ | |||
| 115 | }; | 117 | }; |
| 116 | 118 | ||
| 117 | VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, | 119 | VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, |
| 120 | vk::CommandBuffer& upload_cmdbuf, | ||
| 118 | VkSemaphore signal_semaphore, | 121 | VkSemaphore signal_semaphore, |
| 119 | VkSemaphore wait_semaphore, u64 host_tick) { | 122 | VkSemaphore wait_semaphore, u64 host_tick) { |
| 120 | const VkSemaphore timeline_semaphore = *semaphore; | 123 | const VkSemaphore timeline_semaphore = *semaphore; |
| @@ -123,6 +126,8 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, | |||
| 123 | const std::array signal_values{host_tick, u64(0)}; | 126 | const std::array signal_values{host_tick, u64(0)}; |
| 124 | const std::array signal_semaphores{timeline_semaphore, signal_semaphore}; | 127 | const std::array signal_semaphores{timeline_semaphore, signal_semaphore}; |
| 125 | 128 | ||
| 129 | const std::array cmdbuffers{*upload_cmdbuf, *cmdbuf}; | ||
| 130 | |||
| 126 | const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; | 131 | const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; |
| 127 | const VkTimelineSemaphoreSubmitInfo timeline_si{ | 132 | const VkTimelineSemaphoreSubmitInfo timeline_si{ |
| 128 | .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, | 133 | .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, |
| @@ -138,8 +143,8 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, | |||
| 138 | .waitSemaphoreCount = num_wait_semaphores, | 143 | .waitSemaphoreCount = num_wait_semaphores, |
| 139 | .pWaitSemaphores = &wait_semaphore, | 144 | .pWaitSemaphores = &wait_semaphore, |
| 140 | .pWaitDstStageMask = wait_stage_masks.data(), | 145 | .pWaitDstStageMask = wait_stage_masks.data(), |
| 141 | .commandBufferCount = 1, | 146 | .commandBufferCount = static_cast<u32>(cmdbuffers.size()), |
| 142 | .pCommandBuffers = cmdbuf.address(), | 147 | .pCommandBuffers = cmdbuffers.data(), |
| 143 | .signalSemaphoreCount = num_signal_semaphores, | 148 | .signalSemaphoreCount = num_signal_semaphores, |
| 144 | .pSignalSemaphores = signal_semaphores.data(), | 149 | .pSignalSemaphores = signal_semaphores.data(), |
| 145 | }; | 150 | }; |
| @@ -147,19 +152,23 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, | |||
| 147 | return device.GetGraphicsQueue().Submit(submit_info); | 152 | return device.GetGraphicsQueue().Submit(submit_info); |
| 148 | } | 153 | } |
| 149 | 154 | ||
| 150 | VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, | 155 | VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, |
| 151 | VkSemaphore wait_semaphore, u64 host_tick) { | 156 | vk::CommandBuffer& upload_cmdbuf, |
| 157 | VkSemaphore signal_semaphore, VkSemaphore wait_semaphore, | ||
| 158 | u64 host_tick) { | ||
| 152 | const u32 num_signal_semaphores = signal_semaphore ? 1 : 0; | 159 | const u32 num_signal_semaphores = signal_semaphore ? 1 : 0; |
| 153 | const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; | 160 | const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; |
| 154 | 161 | ||
| 162 | const std::array cmdbuffers{*upload_cmdbuf, *cmdbuf}; | ||
| 163 | |||
| 155 | const VkSubmitInfo submit_info{ | 164 | const VkSubmitInfo submit_info{ |
| 156 | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, | 165 | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, |
| 157 | .pNext = nullptr, | 166 | .pNext = nullptr, |
| 158 | .waitSemaphoreCount = num_wait_semaphores, | 167 | .waitSemaphoreCount = num_wait_semaphores, |
| 159 | .pWaitSemaphores = &wait_semaphore, | 168 | .pWaitSemaphores = &wait_semaphore, |
| 160 | .pWaitDstStageMask = wait_stage_masks.data(), | 169 | .pWaitDstStageMask = wait_stage_masks.data(), |
| 161 | .commandBufferCount = 1, | 170 | .commandBufferCount = static_cast<u32>(cmdbuffers.size()), |
| 162 | .pCommandBuffers = cmdbuf.address(), | 171 | .pCommandBuffers = cmdbuffers.data(), |
| 163 | .signalSemaphoreCount = num_signal_semaphores, | 172 | .signalSemaphoreCount = num_signal_semaphores, |
| 164 | .pSignalSemaphores = &signal_semaphore, | 173 | .pSignalSemaphores = &signal_semaphore, |
| 165 | }; | 174 | }; |
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h index 3f599d7bd..7dfb93ffb 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.h +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h | |||
| @@ -52,14 +52,16 @@ public: | |||
| 52 | void Wait(u64 tick); | 52 | void Wait(u64 tick); |
| 53 | 53 | ||
| 54 | /// Submits the device graphics queue, updating the tick as necessary | 54 | /// Submits the device graphics queue, updating the tick as necessary |
| 55 | VkResult SubmitQueue(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, | 55 | VkResult SubmitQueue(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf, |
| 56 | VkSemaphore wait_semaphore, u64 host_tick); | 56 | VkSemaphore signal_semaphore, VkSemaphore wait_semaphore, u64 host_tick); |
| 57 | 57 | ||
| 58 | private: | 58 | private: |
| 59 | VkResult SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, | 59 | VkResult SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf, |
| 60 | VkSemaphore wait_semaphore, u64 host_tick); | 60 | VkSemaphore signal_semaphore, VkSemaphore wait_semaphore, |
| 61 | VkResult SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore, | 61 | u64 host_tick); |
| 62 | VkSemaphore wait_semaphore, u64 host_tick); | 62 | VkResult SubmitQueueFence(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf, |
| 63 | VkSemaphore signal_semaphore, VkSemaphore wait_semaphore, | ||
| 64 | u64 host_tick); | ||
| 63 | 65 | ||
| 64 | void WaitThread(std::stop_token token); | 66 | void WaitThread(std::stop_token token); |
| 65 | 67 | ||
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 0d604eee3..2a13b2a72 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | |||
| @@ -263,6 +263,22 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program | |||
| 263 | info.y_negate = key.state.y_negate != 0; | 263 | info.y_negate = key.state.y_negate != 0; |
| 264 | return info; | 264 | return info; |
| 265 | } | 265 | } |
| 266 | |||
| 267 | size_t GetTotalPipelineWorkers() { | ||
| 268 | const size_t max_core_threads = | ||
| 269 | std::max<size_t>(static_cast<size_t>(std::thread::hardware_concurrency()), 2ULL) - 1ULL; | ||
| 270 | #ifdef ANDROID | ||
| 271 | // Leave at least a few cores free in android | ||
| 272 | constexpr size_t free_cores = 3ULL; | ||
| 273 | if (max_core_threads <= free_cores) { | ||
| 274 | return 1ULL; | ||
| 275 | } | ||
| 276 | return max_core_threads - free_cores; | ||
| 277 | #else | ||
| 278 | return max_core_threads; | ||
| 279 | #endif | ||
| 280 | } | ||
| 281 | |||
| 266 | } // Anonymous namespace | 282 | } // Anonymous namespace |
| 267 | 283 | ||
| 268 | size_t ComputePipelineCacheKey::Hash() const noexcept { | 284 | size_t ComputePipelineCacheKey::Hash() const noexcept { |
| @@ -294,11 +310,8 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device | |||
| 294 | texture_cache{texture_cache_}, shader_notify{shader_notify_}, | 310 | texture_cache{texture_cache_}, shader_notify{shader_notify_}, |
| 295 | use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()}, | 311 | use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()}, |
| 296 | use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()}, | 312 | use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()}, |
| 297 | #ifdef ANDROID | 313 | workers(device.HasBrokenParallelShaderCompiling() ? 1ULL : GetTotalPipelineWorkers(), |
| 298 | workers(1, "VkPipelineBuilder"), | 314 | "VkPipelineBuilder"), |
| 299 | #else | ||
| 300 | workers(std::max(std::thread::hardware_concurrency(), 2U) - 1, "VkPipelineBuilder"), | ||
| 301 | #endif | ||
| 302 | serialization_thread(1, "VkPipelineSerialization") { | 315 | serialization_thread(1, "VkPipelineSerialization") { |
| 303 | const auto& float_control{device.FloatControlProperties()}; | 316 | const auto& float_control{device.FloatControlProperties()}; |
| 304 | const VkDriverId driver_id{device.GetDriverID()}; | 317 | const VkDriverId driver_id{device.GetDriverID()}; |
| @@ -338,6 +351,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device | |||
| 338 | .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), | 351 | .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), |
| 339 | .support_native_ndc = device.IsExtDepthClipControlSupported(), | 352 | .support_native_ndc = device.IsExtDepthClipControlSupported(), |
| 340 | .support_scaled_attributes = !device.MustEmulateScaledFormats(), | 353 | .support_scaled_attributes = !device.MustEmulateScaledFormats(), |
| 354 | .support_multi_viewport = device.SupportsMultiViewport(), | ||
| 341 | 355 | ||
| 342 | .warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyBiggerThanGuest(), | 356 | .warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyBiggerThanGuest(), |
| 343 | 357 | ||
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 66c03bf17..078777cdd 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp | |||
| @@ -211,6 +211,13 @@ public: | |||
| 211 | return; | 211 | return; |
| 212 | } | 212 | } |
| 213 | PauseCounter(); | 213 | PauseCounter(); |
| 214 | const auto driver_id = device.GetDriverID(); | ||
| 215 | if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || | ||
| 216 | driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { | ||
| 217 | pending_sync.clear(); | ||
| 218 | sync_values_stash.clear(); | ||
| 219 | return; | ||
| 220 | } | ||
| 214 | sync_values_stash.clear(); | 221 | sync_values_stash.clear(); |
| 215 | sync_values_stash.emplace_back(); | 222 | sync_values_stash.emplace_back(); |
| 216 | std::vector<HostSyncValues>* sync_values = &sync_values_stash.back(); | 223 | std::vector<HostSyncValues>* sync_values = &sync_values_stash.back(); |
| @@ -1378,6 +1385,12 @@ bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::Looku | |||
| 1378 | return true; | 1385 | return true; |
| 1379 | } | 1386 | } |
| 1380 | 1387 | ||
| 1388 | auto driver_id = impl->device.GetDriverID(); | ||
| 1389 | if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || | ||
| 1390 | driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { | ||
| 1391 | return true; | ||
| 1392 | } | ||
| 1393 | |||
| 1381 | for (size_t i = 0; i < 2; i++) { | 1394 | for (size_t i = 0; i < 2; i++) { |
| 1382 | is_null[i] = !is_in_ac[i] && check_value(objects[i]->address); | 1395 | is_null[i] = !is_in_ac[i] && check_value(objects[i]->address); |
| 1383 | } | 1396 | } |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 059b7cb40..e0ab1eaac 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp | |||
| @@ -82,7 +82,7 @@ VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t in | |||
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | if (y_negate) { | 84 | if (y_negate) { |
| 85 | y += height; | 85 | y += conv(static_cast<f32>(regs.surface_clip.height)); |
| 86 | height = -height; | 86 | height = -height; |
| 87 | } | 87 | } |
| 88 | 88 | ||
| @@ -199,7 +199,7 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { | |||
| 199 | if (!pipeline) { | 199 | if (!pipeline) { |
| 200 | return; | 200 | return; |
| 201 | } | 201 | } |
| 202 | std::scoped_lock lock{LockCaches()}; | 202 | std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; |
| 203 | // update engine as channel may be different. | 203 | // update engine as channel may be different. |
| 204 | pipeline->SetEngine(maxwell3d, gpu_memory); | 204 | pipeline->SetEngine(maxwell3d, gpu_memory); |
| 205 | pipeline->Configure(is_indexed); | 205 | pipeline->Configure(is_indexed); |
| @@ -621,7 +621,7 @@ void RasterizerVulkan::OnCacheInvalidation(VAddr addr, u64 size) { | |||
| 621 | } | 621 | } |
| 622 | { | 622 | { |
| 623 | std::scoped_lock lock{buffer_cache.mutex}; | 623 | std::scoped_lock lock{buffer_cache.mutex}; |
| 624 | buffer_cache.CachedWriteMemory(addr, size); | 624 | buffer_cache.WriteMemory(addr, size); |
| 625 | } | 625 | } |
| 626 | pipeline_cache.InvalidateRegion(addr, size); | 626 | pipeline_cache.InvalidateRegion(addr, size); |
| 627 | } | 627 | } |
| @@ -710,7 +710,6 @@ void RasterizerVulkan::TiledCacheBarrier() { | |||
| 710 | } | 710 | } |
| 711 | 711 | ||
| 712 | void RasterizerVulkan::FlushCommands() { | 712 | void RasterizerVulkan::FlushCommands() { |
| 713 | std::scoped_lock lock{LockCaches()}; | ||
| 714 | if (draw_counter == 0) { | 713 | if (draw_counter == 0) { |
| 715 | return; | 714 | return; |
| 716 | } | 715 | } |
| @@ -808,7 +807,6 @@ void RasterizerVulkan::FlushWork() { | |||
| 808 | if ((++draw_counter & 7) != 7) { | 807 | if ((++draw_counter & 7) != 7) { |
| 809 | return; | 808 | return; |
| 810 | } | 809 | } |
| 811 | std::scoped_lock lock{LockCaches()}; | ||
| 812 | if (draw_counter < DRAWS_TO_DISPATCH) { | 810 | if (draw_counter < DRAWS_TO_DISPATCH) { |
| 813 | // Send recorded tasks to the worker thread | 811 | // Send recorded tasks to the worker thread |
| 814 | scheduler.DispatchWork(); | 812 | scheduler.DispatchWork(); |
| @@ -923,9 +921,13 @@ void RasterizerVulkan::UpdateDynamicStates() { | |||
| 923 | } | 921 | } |
| 924 | 922 | ||
| 925 | void RasterizerVulkan::HandleTransformFeedback() { | 923 | void RasterizerVulkan::HandleTransformFeedback() { |
| 924 | static std::once_flag warn_unsupported; | ||
| 925 | |||
| 926 | const auto& regs = maxwell3d->regs; | 926 | const auto& regs = maxwell3d->regs; |
| 927 | if (!device.IsExtTransformFeedbackSupported()) { | 927 | if (!device.IsExtTransformFeedbackSupported()) { |
| 928 | LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported"); | 928 | std::call_once(warn_unsupported, [&] { |
| 929 | LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported"); | ||
| 930 | }); | ||
| 929 | return; | 931 | return; |
| 930 | } | 932 | } |
| 931 | query_cache.CounterEnable(VideoCommon::QueryType::StreamingByteCount, | 933 | query_cache.CounterEnable(VideoCommon::QueryType::StreamingByteCount, |
| @@ -1503,7 +1505,7 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs) | |||
| 1503 | void RasterizerVulkan::InitializeChannel(Tegra::Control::ChannelState& channel) { | 1505 | void RasterizerVulkan::InitializeChannel(Tegra::Control::ChannelState& channel) { |
| 1504 | CreateChannel(channel); | 1506 | CreateChannel(channel); |
| 1505 | { | 1507 | { |
| 1506 | std::scoped_lock lock{LockCaches()}; | 1508 | std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; |
| 1507 | texture_cache.CreateChannel(channel); | 1509 | texture_cache.CreateChannel(channel); |
| 1508 | buffer_cache.CreateChannel(channel); | 1510 | buffer_cache.CreateChannel(channel); |
| 1509 | } | 1511 | } |
| @@ -1516,7 +1518,7 @@ void RasterizerVulkan::BindChannel(Tegra::Control::ChannelState& channel) { | |||
| 1516 | const s32 channel_id = channel.bind_id; | 1518 | const s32 channel_id = channel.bind_id; |
| 1517 | BindToChannel(channel_id); | 1519 | BindToChannel(channel_id); |
| 1518 | { | 1520 | { |
| 1519 | std::scoped_lock lock{LockCaches()}; | 1521 | std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; |
| 1520 | texture_cache.BindToChannel(channel_id); | 1522 | texture_cache.BindToChannel(channel_id); |
| 1521 | buffer_cache.BindToChannel(channel_id); | 1523 | buffer_cache.BindToChannel(channel_id); |
| 1522 | } | 1524 | } |
| @@ -1529,7 +1531,7 @@ void RasterizerVulkan::BindChannel(Tegra::Control::ChannelState& channel) { | |||
| 1529 | void RasterizerVulkan::ReleaseChannel(s32 channel_id) { | 1531 | void RasterizerVulkan::ReleaseChannel(s32 channel_id) { |
| 1530 | EraseChannel(channel_id); | 1532 | EraseChannel(channel_id); |
| 1531 | { | 1533 | { |
| 1532 | std::scoped_lock lock{LockCaches()}; | 1534 | std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; |
| 1533 | texture_cache.EraseChannel(channel_id); | 1535 | texture_cache.EraseChannel(channel_id); |
| 1534 | buffer_cache.EraseChannel(channel_id); | 1536 | buffer_cache.EraseChannel(channel_id); |
| 1535 | } | 1537 | } |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index ce3dfbaab..ad069556c 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h | |||
| @@ -133,10 +133,6 @@ public: | |||
| 133 | 133 | ||
| 134 | void ReleaseChannel(s32 channel_id) override; | 134 | void ReleaseChannel(s32 channel_id) override; |
| 135 | 135 | ||
| 136 | std::scoped_lock<std::recursive_mutex, std::recursive_mutex> LockCaches() { | ||
| 137 | return std::scoped_lock{buffer_cache.mutex, texture_cache.mutex}; | ||
| 138 | } | ||
| 139 | |||
| 140 | private: | 136 | private: |
| 141 | static constexpr size_t MAX_TEXTURES = 192; | 137 | static constexpr size_t MAX_TEXTURES = 192; |
| 142 | static constexpr size_t MAX_IMAGES = 48; | 138 | static constexpr size_t MAX_IMAGES = 48; |
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 3be7837f4..146923db4 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp | |||
| @@ -22,11 +22,12 @@ namespace Vulkan { | |||
| 22 | 22 | ||
| 23 | MICROPROFILE_DECLARE(Vulkan_WaitForWorker); | 23 | MICROPROFILE_DECLARE(Vulkan_WaitForWorker); |
| 24 | 24 | ||
| 25 | void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) { | 25 | void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf, |
| 26 | vk::CommandBuffer upload_cmdbuf) { | ||
| 26 | auto command = first; | 27 | auto command = first; |
| 27 | while (command != nullptr) { | 28 | while (command != nullptr) { |
| 28 | auto next = command->GetNext(); | 29 | auto next = command->GetNext(); |
| 29 | command->Execute(cmdbuf); | 30 | command->Execute(cmdbuf, upload_cmdbuf); |
| 30 | command->~Command(); | 31 | command->~Command(); |
| 31 | command = next; | 32 | command = next; |
| 32 | } | 33 | } |
| @@ -180,7 +181,7 @@ void Scheduler::WorkerThread(std::stop_token stop_token) { | |||
| 180 | // Perform the work, tracking whether the chunk was a submission | 181 | // Perform the work, tracking whether the chunk was a submission |
| 181 | // before executing. | 182 | // before executing. |
| 182 | const bool has_submit = work->HasSubmit(); | 183 | const bool has_submit = work->HasSubmit(); |
| 183 | work->ExecuteAll(current_cmdbuf); | 184 | work->ExecuteAll(current_cmdbuf, current_upload_cmdbuf); |
| 184 | 185 | ||
| 185 | // If the chunk was a submission, reallocate the command buffer. | 186 | // If the chunk was a submission, reallocate the command buffer. |
| 186 | if (has_submit) { | 187 | if (has_submit) { |
| @@ -205,6 +206,13 @@ void Scheduler::AllocateWorkerCommandBuffer() { | |||
| 205 | .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, | 206 | .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, |
| 206 | .pInheritanceInfo = nullptr, | 207 | .pInheritanceInfo = nullptr, |
| 207 | }); | 208 | }); |
| 209 | current_upload_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader()); | ||
| 210 | current_upload_cmdbuf.Begin({ | ||
| 211 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, | ||
| 212 | .pNext = nullptr, | ||
| 213 | .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, | ||
| 214 | .pInheritanceInfo = nullptr, | ||
| 215 | }); | ||
| 208 | } | 216 | } |
| 209 | 217 | ||
| 210 | u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { | 218 | u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { |
| @@ -212,7 +220,17 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se | |||
| 212 | InvalidateState(); | 220 | InvalidateState(); |
| 213 | 221 | ||
| 214 | const u64 signal_value = master_semaphore->NextTick(); | 222 | const u64 signal_value = master_semaphore->NextTick(); |
| 215 | Record([signal_semaphore, wait_semaphore, signal_value, this](vk::CommandBuffer cmdbuf) { | 223 | RecordWithUploadBuffer([signal_semaphore, wait_semaphore, signal_value, |
| 224 | this](vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) { | ||
| 225 | static constexpr VkMemoryBarrier WRITE_BARRIER{ | ||
| 226 | .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, | ||
| 227 | .pNext = nullptr, | ||
| 228 | .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, | ||
| 229 | .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, | ||
| 230 | }; | ||
| 231 | upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, | ||
| 232 | VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER); | ||
| 233 | upload_cmdbuf.End(); | ||
| 216 | cmdbuf.End(); | 234 | cmdbuf.End(); |
| 217 | 235 | ||
| 218 | if (on_submit) { | 236 | if (on_submit) { |
| @@ -221,7 +239,7 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se | |||
| 221 | 239 | ||
| 222 | std::scoped_lock lock{submit_mutex}; | 240 | std::scoped_lock lock{submit_mutex}; |
| 223 | switch (const VkResult result = master_semaphore->SubmitQueue( | 241 | switch (const VkResult result = master_semaphore->SubmitQueue( |
| 224 | cmdbuf, signal_semaphore, wait_semaphore, signal_value)) { | 242 | cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore, signal_value)) { |
| 225 | case VK_SUCCESS: | 243 | case VK_SUCCESS: |
| 226 | break; | 244 | break; |
| 227 | case VK_ERROR_DEVICE_LOST: | 245 | case VK_ERROR_DEVICE_LOST: |
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index da03803aa..f8d8ca80a 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h | |||
| @@ -80,7 +80,8 @@ public: | |||
| 80 | 80 | ||
| 81 | /// Send work to a separate thread. | 81 | /// Send work to a separate thread. |
| 82 | template <typename T> | 82 | template <typename T> |
| 83 | void Record(T&& command) { | 83 | requires std::is_invocable_v<T, vk::CommandBuffer, vk::CommandBuffer> |
| 84 | void RecordWithUploadBuffer(T&& command) { | ||
| 84 | if (chunk->Record(command)) { | 85 | if (chunk->Record(command)) { |
| 85 | return; | 86 | return; |
| 86 | } | 87 | } |
| @@ -88,6 +89,15 @@ public: | |||
| 88 | (void)chunk->Record(command); | 89 | (void)chunk->Record(command); |
| 89 | } | 90 | } |
| 90 | 91 | ||
| 92 | template <typename T> | ||
| 93 | requires std::is_invocable_v<T, vk::CommandBuffer> | ||
| 94 | void Record(T&& c) { | ||
| 95 | this->RecordWithUploadBuffer( | ||
| 96 | [command = std::move(c)](vk::CommandBuffer cmdbuf, vk::CommandBuffer) { | ||
| 97 | command(cmdbuf); | ||
| 98 | }); | ||
| 99 | } | ||
| 100 | |||
| 91 | /// Returns the current command buffer tick. | 101 | /// Returns the current command buffer tick. |
| 92 | [[nodiscard]] u64 CurrentTick() const noexcept { | 102 | [[nodiscard]] u64 CurrentTick() const noexcept { |
| 93 | return master_semaphore->CurrentTick(); | 103 | return master_semaphore->CurrentTick(); |
| @@ -119,7 +129,7 @@ private: | |||
| 119 | public: | 129 | public: |
| 120 | virtual ~Command() = default; | 130 | virtual ~Command() = default; |
| 121 | 131 | ||
| 122 | virtual void Execute(vk::CommandBuffer cmdbuf) const = 0; | 132 | virtual void Execute(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) const = 0; |
| 123 | 133 | ||
| 124 | Command* GetNext() const { | 134 | Command* GetNext() const { |
| 125 | return next; | 135 | return next; |
| @@ -142,8 +152,8 @@ private: | |||
| 142 | TypedCommand(TypedCommand&&) = delete; | 152 | TypedCommand(TypedCommand&&) = delete; |
| 143 | TypedCommand& operator=(TypedCommand&&) = delete; | 153 | TypedCommand& operator=(TypedCommand&&) = delete; |
| 144 | 154 | ||
| 145 | void Execute(vk::CommandBuffer cmdbuf) const override { | 155 | void Execute(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) const override { |
| 146 | command(cmdbuf); | 156 | command(cmdbuf, upload_cmdbuf); |
| 147 | } | 157 | } |
| 148 | 158 | ||
| 149 | private: | 159 | private: |
| @@ -152,7 +162,7 @@ private: | |||
| 152 | 162 | ||
| 153 | class CommandChunk final { | 163 | class CommandChunk final { |
| 154 | public: | 164 | public: |
| 155 | void ExecuteAll(vk::CommandBuffer cmdbuf); | 165 | void ExecuteAll(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf); |
| 156 | 166 | ||
| 157 | template <typename T> | 167 | template <typename T> |
| 158 | bool Record(T& command) { | 168 | bool Record(T& command) { |
| @@ -228,6 +238,7 @@ private: | |||
| 228 | VideoCommon::QueryCacheBase<QueryCacheParams>* query_cache = nullptr; | 238 | VideoCommon::QueryCacheBase<QueryCacheParams>* query_cache = nullptr; |
| 229 | 239 | ||
| 230 | vk::CommandBuffer current_cmdbuf; | 240 | vk::CommandBuffer current_cmdbuf; |
| 241 | vk::CommandBuffer current_upload_cmdbuf; | ||
| 231 | 242 | ||
| 232 | std::unique_ptr<CommandChunk> chunk; | 243 | std::unique_ptr<CommandChunk> chunk; |
| 233 | std::function<void()> on_submit; | 244 | std::function<void()> on_submit; |
diff --git a/src/video_core/renderer_vulkan/vk_smaa.cpp b/src/video_core/renderer_vulkan/vk_smaa.cpp index 5efd7d66e..70644ea82 100644 --- a/src/video_core/renderer_vulkan/vk_smaa.cpp +++ b/src/video_core/renderer_vulkan/vk_smaa.cpp | |||
| @@ -672,7 +672,7 @@ void SMAA::UploadImages(Scheduler& scheduler) { | |||
| 672 | UploadImage(m_device, m_allocator, scheduler, m_static_images[Search], search_extent, | 672 | UploadImage(m_device, m_allocator, scheduler, m_static_images[Search], search_extent, |
| 673 | VK_FORMAT_R8_UNORM, ARRAY_TO_SPAN(searchTexBytes)); | 673 | VK_FORMAT_R8_UNORM, ARRAY_TO_SPAN(searchTexBytes)); |
| 674 | 674 | ||
| 675 | scheduler.Record([&](vk::CommandBuffer& cmdbuf) { | 675 | scheduler.Record([&](vk::CommandBuffer cmdbuf) { |
| 676 | for (auto& images : m_dynamic_images) { | 676 | for (auto& images : m_dynamic_images) { |
| 677 | for (size_t i = 0; i < MaxDynamicImage; i++) { | 677 | for (size_t i = 0; i < MaxDynamicImage; i++) { |
| 678 | ClearColorImage(cmdbuf, *images.images[i]); | 678 | ClearColorImage(cmdbuf, *images.images[i]); |
| @@ -707,7 +707,7 @@ VkImageView SMAA::Draw(Scheduler& scheduler, size_t image_index, VkImage source_ | |||
| 707 | UpdateDescriptorSets(source_image_view, image_index); | 707 | UpdateDescriptorSets(source_image_view, image_index); |
| 708 | 708 | ||
| 709 | scheduler.RequestOutsideRenderPassOperationContext(); | 709 | scheduler.RequestOutsideRenderPassOperationContext(); |
| 710 | scheduler.Record([=, this](vk::CommandBuffer& cmdbuf) { | 710 | scheduler.Record([=, this](vk::CommandBuffer cmdbuf) { |
| 711 | TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL); | 711 | TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL); |
| 712 | TransitionImageLayout(cmdbuf, edges_image, VK_IMAGE_LAYOUT_GENERAL); | 712 | TransitionImageLayout(cmdbuf, edges_image, VK_IMAGE_LAYOUT_GENERAL); |
| 713 | BeginRenderPass(cmdbuf, m_renderpasses[EdgeDetection], edge_detection_framebuffer, | 713 | BeginRenderPass(cmdbuf, m_renderpasses[EdgeDetection], edge_detection_framebuffer, |
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h index d3deb9072..f63a20327 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h | |||
| @@ -36,6 +36,10 @@ public: | |||
| 36 | StagingBufferRef Request(size_t size, MemoryUsage usage, bool deferred = false); | 36 | StagingBufferRef Request(size_t size, MemoryUsage usage, bool deferred = false); |
| 37 | void FreeDeferred(StagingBufferRef& ref); | 37 | void FreeDeferred(StagingBufferRef& ref); |
| 38 | 38 | ||
| 39 | [[nodiscard]] VkBuffer StreamBuf() const noexcept { | ||
| 40 | return *stream_buffer; | ||
| 41 | } | ||
| 42 | |||
| 39 | void TickFrame(); | 43 | void TickFrame(); |
| 40 | 44 | ||
| 41 | private: | 45 | private: |
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index de34f6d49..5dbec2e62 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp | |||
| @@ -1785,8 +1785,22 @@ ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, | |||
| 1785 | : VideoCommon::ImageViewBase{info, view_info, gpu_addr_}, | 1785 | : VideoCommon::ImageViewBase{info, view_info, gpu_addr_}, |
| 1786 | buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {} | 1786 | buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {} |
| 1787 | 1787 | ||
| 1788 | ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams& params) | 1788 | ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageViewParams& params) |
| 1789 | : VideoCommon::ImageViewBase{params} {} | 1789 | : VideoCommon::ImageViewBase{params}, device{&runtime.device} { |
| 1790 | if (device->HasNullDescriptor()) { | ||
| 1791 | return; | ||
| 1792 | } | ||
| 1793 | |||
| 1794 | // Handle fallback for devices without nullDescriptor | ||
| 1795 | ImageInfo info{}; | ||
| 1796 | info.format = PixelFormat::A8B8G8R8_UNORM; | ||
| 1797 | |||
| 1798 | null_image = MakeImage(*device, runtime.memory_allocator, info, {}); | ||
| 1799 | image_handle = *null_image; | ||
| 1800 | for (u32 i = 0; i < Shader::NUM_TEXTURE_TYPES; i++) { | ||
| 1801 | image_views[i] = MakeView(VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_IMAGE_ASPECT_COLOR_BIT); | ||
| 1802 | } | ||
| 1803 | } | ||
| 1790 | 1804 | ||
| 1791 | ImageView::~ImageView() = default; | 1805 | ImageView::~ImageView() = default; |
| 1792 | 1806 | ||
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 7a0807709..edf5d7635 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h | |||
| @@ -267,6 +267,7 @@ private: | |||
| 267 | vk::ImageView depth_view; | 267 | vk::ImageView depth_view; |
| 268 | vk::ImageView stencil_view; | 268 | vk::ImageView stencil_view; |
| 269 | vk::ImageView color_view; | 269 | vk::ImageView color_view; |
| 270 | vk::Image null_image; | ||
| 270 | VkImage image_handle = VK_NULL_HANDLE; | 271 | VkImage image_handle = VK_NULL_HANDLE; |
| 271 | VkImageView render_target = VK_NULL_HANDLE; | 272 | VkImageView render_target = VK_NULL_HANDLE; |
| 272 | VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; | 273 | VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; |