graphics: bound unbounded buffer reads and writes (#1066)

Co-authored-by: João Lobo <joaodlobo@gmail.com>
This commit is contained in:
nmzik
2026-10-05 23:08:28 +04:00
parent 984010227f
commit e5dcfdb87b
4 changed files with 123 additions and 30 deletions
@@ -807,30 +807,52 @@ void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
} }
} }
void RenderExecutor::FindBuffers(PreparedBindings& prepared) { void RenderExecutor::FindBuffers(std::span<PreparedBindings* const> stages) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime); auto& cache = m_context.GetBufferCache();
const auto& program = *prepared.runtime->program; for (auto* stage: stages) {
const auto& snapshot = *prepared.runtime->resources; auto& prepared = *stage;
auto& cache = m_context.GetBufferCache(); EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
const auto& program = *prepared.runtime->program;
prepared.buffer_sources.clear(); const auto& snapshot = *prepared.runtime->resources;
const auto& layout = program.bindings; prepared.buffer_sources.clear();
if (layout.memory_offset_count == 0) { const auto& layout = program.bindings;
return; if (layout.memory_offset_count == 0) {
}
const auto& resources = layout.descriptors.front().resources;
prepared.buffer_sources.reserve(resources.size());
for (const auto resource: resources) {
auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(snapshot.buffers[resource]);
const auto address = descriptor.Base48();
const auto requested_size = descriptor.GetSize();
if (address == 0 || requested_size == 0) {
prepared.buffer_sources.push_back({});
continue; continue;
} }
const auto size = Libs::LibKernel::Memory::ClampRangeSize(address, requested_size); const auto& resources = layout.descriptors.front().resources;
prepared.buffer_sources.push_back({address, size, cache.FindBuffer(address, size)}); prepared.buffer_sources.reserve(resources.size());
for (const auto resource: resources) {
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(snapshot.buffers[resource]);
const auto address = descriptor.Base48();
auto size = descriptor.GetSize();
if (address == 0 || size == 0) {
prepared.buffer_sources.push_back({});
continue;
}
if (descriptor.NumRecords() == UINT32_MAX) {
if (program.info.buffers[resource].written) {
// Sentinel write ranges end before tables captured by any bound stage.
for (const auto* reader: stages) {
for (const auto [read_address, read_size]: reader->runtime->resources->specialization_reads) {
if (read_size == 0) continue;
if (read_address > address) {
size = std::min(size, read_address - address);
} else if (address - read_address < read_size) {
EXIT("scalar resource reads overlap a shader buffer write\n");
}
}
}
} else {
const auto& graphics = m_context.GetGraphics();
const auto limit = uint64_t {graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange} -
(graphics.StorageMinAlignment() - 1);
size = std::min({size, uint64_t {256} * 1024 * 1024, limit});
}
}
size = Libs::LibKernel::Memory::ClampRangeSize(address, size);
prepared.buffer_sources.push_back({address, size, cache.FindBuffer(address, size)});
}
} }
} }
@@ -919,8 +941,8 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const> stages, void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
std::span<RenderColorInfo> colors) { std::span<RenderColorInfo> colors) {
bool uses_dma = false; bool uses_dma = false;
FindBuffers(stages);
for (auto* stage: stages) { for (auto* stage: stages) {
FindBuffers(*stage);
uses_dma |= stage->runtime->program->info.uses_dma; uses_dma |= stage->runtime->program->info.uses_dma;
} }
if (uses_dma) { if (uses_dma) {
+1 -1
View File
@@ -161,7 +161,7 @@ public:
uint32_t mode); uint32_t mode);
void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared); void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared);
void FindBuffers(PreparedBindings& bindings); void FindBuffers(std::span<PreparedBindings* const> stages);
void RebindBuffers(PreparedBindings& bindings); void RebindBuffers(PreparedBindings& bindings);
void RebindImages(PreparedBindings& bindings); void RebindImages(PreparedBindings& bindings);
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point, void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
@@ -342,7 +342,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
std::copy(std::begin(input_info.dispatch_threads_num), std::end(input_info.dispatch_threads_num), std::copy(std::begin(input_info.dispatch_threads_num), std::end(input_info.dispatch_threads_num),
bindings.shader_data.begin() + program.bindings.dispatch_thread_dword); bindings.shader_data.begin() + program.bindings.dispatch_thread_dword);
} }
FindBuffers(bindings); PreparedBindings* descriptor_stage = &bindings;
FindBuffers(std::span {&descriptor_stage, 1u});
if (program.info.uses_dma) { if (program.info.uses_dma) {
m_context.PrepareBda(); m_context.PrepareBda();
} }
@@ -351,7 +352,6 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
RebindBuffers(bindings); RebindBuffers(bindings);
auto vk_buffer = buffer.Handle(); auto vk_buffer = buffer.Handle();
PreparedBindings* descriptor_stage = &bindings;
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline, CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline,
std::span {&descriptor_stage, 1u}); std::span {&descriptor_stage, 1u});
bool has_storage_writes = bindings.shared_memory.buffer != nullptr || bool has_storage_writes = bindings.shared_memory.buffer != nullptr ||
@@ -457,7 +457,8 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program); auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
auto& bindings = m_compute_bindings; auto& bindings = m_compute_bindings;
PrepareBindings(input_info.stage, bindings); PrepareBindings(input_info.stage, bindings);
FindBuffers(bindings); PreparedBindings* descriptor_stage = &bindings;
FindBuffers(std::span {&descriptor_stage, 1u});
const auto& program = *input_info.stage.program; const auto& program = *input_info.stage.program;
if (program.info.uses_dma) { if (program.info.uses_dma) {
m_context.PrepareBda(); m_context.PrepareBda();
@@ -469,7 +470,6 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
args_addr, sizeof(vk::DispatchIndirectCommand), false); args_addr, sizeof(vk::DispatchIndirectCommand), false);
EXIT_IF(args_buffer == nullptr || (args_offset & 3u) != 0); EXIT_IF(args_buffer == nullptr || (args_offset & 3u) != 0);
RebindBuffers(bindings); RebindBuffers(bindings);
PreparedBindings* descriptor_stage = &bindings;
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline, CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline,
std::span {&descriptor_stage, 1u}); std::span {&descriptor_stage, 1u});
const auto vk_buffer = buffer.Handle(); const auto vk_buffer = buffer.Handle();
+74 -3
View File
@@ -11770,7 +11770,8 @@ public:
value.dword_count = 4; value.dword_count = 4;
PreparedBindings buffer_bindings; PreparedBindings buffer_bindings;
executor.PrepareBindings(buffer_runtime, buffer_bindings); executor.PrepareBindings(buffer_runtime, buffer_bindings);
executor.FindBuffers(buffer_bindings); std::array<PreparedBindings *, 1> buffer_stages{&buffer_bindings};
executor.FindBuffers(buffer_stages);
const auto original_id = buffer_bindings.buffer_sources[0].id; const auto original_id = buffer_bindings.buffer_sources[0].id;
auto &buffer_cache = resources.GetBufferCache(); auto &buffer_cache = resources.GetBufferCache();
@@ -11803,7 +11804,8 @@ public:
ShaderStageRuntime runtime{&buffer_program, &snapshot}; ShaderStageRuntime runtime{&buffer_program, &snapshot};
PreparedBindings bindings; PreparedBindings bindings;
executor.PrepareBindings(runtime, bindings); executor.PrepareBindings(runtime, bindings);
executor.FindBuffers(bindings); std::array<PreparedBindings *, 1> stages{&bindings};
executor.FindBuffers(stages);
executor.RebindBuffers(bindings); executor.RebindBuffers(bindings);
const auto &owner = resources.GetBufferCache().GetBuffer(bindings.buffer_sources[0].id); const auto &owner = resources.GetBufferCache().GetBuffer(bindings.buffer_sources[0].id);
Require(name, "halfword buffer base and exact tail", Require(name, "halfword buffer base and exact tail",
@@ -11814,6 +11816,74 @@ public:
"native binding lost the byte adjustment or rounded the final halfword range"); "native binding lost the byte adjustment or rounded the final halfword range");
} }
{
constexpr uint64_t address = base + allocation_size - 0x4000 + 12;
constexpr uint64_t mapped_tail = base + allocation_size - address;
auto writer = make_buffer_program(ShaderType::Vertex, {.written = true});
ShaderRecompiler::IR::CompiledShaderInfo reader{};
reader.stage = ShaderType::Pixel;
ShaderRecompiler::IR::ResourceSnapshot writer_snapshot, reader_snapshot;
ShaderBufferResource descriptor{};
descriptor.UpdateAddress48(address);
descriptor.fields[2] = UINT32_MAX;
auto &value = writer_snapshot.buffers.emplace_back();
std::copy_n(descriptor.fields, 4, value.dwords.begin());
value.dword_count = 4;
ShaderStageRuntime writer_runtime{&writer, &writer_snapshot};
ShaderStageRuntime reader_runtime{&reader, &reader_snapshot};
PreparedBindings writer_bindings, reader_bindings;
const auto check_range = [&](uint64_t expected, const char *description) {
executor.PrepareBindings(writer_runtime, writer_bindings);
executor.PrepareBindings(reader_runtime, reader_bindings);
std::array<PreparedBindings *, 2> stages{&reader_bindings, &writer_bindings};
executor.FindBuffers(stages);
executor.RebindBuffers(writer_bindings);
const auto &source = writer_bindings.buffer_sources[0];
const auto &binding = writer_bindings.buffers[0];
const auto &owner = resources.GetBufferCache().GetBuffer(source.id);
Require(name, description, source.address == address && source.size == expected &&
binding.buffer == owner.Handle() && binding.offset <= owner.Offset(address) &&
binding.range == expected + owner.Offset(address) - binding.offset,
"descriptor discovery or rebind changed the write boundary or base adjustment");
};
writer_snapshot.specialization_reads = {{address + 0x500, 4},
{address - 0x40, 0x40}, {address + 0x80, 0}};
reader_snapshot.specialization_reads = {{address + 0x300, 4}, {address + 0x100, 4}};
check_range(0x100, "unbounded write stops before nearest cross-stage read");
reader_snapshot.specialization_reads.clear();
check_range(0x500, "unbounded write stops before same-stage read");
writer_snapshot.specialization_reads = {{address - 0x40, 0x40},
{address + mapped_tail + 0x100, 4}};
check_range(mapped_tail, "earlier and unmapped reads preserve mapped tail");
writer_snapshot.specialization_reads.clear();
check_range(mapped_tail, "unbounded write without reads preserves mapped tail");
reader_snapshot.specialization_reads = {{address + 0x100, 4}};
value.dwords[2] = 0x300;
check_range(0x300, "finite write preserves its declared range");
value.dwords[2] = UINT32_MAX;
writer.info.buffers[0].written = false;
writer.info.buffers[0].read = true;
check_range(mapped_tail, "read-only sentinel preserves mapped tail");
auto &graphics = context.GetGraphics();
auto &storage_limit = graphics.physical_device_properties.limits.maxStorageBufferRange;
const auto original_limit = storage_limit;
storage_limit = 0x2000;
value.dwords[1] |= 16u << 16u;
check_range(storage_limit - (graphics.StorageMinAlignment() - 1),
"read-only structured sentinel reserves native alignment prefix");
Require(name, "read-only native range fits device", writer_bindings.buffers[0].range <= storage_limit,
"read-only cap omitted the native descriptor alignment prefix");
value.dwords[2] = 0x300; // A finite 12 KiB descriptor remains finite despite the mock limit.
std::array<PreparedBindings *, 1> stages{&writer_bindings};
executor.FindBuffers(stages);
Require(name, "finite read exceeds mock cap unchanged", writer_bindings.buffer_sources[0].size == 0x3000,
"read-only window changed a finite descriptor's declared range");
storage_limit = original_limit;
check_range(0x3000, "finite structured read preserves native range");
value.dwords[2] = UINT32_MAX;
check_range(mapped_tail, "read-only structured sentinel preserves smaller mapping");
}
for (const auto format : {Prospero::BufferFormat::k8UNorm, for (const auto format : {Prospero::BufferFormat::k8UNorm,
Prospero::BufferFormat::k16UNorm}) { Prospero::BufferFormat::k16UNorm}) {
const uint64_t alias_address = base + 0xe00000 + const uint64_t alias_address = base + 0xe00000 +
@@ -14521,7 +14591,8 @@ public:
plane_dependency.memoryBarrierCount = 1; plane_dependency.memoryBarrierCount = 1;
plane_dependency.pMemoryBarriers = &plane_barrier; plane_dependency.pMemoryBarriers = &plane_barrier;
const auto write_plane = [&] { const auto write_plane = [&] {
executor.FindBuffers(plane_bindings); std::array<PreparedBindings *, 1> stages{&plane_bindings};
executor.FindBuffers(stages);
executor.RebindBuffers(plane_bindings); executor.RebindBuffers(plane_bindings);
const auto &plane_buffer = plane_bindings.buffers[0]; const auto &plane_buffer = plane_bindings.buffers[0];
const vk::BufferCopy plane_copy{0, plane_buffer.offset, plane_size}; const vk::BufferCopy plane_copy{0, plane_buffer.offset, plane_size};