texture cache: select color-metadata clears on the GPU with conditional rendering

This commit is contained in:
Mr. Big Britches
2026-10-22 11:50:35 +02:00
committed by nmzik
parent b6495a5b67
commit b54057942d
12 changed files with 311 additions and 20 deletions
+1
View File
@@ -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)
+1
View File
@@ -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,
+1
View File
@@ -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 = {};
+25
View File
@@ -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<Buffer*, uint64_t> 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);
}
+8
View File
@@ -11,6 +11,7 @@
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include <array>
#include <map>
#include <span>
#include <utility>
@@ -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 <bool async>
[[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<RangeSet, 8> m_checked_color_metadata;
MemoryTracker m_memory_tracker;
StreamBuffer m_staging_buffer;
StreamBuffer m_stream_buffer;
+69 -5
View File
@@ -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<vk::ClearAttachment, TileManager::ColorClearSlots> 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<uint8_t>(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;
}
+59 -2
View File
@@ -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<Buffer>(
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<vk::WriteDescriptorSet, 2> 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<uint32_t>(writes.size()), writes.data());
const Check push {source.base, static_cast<uint32_t>(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
@@ -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<Buffer> m_clear_predicates;
};
} // namespace Libs::Graphics
@@ -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;
}
}
}
@@ -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) {
@@ -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<void*>(create_info.pNext);
create_info.pNext = &maintenance8;
}
if (graphics.conditional_rendering_enabled) {
conditional_rendering.pNext = const_cast<void*>(create_info.pNext);
create_info.pNext = &conditional_rendering;
}
create_info.pQueueCreateInfos = &queue_create_info;
create_info.queueCreateInfoCount = 1;
create_info.enabledExtensionCount = static_cast<uint32_t>(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);
}
+72 -12
View File
@@ -10266,6 +10266,32 @@ public:
ReadCachedTexel(name, context, color.image_id, {0, 0, 31}) ==
std::vector<u32>{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<u32>{0} &&
ReadCachedTexel(name, context, color.image_id, {0, 0, 31}) ==
std::vector<u32>{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<uint32_t> 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);