diff --git a/CMakeLists.txt b/CMakeLists.txt index 88320c6c6..e0356c124 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,7 @@ set(gpu_tiler_shader_names promote_d16 demote_d16 color_transform + color_clear ) file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc") foreach(shader_name IN LISTS gpu_tiler_shader_names) diff --git a/src/common/debugCounters.h b/src/common/debugCounters.h index 5d346da28..9e89a9bd9 100644 --- a/src/common/debugCounters.h +++ b/src/common/debugCounters.h @@ -31,6 +31,7 @@ enum class Counter { TextureEvictions, ColorMetadataReadbacks, // CPU blocked reading back GPU-written DCC/CMASK metadata. ColorMetadataReadbackNs, + ColorMetadataChecks, // GPU clear-key checks of DCC/CMASK metadata. BufferUploadBytes, BufferDownloadBytes, BufferEvictions, diff --git a/src/graphics/host_gpu/graphicContext.h b/src/graphics/host_gpu/graphicContext.h index 971cc983e..9f999c188 100644 --- a/src/graphics/host_gpu/graphicContext.h +++ b/src/graphics/host_gpu/graphicContext.h @@ -35,6 +35,7 @@ struct GraphicContext { bool attachment_feedback_loop_enabled = false; bool provoking_vertex_last_enabled = false; bool maintenance8_enabled = false; + bool conditional_rendering_enabled = false; bool supports_block_texel_view = false; bool mesh_shader_enabled = false; vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {}; diff --git a/src/graphics/host_gpu/renderer/cache/bufferCache.cpp b/src/graphics/host_gpu/renderer/cache/bufferCache.cpp index bad5188af..91f30d77c 100644 --- a/src/graphics/host_gpu/renderer/cache/bufferCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/bufferCache.cpp @@ -174,6 +174,7 @@ bool BufferCache::DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t total_size += Common::AlignUp(end - start, 64); }); m_gpu_modified_ranges.Subtract(address, bytes); + ForgetColorMetadataChecks(address, bytes); }); if (copies.empty()) { return false; @@ -519,6 +520,7 @@ std::pair BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t (void)SynchronizeBuffer(buffer, vaddr, size, is_written, is_texel_buffer); if (is_written) { m_gpu_modified_ranges.Add(vaddr, size); + ForgetColorMetadataChecks(vaddr, size); } return {&buffer, buffer.Offset(vaddr)}; } @@ -623,6 +625,29 @@ bool BufferCache::IsRegionRegistered(uint64_t vaddr, uint64_t size) { return address + m_slot_buffers[id].Size() > vaddr; } +void BufferCache::MarkColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) { + for (uint32_t slot = 0; slot < m_checked_color_metadata.size(); slot++) { + if ((keys & (1u << slot)) != 0) { + m_checked_color_metadata[slot].Add(vaddr, size); + } + } +} + +bool BufferCache::IsColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) const { + for (uint32_t slot = 0; slot < m_checked_color_metadata.size(); slot++) { + if ((keys & (1u << slot)) != 0 && !m_checked_color_metadata[slot].Contains(vaddr, size)) { + return false; + } + } + return true; +} + +void BufferCache::ForgetColorMetadataChecks(uint64_t vaddr, uint64_t size) { + for (auto& checked: m_checked_color_metadata) { + checked.Subtract(vaddr, size); + } +} + bool BufferCache::IsRegionGpuModified(uint64_t vaddr, uint64_t size) { return m_memory_tracker.IsRegionGpuModified(vaddr, size); } diff --git a/src/graphics/host_gpu/renderer/cache/bufferCache.h b/src/graphics/host_gpu/renderer/cache/bufferCache.h index 27bf987c6..8a30e68e1 100644 --- a/src/graphics/host_gpu/renderer/cache/bufferCache.h +++ b/src/graphics/host_gpu/renderer/cache/bufferCache.h @@ -11,6 +11,7 @@ #include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h" #include "graphics/host_gpu/renderer/cache/streamBuffer.h" +#include #include #include #include @@ -79,6 +80,10 @@ public: [[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size); [[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size); [[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size); + // Color metadata the GPU found free of the clear keys in `keys` (bits of code >> 5) stays so + // until a GPU write or download. + void MarkColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys); + [[nodiscard]] bool IsColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) const; void ProcessFaultBuffer(); void SynchronizeBda(const RangeSet& mapped); void QueueBdaSync(uint64_t vaddr, uint64_t size) { @@ -124,6 +129,7 @@ private: // Synchronous downloads publish before returning; asynchronous callers wait before reuse. template [[nodiscard]] bool DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t size); + void ForgetColorMetadataChecks(uint64_t vaddr, uint64_t size); GraphicContext& m_graphics; CommandScheduler& m_scheduler; @@ -135,6 +141,8 @@ private: BufferMap m_buffers; PageTable m_page_table; RangeSet m_gpu_modified_ranges; + // One per TileManager::ColorClearSlots key slot. + std::array m_checked_color_metadata; MemoryTracker m_memory_tracker; StreamBuffer m_staging_buffer; StreamBuffer m_stream_buffer; diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index ca9a73109..3d75ac511 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -1067,15 +1067,78 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc, if (first >= layers || count > layers - first) { EXIT("TextureCache: color view exceeds its native metadata slices\n"); } - // Finish native metadata writes before reading backing bytes. This can submit the scheduler, - // so discovery runs before final draw uploads and never holds the texture lock across it. - if (m_buffer_cache.IsRegionGpuModified(range.address, range.size)) { + const auto slice_size = range.size / layers; + const GuestRange viewed {range.address + slice_size * first, slice_size * count}; + if (m_buffer_cache.IsRegionGpuModified(viewed.address, viewed.size)) { + std::array clears {}; + uint32_t keys = 0; + for (uint32_t key = 0; key < clears.size(); key++) { + clears[key].aspectMask = vk::ImageAspectFlagBits::eColor; + if (DecodeColorClear(desc, static_cast(key << 5u), + clears[key].clearValue.color)) { + keys |= 1u << key; + } + } + // No selected key to clear, or none left since the last check. + if (m_buffer_cache.IsColorMetadataChecked(viewed.address, viewed.size, keys)) { + return; + } + { + std::scoped_lock lock {m_lock}; + auto& image = m_slot_images[id]; + // Compressed images cannot upload, so they must already hold native contents. + const bool native_current = (image.usage.render_target || image.IsGpuModified()) && + !image.IsBufferModified() && !image.IsCpuDirty(); + // 2D-array attachment views select 2D layers or, of a volume, depth slices. + if (m_graphics.conditional_rendering_enabled && + image.backing.image_type != vk::ImageType::e1D && + count <= m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers && + (image.backing.usage & vk::ImageUsageFlagBits::eColorAttachment) && + (m_graphics.GetFormatProperties(view.format).optimalTilingFeatures & + vk::FormatFeatureFlagBits::eColorAttachment) && + (desc.info.metadata.compression == VideoOutCompression::Uncompressed || + native_current)) { + // The GPU selects each slice's clear, so the CPU never waits for the metadata. + Common::DebugCounters::Add(Common::DebugCounters::Counter::ColorMetadataChecks); + InitializeImage(id); + const bool expand = desc.type != BindingType::VideoOut; + const auto [buffer, offset] = + m_buffer_cache.ObtainBuffer(viewed.address, viewed.size, expand); + const auto predicates = m_tiler.CheckColorClear( + {buffer->Handle(), offset, viewed.size}, count, keys, expand); + if (expand) { + m_buffer_cache.MarkColorMetadataChecked(viewed.address, viewed.size, keys); + } + const auto command = m_scheduler.Current().Handle(); + const auto area = BeginColorRendering(image, view.format, view.base_level, + image_first, count, nullptr, command); + vk::ClearRect rect {.rect = area, .layerCount = 1}; + for (uint32_t slice = 0; slice < count; slice++) { + rect.baseArrayLayer = slice; + for (uint32_t key = 0; key < clears.size(); key++) { + if ((keys & (1u << key)) != 0) { + const vk::ConditionalRenderingBeginInfoEXT predicate { + .buffer = predicates, + .offset = (slice * clears.size() + key) * sizeof(uint32_t)}; + command.beginConditionalRenderingEXT(&predicate); + command.clearAttachments(1, &clears[key], 1, &rect); + command.endConditionalRenderingEXT(); + } + } + } + command.endRendering(); + CommitGpuWrite(image); + return; + } + } + // Finish native metadata writes before reading backing bytes. This can submit the + // scheduler, so discovery runs before final draw uploads and never holds the texture + // lock across it. Common::DebugCounters::ScopedTimer timer( Common::DebugCounters::Counter::ColorMetadataReadbacks, Common::DebugCounters::Counter::ColorMetadataReadbackNs); - m_buffer_cache.ReadMemory(range.address, range.size, false); + m_buffer_cache.ReadMemory(viewed.address, viewed.size, false); } - const auto slice_size = range.size / layers; for (uint32_t slice = 0; slice < count; slice++) { const auto address = range.address + slice_size * (first + slice); uint8_t code = 0; @@ -1768,6 +1831,7 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin copy_size != image.info.data.size)) { return false; } + ForgetColorMetadataChecks(image.info.data.address, copy_size); m_texture_cache.DownloadImage(image, buffer, buf_offset, copy_size, levels); return true; } diff --git a/src/graphics/host_gpu/renderer/image/tiler.cpp b/src/graphics/host_gpu/renderer/image/tiler.cpp index fbc157982..3b524e7a2 100644 --- a/src/graphics/host_gpu/renderer/image/tiler.cpp +++ b/src/graphics/host_gpu/renderer/image/tiler.cpp @@ -14,6 +14,7 @@ #include "gpu_tiler_shaders/gpu_tiler_standard64_3d_spv.h" #include "gpu_tiler_shaders/gpu_tiler_standard64_spv.h" #include "gpu_tiler_shaders/gpu_tiler_color_transform_spv.h" +#include "gpu_tiler_shaders/gpu_tiler_color_clear_spv.h" #include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/renderer/cache/streamBuffer.h" #include "graphics/host_gpu/renderer/commandScheduler.h" @@ -71,8 +72,8 @@ TileManager::~TileManager() { m_graphics.device.destroyPipeline(pipeline, nullptr); } } - for (auto pipeline: - {m_d16_to_d24, m_d16_to_d32, m_d24_to_d16, m_d32_to_d16, m_color_transform}) { + for (auto pipeline: {m_d16_to_d24, m_d16_to_d32, m_d24_to_d16, m_d32_to_d16, m_color_transform, + m_color_clear}) { if (pipeline != nullptr) { m_graphics.device.destroyPipeline(pipeline, nullptr); } @@ -627,4 +628,60 @@ TileManager::Result TileManager::TransformColor(Result input, ColorTransform tra return result; } +vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32_t keys, + bool expand) { + struct Check { + uint32_t metadata_base; + uint32_t words; + uint32_t keys; + uint32_t expand; + }; + static_assert(sizeof(Check) <= sizeof(Conversion)); + const uint32_t max_slices = + m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers; + EXIT_IF(slices == 0 || slices > max_slices || metadata.size % (4ull * slices) != 0 || + metadata.size / 4 > UINT32_MAX); + if (m_color_clear == nullptr) { + m_color_clear = CreatePipeline(GPU_TILER_COLOR_CLEAR_SPV, nullptr); + m_clear_predicates = std::make_unique( + m_graphics, m_scheduler, MemoryUsage::DeviceLocal, 0, + vk::BufferUsageFlagBits::eStorageBuffer | + vk::BufferUsageFlagBits::eConditionalRenderingEXT, + uint64_t {max_slices} * ColorClearSlots * sizeof(uint32_t)); + } + const auto source = BindStorage(metadata, metadata.size); + const vk::DescriptorBufferInfo infos[] {source.info, + {m_clear_predicates->Handle(), 0, VK_WHOLE_SIZE}}; + std::array writes {}; + for (uint32_t index = 0; index < writes.size(); index++) { + writes[index].dstBinding = index; + writes[index].descriptorCount = 1; + writes[index].descriptorType = vk::DescriptorType::eStorageBuffer; + writes[index].pBufferInfo = &infos[index]; + } + // Orders the metadata writers and the previous check's predicate reads before this check. + vk::MemoryBarrier barrier { + .srcAccessMask = vk::AccessFlagBits::eMemoryWrite, + .dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite}; + m_scheduler.EndRendering(); + auto command = m_scheduler.Current().Handle(); + command.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, + vk::PipelineStageFlagBits::eComputeShader, {}, 1, &barrier, 0, nullptr, + 0, nullptr); + command.bindPipeline(vk::PipelineBindPoint::eCompute, m_color_clear); + command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0, + static_cast(writes.size()), writes.data()); + const Check push {source.base, static_cast(metadata.size / 4 / slices), keys, + expand}; + command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push), + &push); + command.dispatch(slices, 1, 1); + barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite; + command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, + vk::PipelineStageFlagBits::eAllCommands, {}, 1, &barrier, 0, nullptr, + 0, nullptr); + return m_clear_predicates->Handle(); +} + } // namespace Libs::Graphics diff --git a/src/graphics/host_gpu/renderer/image/tiler.h b/src/graphics/host_gpu/renderer/image/tiler.h index 6f183b9ad..95babc39e 100644 --- a/src/graphics/host_gpu/renderer/image/tiler.h +++ b/src/graphics/host_gpu/renderer/image/tiler.h @@ -54,6 +54,13 @@ public: const D16Layout& layout); [[nodiscard]] Result TransformColor(Result input, ColorTransform transform, bool to_host); void TransformColor(Result input, Result output, ColorTransform transform, bool to_host); + // Color-clear keys are multiples of 0x20, so key >> 5 selects one of these slots. + static constexpr uint32_t ColorClearSlots = 8; + // Returns the buffer whose dword slice * ColorClearSlots + (key >> 5) is set when every byte + // of that metadata slice equals a key whose bit `keys` selects; a hit expands to 0xff when + // `expand` is set. + [[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint32_t slices, uint32_t keys, + bool expand); private: friend struct TileManagerTestAccess; @@ -132,6 +139,8 @@ private: vk::Pipeline m_d24_to_d16 = nullptr; vk::Pipeline m_d32_to_d16 = nullptr; vk::Pipeline m_color_transform = nullptr; + vk::Pipeline m_color_clear = nullptr; + std::unique_ptr m_clear_predicates; }; } // namespace Libs::Graphics diff --git a/src/graphics/host_gpu/shaders/gpu_tiler_color_clear.comp b/src/graphics/host_gpu/shaders/gpu_tiler_color_clear.comp new file mode 100644 index 000000000..396da68e2 --- /dev/null +++ b/src/graphics/host_gpu/shaders/gpu_tiler_color_clear.comp @@ -0,0 +1,50 @@ +#version 450 + +// One workgroup per metadata slice; see TileManager::CheckColorClear. +layout(local_size_x = 256) in; + +layout(set = 0, binding = 0, std430) buffer Metadata { uint data[]; } metadata; +layout(set = 0, binding = 1, std430) writeonly buffer Predicates { uint data[]; } predicates; + +layout(push_constant, std430) uniform Check { + uint metadata_base; + uint words; + uint keys; + uint expand; +} params; + +const uint Slots = 8u; // TileManager::ColorClearSlots + +shared uint key; +shared uint mismatch; + +void main() { + const uint slice = gl_WorkGroupID.x; + const uint base = (params.metadata_base >> 2u) + slice * params.words; + // Each invocation reads, and may rewrite, only its own words. + if (gl_LocalInvocationIndex == 0u) { + key = metadata.data[base] & 0xffu; + mismatch = 0u; + } + barrier(); + const uint pattern = key * 0x01010101u; + bool same = true; + for (uint i = gl_LocalInvocationIndex; same && i < params.words; i += gl_WorkGroupSize.x) { + same = metadata.data[base + i] == pattern; + } + if (!same) { + atomicOr(mismatch, 1u); + } + barrier(); + const uint slot = key >> 5u; + const bool hit = mismatch == 0u && (key & 0x1fu) == 0u && (params.keys & (1u << slot)) != 0u; + if (gl_LocalInvocationIndex < Slots) { + predicates.data[slice * Slots + gl_LocalInvocationIndex] = + uint(hit && slot == gl_LocalInvocationIndex); + } + if (hit && params.expand != 0u) { + for (uint i = gl_LocalInvocationIndex; i < params.words; i += gl_WorkGroupSize.x) { + metadata.data[base + i] = 0xffffffffu; + } + } +} diff --git a/src/graphics/presentation/performanceOverlay.cpp b/src/graphics/presentation/performanceOverlay.cpp index 1f8a71d6b..11d7943c4 100644 --- a/src/graphics/presentation/performanceOverlay.cpp +++ b/src/graphics/presentation/performanceOverlay.cpp @@ -313,6 +313,7 @@ void GpuActivityRows(const Snapshot& shown) { Row("DCC/CMASK readbacks", Fmt("%.0f ms/s (%.0f/s)", Rate(shown, Counter::ColorMetadataReadbackNs) / 1e6, Rate(shown, Counter::ColorMetadataReadbacks))); + Row("DCC/CMASK GPU checks", Fmt("%.0f/s", Rate(shown, Counter::ColorMetadataChecks))); } void CompilationRows(const Snapshot& shown) { diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index c1a2b98ff..4b698159f 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -499,6 +499,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, image_atomic_int64.pNext = supported_features2.pNext; supported_features2.pNext = &image_atomic_int64; } + const bool conditional_rendering_extension = + HasExtension(device_extensions, VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME); + vk::PhysicalDeviceConditionalRenderingFeaturesEXT conditional_rendering {}; + if (conditional_rendering_extension) { + conditional_rendering.pNext = supported_features2.pNext; + supported_features2.pNext = &conditional_rendering; + } const bool barycentric_extension = HasExtension(device_extensions, VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME); vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR supported_barycentric {}; @@ -572,6 +579,8 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout && feedback_dynamic.attachmentFeedbackLoopDynamicState; graphics.maintenance8_enabled = maintenance8_extension && maintenance8.maintenance8; + graphics.conditional_rendering_enabled = + conditional_rendering_extension && conditional_rendering.conditionalRendering; LOGF("Vulkan maintenance8 support: %s\n", graphics.maintenance8_enabled ? "true" : "false"); LOGF("Vulkan depth feedback support: %s\n", @@ -670,6 +679,10 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, maintenance8.pNext = const_cast(create_info.pNext); create_info.pNext = &maintenance8; } + if (graphics.conditional_rendering_enabled) { + conditional_rendering.pNext = const_cast(create_info.pNext); + create_info.pNext = &conditional_rendering; + } create_info.pQueueCreateInfos = &queue_create_info; create_info.queueCreateInfoCount = 1; create_info.enabledExtensionCount = static_cast(device_extensions.size()); @@ -1046,7 +1059,8 @@ void WindowContext::CreateVulkan() { VK_EXT_MESH_SHADER_EXTENSION_NAME, VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME, VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME, - VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME}) { + VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME, + VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME}) { if (HasExtension(available_extensions, extension)) { device_extensions.push_back(extension); } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 394013afa..c0088940a 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -10266,6 +10266,32 @@ public: ReadCachedTexel(name, context, color.image_id, {0, 0, 31}) == std::vector{0x5a5a5a5a}, "clearing one volume slice changed untouched slice contents"); + // A GPU writer clears slice 9's metadata; its bind selects the clear on the GPU. + auto [writer, writer_offset] = resources.GetBufferCache().ObtainBuffer( + dcc_address + 9 * metadata_slice_size, metadata_slice_size, true); + writer->Fill(writer_offset, metadata_slice_size, 0); + Common::DebugCounters::SetEnabled(true); + const auto readbacks = + Common::DebugCounters::Get(Common::DebugCounters::Counter::ColorMetadataReadbacks); + const auto checks = + Common::DebugCounters::Get(Common::DebugCounters::Counter::ColorMetadataChecks); + RenderExecutorTestAccess::ResetBindings(executor); + registers.SetColorView(0, {.base_array_slice_index = 9, .last_array_slice_index = 9}); + RenderColorInfo gpu_slice{}; + RenderExecutorTestAccess::ResolveRenderColorTarget( + executor, scheduler.Current(), gpu_slice, 0); + Common::DebugCounters::SetEnabled(false); + Require(name, "GPU-written volume metadata clears on the GPU", + gpu_slice.image_id == color.image_id && + Common::DebugCounters::Get( + Common::DebugCounters::Counter::ColorMetadataReadbacks) == readbacks && + Common::DebugCounters::Get( + Common::DebugCounters::Counter::ColorMetadataChecks) == checks + 1 && + ReadCachedTexel(name, context, color.image_id, {0, 0, 9}) == + std::vector{0} && + ReadCachedTexel(name, context, color.image_id, {0, 0, 31}) == + std::vector{0x5a5a5a5a}, + "a volume slice's GPU-written metadata was read back or cleared other slices"); auto storage_desc = color.desc; storage_desc.type = BindingType::Storage; storage_desc.info.metadata = {}; @@ -12271,14 +12297,43 @@ public: const auto readbacks = Common::DebugCounters::Get(Common::DebugCounters::Counter::ColorMetadataReadbacks); bind(); - Common::DebugCounters::SetEnabled(false); - Require(name, "metadata readback counter", - Common::DebugCounters::Get( - Common::DebugCounters::Counter::ColorMetadataReadbacks) == readbacks + 1, - "binding GPU-written metadata did not count exactly one readback"); Require(name, "same-value raw native re-clear", read_texel() == expected, "a repeated metadata fill did not clear an already drawn target"); + const auto checks = + Common::DebugCounters::Get(Common::DebugCounters::Counter::ColorMetadataChecks); + bind(); + Require(name, "checked metadata skips rebinding", + Common::DebugCounters::Get( + Common::DebugCounters::Counter::ColorMetadataChecks) == checks, + "rebinding unwritten GPU-checked metadata repeated its clear check"); + check_expanded_metadata(); + paint(); + fill_metadata(metadata_words - 1, true); + bind(); + Require(name, "partial raw native metadata fill", retains_painted_texel(), + "a nonuniform GPU-written metadata slice cleared the whole target"); + fill_metadata(metadata_words, true); + bind(); + Require(name, "complete raw native fill after partial fill", read_texel() == expected, + "completing a GPU-written metadata fill did not restore its clear"); + if (layout == Prospero::ChannelLayout::k16_16_16_16 && !fill_case.cmask && + !fill_case.reuse_unorm) { + auto video = color.desc; + video.type = BindingType::VideoOut; + paint(); + fill_metadata(metadata_words, true); + const bool cleared = + texture_cache.FindImage(video) == color.image_id && read_texel() == expected; + std::vector keys(metadata_words); + context.GetBufferCache().ReadMemory(dcc_address, metadata_size, false); + Require(name, "video-out clear keeps its metadata keys", + cleared && + LibKernel::Memory::TryReadBacking(dcc_address, keys.data(), metadata_size) && + std::ranges::all_of(keys, + [&](uint32_t word) { return word == fill_case.fill; }), + "a video-out bind skipped its clear or expanded the flip's metadata keys"); + } paint(); WriteMetadata(context, dcc_address, metadata_size, UINT32_MAX); fill_metadata(metadata_words - 1); @@ -12287,15 +12342,15 @@ public: retains_painted_texel(), "a nonuniform metadata allocation cleared the whole target"); fill_metadata(metadata_words); - if (fill_case.goat) { - Require(name, "repeated Goat fill stays CPU-owned", - !resources.GetBufferCache().IsRegionGpuModified(dcc_address, metadata_size), - "re-clearing expanded metadata required another GPU readback"); - } bind(); Require(name, "complete fill after partial fill", read_texel() == expected, "completing the metadata overwrite did not restore its clear"); + Common::DebugCounters::SetEnabled(false); + Require(name, "GPU-written metadata stays on the GPU", + Common::DebugCounters::Get( + Common::DebugCounters::Counter::ColorMetadataReadbacks) == readbacks, + "binding GPU-written color metadata read it back"); // Consuming a clear key updates metadata backing without invalidating // a pooled image whose first texel precedes the metadata range. @@ -20434,6 +20489,7 @@ private: m_runtime_context.queue = m_queue; m_runtime_context.shader_image_int64_atomics_enabled = true; m_runtime_context.maintenance8_enabled = true; + m_runtime_context.conditional_rendering_enabled = true; m_runtime_context.attachment_feedback_loop_enabled = m_rasterization_supported; m_runtime_context.provoking_vertex_last_enabled = m_rasterization_supported; const vk::PhysicalDeviceImageFormatInfo2 block_texel_view_info{ @@ -20717,7 +20773,10 @@ private: vk::PhysicalDeviceRobustness2FeaturesEXT robustness2{}; robustness2.nullDescriptor = true; robustness2.pNext = &maintenance8; - device_info.pNext = &robustness2; + vk::PhysicalDeviceConditionalRenderingFeaturesEXT conditional_rendering{}; + conditional_rendering.conditionalRendering = true; + conditional_rendering.pNext = &robustness2; + device_info.pNext = &conditional_rendering; vk::PhysicalDeviceFeatures device_features{}; device_features.shaderStorageImageWriteWithoutFormat = true; device_features.shaderImageGatherExtended = true; @@ -20736,7 +20795,8 @@ private: VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME, VK_KHR_MAINTENANCE_8_EXTENSION_NAME, - VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME}; + VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME, + VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME}; if (m_rasterization_supported) { device_extensions.push_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME); device_extensions.push_back(VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME);