renderer: preserve guest writes to reused metadata buffers

This commit is contained in:
nmzik
2026-10-13 10:57:25 +02:00
parent f57d2c0965
commit 01ce973882
3 changed files with 98 additions and 43 deletions
+1 -1
View File
@@ -201,7 +201,7 @@ private:
void BindImage(ImageId id, bool storage); void BindImage(ImageId id, bool storage);
void BindRenderTarget(ImageId id); void BindRenderTarget(ImageId id);
void ResetBindings(); void ResetBindings();
[[nodiscard]] bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input, void UpdateComputeMetaClears(const ShaderComputeInputInfo& input,
const CommandBuffer& buffer); const CommandBuffer& buffer);
[[nodiscard]] bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, [[nodiscard]] bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input,
CommandBuffer& command, uint32_t group_x, CommandBuffer& command, uint32_t group_x,
@@ -49,7 +49,7 @@ static bool FillSourcesDisjoint(std::span<const ShaderRecompiler::IR::Descriptor
return true; return true;
} }
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input, void RenderExecutor::UpdateComputeMetaClears(const ShaderComputeInputInfo& input,
const CommandBuffer& buffer) { const CommandBuffer& buffer) {
const auto& program = *input.stage.program; const auto& program = *input.stage.program;
const auto& resources = *input.stage.resources; const auto& resources = *input.stage.resources;
@@ -60,10 +60,9 @@ bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& in
for (uint32_t i = 0; i < program.info.buffers.size(); i++) { for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& resource = program.info.buffers[i]; const auto& resource = program.info.buffers[i];
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]); const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
// A metadata resource that is also read is not proven to be a full overwrite. Execute it // Reads and transforms of metadata do not establish a clear state.
// conservatively instead of replacing the dispatch with a coarse full-surface clear.
if ((!resource.written || resource.read) && cache.IsMeta(descriptor.Base48())) { if ((!resource.written || resource.read) && cache.IsMeta(descriptor.Base48())) {
return false; return;
} }
} }
@@ -73,14 +72,11 @@ bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& in
if (resource.written) { if (resource.written) {
const auto descriptor = const auto descriptor =
DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]); DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
if (cache.ClearMeta(descriptor.Base48())) { (void)cache.ClearMeta(descriptor.Base48());
return true;
} }
} }
} }
} }
return false;
}
bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t group_x, bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode, uint32_t group_y, uint32_t group_z, uint32_t mode,
@@ -306,13 +302,15 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
const auto& program = *input_info.stage.program; const auto& program = *input_info.stage.program;
const auto& resources = *input_info.stage.resources; const auto& resources = *input_info.stage.resources;
if (resources.specialization_reads.empty() && if (resources.specialization_reads.empty()) {
(TryConsumeComputeMetaClear(input_info, buffer) || // Cached metadata addresses can be reused as ordinary buffers; preserve their guest writes.
TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y, UpdateComputeMetaClears(input_info, buffer);
thread_group_z, mode))) { if (TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y,
thread_group_z, mode)) {
ResetBindings(); ResetBindings();
return; return;
} }
}
if (use_thread_dimensions) { if (use_thread_dimensions) {
const uint32_t old_x = thread_group_x; const uint32_t old_x = thread_group_x;
+74 -17
View File
@@ -416,10 +416,10 @@ struct RenderExecutorTestAccess {
return executor.TryConsumeComputeImageClear(input, command, x, y, z, mode); return executor.TryConsumeComputeImageClear(input, command, x, y, z, mode);
} }
static bool TryConsumeComputeMetaClear(RenderExecutor &executor, static void UpdateComputeMetaClears(RenderExecutor &executor,
const ShaderComputeInputInfo &input, const ShaderComputeInputInfo &input,
const CommandBuffer &buffer) { const CommandBuffer &buffer) {
return executor.TryConsumeComputeMetaClear(input, buffer); executor.UpdateComputeMetaClears(input, buffer);
} }
static TextureBinding static TextureBinding
@@ -5111,16 +5111,34 @@ public:
void CheckComputeMetaClearClassification() { void CheckComputeMetaClearClassification() {
constexpr const char *name = "ComputeMetaClearClassification"; constexpr const char *name = "ComputeMetaClearClassification";
constexpr uintptr_t base = 0x0000000204200000ull;
constexpr uint64_t allocation_size = 0x10000;
constexpr uint64_t read_only_meta = 0x0000000204201f00ull; constexpr uint64_t read_only_meta = 0x0000000204201f00ull;
constexpr uint64_t read_write_meta = 0x0000000204202000ull; constexpr uint64_t read_write_meta = 0x0000000204202000ull;
constexpr uint64_t write_only_meta = 0x0000000204202100ull; constexpr uint64_t write_only_meta = 0x0000000204202100ull;
EnsureRuntimeContext(); EnsureRuntimeContext();
int64_t direct_offset = -1;
Require(name, "scratch allocation",
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
allocation_size, allocation_size, 0, &direct_offset) == 0,
"metadata scratch allocation failed");
void *mapped = reinterpret_cast<void *>(base);
Require(name, "scratch mapping",
Libs::LibKernel::Memory::KernelMapDirectMemory(
&mapped, allocation_size, 0x3, 0x10, direct_offset,
allocation_size) == 0 && mapped == reinterpret_cast<void *>(base),
"metadata scratch mapping failed");
std::memset(mapped, 0xa5, allocation_size);
RenderContext context(m_runtime_context); RenderContext context(m_runtime_context);
context.InitializeGpu(nullptr);
LibKernel::Memory::InstallGpuResources(&context);
auto &scheduler = context.GetCommandScheduler(); auto &scheduler = context.GetCommandScheduler();
HW::Context registers{}; HW::Context registers{};
HW::UserConfig user_config{}; HW::UserConfig user_config{};
HW::Shader shaders{}; HW::Shader shaders{};
scheduler.Begin(registers, user_config, shaders); scheduler.Begin(registers, user_config, shaders);
context.MapMemory(base, allocation_size);
auto &texture_cache = context.GetTextureCache(); auto &texture_cache = context.GetTextureCache();
TextureCacheTestAccess::RegisterHtileMeta(texture_cache, read_only_meta); TextureCacheTestAccess::RegisterHtileMeta(texture_cache, read_only_meta);
TextureCacheTestAccess::RegisterHtileMeta(texture_cache, read_write_meta); TextureCacheTestAccess::RegisterHtileMeta(texture_cache, read_write_meta);
@@ -5179,11 +5197,9 @@ public:
const auto read_only_input = const auto read_only_input =
MakeInput(read_only_meta, true, false, read_only_program, MakeInput(read_only_meta, true, false, read_only_program,
read_only_snapshot); read_only_snapshot);
const bool read_only_consumed = RenderExecutorTestAccess::UpdateComputeMetaClears(
RenderExecutorTestAccess::TryConsumeComputeMetaClear(
executor, read_only_input, command); executor, read_only_input, command);
Require(name, "read-only dispatch preserved", Require(name, "read-only metadata preserved",
!read_only_consumed &&
!texture_cache.IsMetaCleared(read_only_meta, 0), !texture_cache.IsMetaCleared(read_only_meta, 0),
"a metadata read-only dispatch changed logical clear state"); "a metadata read-only dispatch changed logical clear state");
@@ -5192,28 +5208,69 @@ public:
const auto read_write_input = const auto read_write_input =
MakeInput(read_write_meta, true, true, read_write_program, MakeInput(read_write_meta, true, true, read_write_program,
read_write_snapshot); read_write_snapshot);
const bool read_write_consumed = RenderExecutorTestAccess::UpdateComputeMetaClears(
RenderExecutorTestAccess::TryConsumeComputeMetaClear(
executor, read_write_input, command); executor, read_write_input, command);
Require(name, "read/write dispatch preserved", Require(name, "read/write metadata preserved",
!read_write_consumed &&
!texture_cache.IsMetaCleared(read_write_meta, 0), !texture_cache.IsMetaCleared(read_write_meta, 0),
"a metadata read/modify/write dispatch was replaced by a clear"); "a metadata read/modify/write dispatch changed logical clear state");
ShaderRecompiler::IR::CompiledShaderInfo write_only_program{}; ShaderRecompiler::IR::CompiledShaderInfo write_only_program{};
ShaderRecompiler::IR::ResourceSnapshot write_only_snapshot; ShaderRecompiler::IR::ResourceSnapshot write_only_snapshot;
const auto write_only_input = const auto write_only_input =
MakeInput(write_only_meta, false, true, write_only_program, MakeInput(write_only_meta, false, true, write_only_program,
write_only_snapshot); write_only_snapshot);
const bool write_only_consumed = RenderExecutorTestAccess::UpdateComputeMetaClears(
RenderExecutorTestAccess::TryConsumeComputeMetaClear(
executor, write_only_input, command); executor, write_only_input, command);
Require(name, "write-only fill consumed", Require(name, "write-only metadata clear state",
write_only_consumed &&
texture_cache.IsMetaCleared(write_only_meta, 0), texture_cache.IsMetaCleared(write_only_meta, 0),
"a metadata write-only fill was not consumed as a clear"); "a metadata write-only dispatch did not update logical clear state");
context.GetGpu().SendCommandSync([&] {
// Scratch reuse retains an old HTile registration, but both native stores
// still have to execute without clearing the unwritten adjacent words.
TextureCacheTestAccess::RegisterHtileMeta(texture_cache, write_only_meta);
std::vector<u32> code;
AppendVMovU32(&code, 1, 0);
for (const auto offset : {0u, 8u}) {
code.push_back(EncodeMubuf0(0x1cu, offset, false, false));
code.push_back(EncodeMubuf1(1, 0, 0));
}
AppendEnd(&code);
ShaderMapUserData(reinterpret_cast<uint64_t>(code.data()),
{.type = Prospero::ShaderBinaryType::kCs,
.code_size_bytes = static_cast<uint32_t>(code.size() * sizeof(u32))});
shaders.SetCsShader({.data_addr = reinterpret_cast<uint64_t>(code.data()),
.num_thread_x = 1, .num_thread_y = 1, .num_thread_z = 1,
.wave_size = 64, .user_sgpr = 4});
ShaderBufferResource descriptor{};
descriptor.UpdateAddress48(write_only_meta);
descriptor.fields[2] = 12;
descriptor.fields[3] = DstSel(4, 5, 6, 7) |
(static_cast<u32>(Prospero::BufferFormat::k32UInt) << 12u);
for (u32 i = 0; i < 4; i++) {
shaders.SetCsUserSgpr(i, descriptor.fields[i], HW::UserSgprType::Unknown);
}
executor.DispatchDirect(0, scheduler.Current(), 1, 1, 1, 0x41u, false);
std::array<u32, 4> actual{};
context.GetBufferCache().ReadMemory(write_only_meta, sizeof(actual));
Require(name, "metadata scratch stores and guards",
Libs::LibKernel::Memory::TryReadBacking(
write_only_meta, actual.data(), sizeof(actual)) &&
actual == std::array<u32, 4>{0, 0xa5a5a5a5u, 0, 0xa5a5a5a5u},
"metadata registration suppressed guest stores or erased adjacent data");
context.UnmapMemory(base, allocation_size);
scheduler.Finish(); scheduler.Finish();
std::printf("[host] %-32s ok\n", name); });
LibKernel::Memory::InstallGpuResources(nullptr);
context.ShutdownGpu();
Require(name, "scratch unmap",
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
"metadata scratch unmap failed");
Require(name, "scratch release",
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
direct_offset, allocation_size) == 0,
"metadata scratch release failed");
std::printf("[gpu] %-32s ok\n", name);
} }
void CheckBdaPageTableUploads() { void CheckBdaPageTableUploads() {