graphics: do not arm a fast clear from a read-modify-write dispatch (#428)

* graphics: do not arm a fast clear from a read-modify-write dispatch

* tests: cover compute metadata clear classification

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
Brandon Strong
2026-08-31 18:48:06 -07:00
committed by GitHub
parent 7684c343d6
commit b8a459e83d
3 changed files with 109 additions and 1 deletions
+2
View File
@@ -574,6 +574,8 @@ if(BUILD_TESTING)
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
add_test(NAME texture_cache_depth_readback
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
add_test(NAME compute_meta_clear_classification
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --compute-meta-clear-only)
add_test(NAME buffer_cache_dirty_gc
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
@@ -54,7 +54,9 @@ bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& in
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& resource = program.info.buffers[i];
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
if (!resource.written && cache.IsMeta(descriptor.Base48())) {
// A metadata resource that is also read is not proven to be a full overwrite. Execute it
// conservatively instead of replacing the dispatch with a coarse full-surface clear.
if (cache.IsMeta(descriptor.Base48()) && (!resource.written || resource.read)) {
return false;
}
}
+104
View File
@@ -357,10 +357,23 @@ struct TextureCacheTestAccess {
return cache.TryDownloadImage(id);
}
static void RegisterHtileMeta(TextureCache &cache, uint64_t address) {
std::lock_guard lock(cache.m_lock);
auto &metadata = cache.m_surface_metas[address];
metadata.type = TextureCache::MetaDataInfo::Type::HTile;
metadata.clear_mask = 0;
}
static TileManager &Tiler(TextureCache &cache) { return cache.m_tiler; }
};
struct RenderExecutorTestAccess {
static bool TryConsumeComputeMetaClear(RenderExecutor &executor,
const ShaderComputeInputInfo &input,
const CommandBuffer &buffer) {
return executor.TryConsumeComputeMetaClear(input, buffer);
}
static TextureBinding
ResolveTexture(RenderExecutor &executor,
const ShaderRecompiler::IR::ImageResource &resource,
@@ -3778,6 +3791,92 @@ public:
std::printf("[host] %-32s ok\n", name);
}
void CheckComputeMetaClearClassification() {
constexpr const char *name = "ComputeMetaClearClassification";
constexpr uint64_t read_only_meta = 0x0000000204201f00ull;
constexpr uint64_t read_write_meta = 0x0000000204202000ull;
constexpr uint64_t write_only_meta = 0x0000000204202100ull;
EnsureRuntimeContext();
RenderContext context(m_runtime_context);
auto &scheduler = context.GetCommandScheduler();
HW::Context registers{};
HW::UserConfig user_config{};
HW::Shader shaders{};
scheduler.Begin(registers, user_config, shaders);
auto &texture_cache = context.GetTextureCache();
TextureCacheTestAccess::RegisterHtileMeta(texture_cache, read_only_meta);
TextureCacheTestAccess::RegisterHtileMeta(texture_cache, read_write_meta);
TextureCacheTestAccess::RegisterHtileMeta(texture_cache, write_only_meta);
Require(name, "HTile fixture",
texture_cache.IsMeta(read_only_meta) &&
texture_cache.IsMeta(read_write_meta) &&
texture_cache.IsMeta(write_only_meta) &&
!texture_cache.IsMetaCleared(read_only_meta, 0) &&
!texture_cache.IsMetaCleared(read_write_meta, 0) &&
!texture_cache.IsMetaCleared(write_only_meta, 0),
"test HTile entries were not registered in their initial state");
const auto MakeInput = [](uint64_t address, bool read, bool written) {
auto program = std::make_shared<ShaderRecompiler::IR::Program>();
program->stage = ShaderType::Compute;
ShaderRecompiler::IR::BufferResource resource{};
resource.read = read;
resource.written = written;
resource.formatted = true;
program->info.buffers.push_back(resource);
ShaderBufferResource descriptor{};
descriptor.UpdateAddress48(address);
descriptor.fields[1] |= 16u << 16u;
descriptor.fields[2] = 8;
descriptor.fields[3] =
DstSel(4, 5, 6, 7) |
(static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32UInt)
<< 12u);
ShaderRecompiler::IR::DescriptorValue value{};
value.dword_count = 4;
std::copy_n(descriptor.fields, value.dword_count, value.dwords.begin());
auto snapshot =
std::make_shared<ShaderRecompiler::IR::ResourceSnapshot>();
snapshot->buffers.push_back(value);
ShaderComputeInputInfo input{};
input.stage.program = std::move(program);
input.stage.resources = std::move(snapshot);
return input;
};
auto &executor = context.GetRenderExecutor();
auto &command = scheduler.Current();
const auto read_only_input = MakeInput(read_only_meta, true, false);
const bool read_only_consumed =
RenderExecutorTestAccess::TryConsumeComputeMetaClear(
executor, read_only_input, command);
Require(name, "read-only dispatch preserved",
!read_only_consumed &&
!texture_cache.IsMetaCleared(read_only_meta, 0),
"a metadata read-only dispatch changed logical clear state");
const auto read_write_input = MakeInput(read_write_meta, true, true);
const bool read_write_consumed =
RenderExecutorTestAccess::TryConsumeComputeMetaClear(
executor, read_write_input, command);
Require(name, "read/write dispatch preserved",
!read_write_consumed &&
!texture_cache.IsMetaCleared(read_write_meta, 0),
"a metadata read/modify/write dispatch was replaced by a clear");
const auto write_only_input = MakeInput(write_only_meta, false, true);
const bool write_only_consumed =
RenderExecutorTestAccess::TryConsumeComputeMetaClear(
executor, write_only_input, command);
Require(name, "write-only fill consumed",
write_only_consumed &&
texture_cache.IsMetaCleared(write_only_meta, 0),
"a metadata write-only fill was not consumed as a clear");
scheduler.Finish();
std::printf("[host] %-32s ok\n", name);
}
void CheckUnifiedTextureCacheFlow() {
constexpr const char *name = "UnifiedTextureCacheFlow";
constexpr uintptr_t base = 0x0000000200600000ull;
@@ -25443,6 +25542,11 @@ int main(int argc, char **argv) {
vulkan.CheckUnifiedTextureCacheFlow();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--compute-meta-clear-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckComputeMetaClearClassification();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--layered-image-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckUnifiedImageViewCache();