renderer: drop the per-draw scalar-read overlap guard

This commit is contained in:
nmzik committed 2026-10-30 04:19:05 +01:00
1 parent a5cfdcfd32
commit eb3cb47296
6 files changed
+11 -53

No files matched your search

@@ -934,43 +934,6 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
(pipeline_bind_point == vk::PipelineBindPoint::eCompute && (pipeline_bind_point == vk::PipelineBindPoint::eCompute &&
shader_stage != vk::ShaderStageFlagBits::eCompute)); shader_stage != vk::ShaderStageFlagBits::eCompute));
} }
for (const auto* reader: prepared_bindings) {
const auto& reads = reader->runtime->resources->specialization_reads;
if (reads.empty()) continue;
for (const auto* writer: prepared_bindings) {
if (writer->runtime->module->info.has_address_writes) {
EXIT("scalar resource reads cannot be proven disjoint from shader address writes\n");
}
}
for (const auto [address, size]: reads) {
for (const auto id: m_bound_images) {
const auto& image = m_context.GetTextureCache().m_slot_images[id];
if (!image.binding.shader_write && !image.binding.is_target) continue;
for (const auto written: {image.info.data, image.info.stencil,
image.info.metadata.range}) {
if (written.size != 0 && ImageRangeOverlaps(address, size,
written.address, written.size)) {
EXIT("scalar resource reads overlap an image or attachment write\n");
}
}
}
for (const auto* writer: prepared_bindings) {
const auto& program = writer->runtime->module->info;
for (const auto& resource: program.info.buffers) {
if (!resource.written || resource.descriptor_index == UINT32_MAX) continue;
const auto& written = writer->buffer_sources[resource.descriptor_index];
// ADD_TID and swizzle move fixed-offset writes past write_extent.
const bool linear = (resource.packed_stride & ((1u << 14u) | (1u << 20u))) == 0u;
const auto written_size =
linear ? std::min<uint64_t>(written.size, resource.write_extent) : written.size;
if (written_size != 0 && ImageRangeOverlaps(address, size,
written.address, written_size)) {
EXIT("scalar resource reads overlap a shader buffer write\n");
}
}
}
}
}
if (pipeline.uses_image_heap) { if (pipeline.uses_image_heap) {
m_context.GetImageHeap().Commit(buffer, pipeline_bind_point, pipeline.pipeline_layout); m_context.GetImageHeap().Commit(buffer, pipeline_bind_point, pipeline.pipeline_layout);
} }
@@ -148,7 +148,7 @@ template <class S, class T> void Io(S& io, T& value) {
IoAll(io, __VA_ARGS__); \ IoAll(io, __VA_ARGS__); \
} }
KYTY_IO_FIELDS(MemoryInfo, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y) KYTY_IO_FIELDS(MemoryInfo, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y)
KYTY_IO_FIELDS(BufferResource, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s) KYTY_IO_FIELDS(BufferResource, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r)
KYTY_IO_FIELDS(ImageResource, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w) KYTY_IO_FIELDS(ImageResource, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w)
KYTY_IO_FIELDS(SamplerResource, a, b, c, d, e, f, g, h, i, j, k) KYTY_IO_FIELDS(SamplerResource, a, b, c, d, e, f, g, h, i, j, k)
KYTY_IO_FIELDS(SampledResourcePair, a, b, c) KYTY_IO_FIELDS(SampledResourcePair, a, b, c)
@@ -122,7 +122,6 @@ struct BufferResource {
uint32_t descriptor_index = UINT32_MAX; uint32_t descriptor_index = UINT32_MAX;
uint32_t first_use_pc = 0; uint32_t first_use_pc = 0;
uint32_t max_byte_extent = 0; uint32_t max_byte_extent = 0;
uint32_t write_extent = 0; // UINT32_MAX once a write is runtime-addressed or formatted
uint32_t packed_stride = 0; uint32_t packed_stride = 0;
Prospero::BufferFormat descriptor_format = Prospero::BufferFormat::kInvalid; Prospero::BufferFormat descriptor_format = Prospero::BufferFormat::kInvalid;
uint32_t descriptor_swizzle = DstSel(4, 5, 6, 7); uint32_t descriptor_swizzle = DstSel(4, 5, 6, 7);
@@ -1167,7 +1167,7 @@ bool BuildSamplerPlan(const ShaderInfo& base, SamplerPlan& plan) {
template <typename Predicate> template <typename Predicate>
static std::vector<ResourceBlock> ResourceControlFlow(const Program& program, const Predicate& predicate) { static std::vector<ResourceBlock> ResourceControlFlow(const Program& program, const Predicate& predicate) {
// The renderer checks captured scalar reads against final buffer and image write ranges. // Address writes can change the values control flow reads.
if (program.has_address_writes) { if (program.has_address_writes) {
return {}; return {};
} }
@@ -4022,10 +4022,10 @@ private:
} }
} }
uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, const Inst& inst, uint32_t pc) { uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) {
for (uint32_t i = 0; i < m_info.buffers.size(); i++) { for (uint32_t i = 0; i < m_info.buffers.size(); i++) {
if (m_info.buffers[i].source == source) { if (m_info.buffers[i].source == source) {
Merge(m_info.buffers[i], memory, inst, pc); Merge(m_info.buffers[i], memory, op, pc);
return i; return i;
} }
} }
@@ -4035,14 +4035,13 @@ private:
BufferResource resource; BufferResource resource;
resource.source = source; resource.source = source;
resource.first_use_pc = pc; resource.first_use_pc = pc;
Merge(resource, memory, inst, pc); Merge(resource, memory, op, pc);
m_info.buffers.push_back(resource); m_info.buffers.push_back(resource);
return static_cast<uint32_t>(m_info.buffers.size() - 1); return static_cast<uint32_t>(m_info.buffers.size() - 1);
} }
static void Merge(BufferResource& resource, const MemoryInfo& memory, const Inst& inst, static void Merge(BufferResource& resource, const MemoryInfo& memory, ValueOpcode op,
uint32_t pc) { uint32_t pc) {
const auto op = inst.GetOpcode();
const auto access = BufferAccessOf(op); const auto access = BufferAccessOf(op);
const bool atomic = access == BufferAccess::Atomic; const bool atomic = access == BufferAccess::Atomic;
const bool write = access == BufferAccess::Write || atomic; const bool write = access == BufferAccess::Write || atomic;
@@ -4050,13 +4049,6 @@ private:
resource.max_byte_extent = std::max(resource.max_byte_extent, ByteExtent(memory)); resource.max_byte_extent = std::max(resource.max_byte_extent, ByteExtent(memory));
resource.read = resource.read || !write || atomic; resource.read = resource.read || !write || atomic;
resource.written = resource.written || write; resource.written = resource.written || write;
if (write) {
// Formatted stores write the whole format; otherwise zero index, VGPR and SGPR offsets
// leave only the immediate offset.
const bool fixed = !memory.formatted && inst.Arg(1).Resolve() == Value(0u) &&
inst.Arg(2).Resolve() == Value(0u) && inst.Arg(3).Resolve() == Value(0u);
resource.write_extent = std::max(resource.write_extent, fixed ? ByteExtent(memory) : UINT32_MAX);
}
resource.atomic = resource.atomic || atomic; resource.atomic = resource.atomic || atomic;
resource.formatted = resource.formatted || memory.formatted; resource.formatted = resource.formatted || memory.formatted;
resource.scalar = resource.scalar || op == ValueOpcode::ReadConstBuffer || resource.scalar = resource.scalar || op == ValueOpcode::ReadConstBuffer ||
@@ -4226,7 +4218,7 @@ private:
m_info.uses_dma = true; m_info.uses_dma = true;
return; return;
} }
resource = AddBuffer(source, memory, inst, flags.pc); resource = AddBuffer(source, memory, op, flags.pc);
if (resource == UINT32_MAX) { if (resource == UINT32_MAX) {
Fail(flags.pc, "buffer resource limit exceeded"); Fail(flags.pc, "buffer resource limit exceeded");
} }
+4
View File
@@ -2331,6 +2331,10 @@ public:
vertex.shader_data_buffer.offset == 0, vertex.shader_data_buffer.offset == 0,
"repeated shader binding preparation allocated or retained stale state"); "repeated shader binding preparation allocated or retained stale state");
// Scalar resource reads are captured before the draw, so a stage that writes through
// computed addresses commits alongside them.
vertex_module.info.has_address_writes = true;
pixel_snapshot.specialization_reads = {{0x1000u, 4u}};
const auto pipeline = RenderExecutorTestAccess::CommitBindings( const auto pipeline = RenderExecutorTestAccess::CommitBindings(
context.GetRenderExecutor(), scheduler.Current(), vertex, pixel); context.GetRenderExecutor(), scheduler.Current(), vertex, pixel);
Require(name, "shared push data", Require(name, "shared push data",