diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index 142e36387..294889e33 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -807,30 +807,52 @@ void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime, } } -void RenderExecutor::FindBuffers(PreparedBindings& prepared) { +void RenderExecutor::FindBuffers(std::span stages) { KYTY_PROFILER_FUNCTION(); - EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime); - const auto& program = *prepared.runtime->program; - const auto& snapshot = *prepared.runtime->resources; - auto& cache = m_context.GetBufferCache(); - - prepared.buffer_sources.clear(); - const auto& layout = program.bindings; - if (layout.memory_offset_count == 0) { - return; - } - const auto& resources = layout.descriptors.front().resources; - prepared.buffer_sources.reserve(resources.size()); - for (const auto resource: resources) { - auto descriptor = DecodeNativeDescriptor(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({}); + auto& cache = m_context.GetBufferCache(); + for (auto* stage: stages) { + auto& prepared = *stage; + EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime); + const auto& program = *prepared.runtime->program; + const auto& snapshot = *prepared.runtime->resources; + prepared.buffer_sources.clear(); + const auto& layout = program.bindings; + if (layout.memory_offset_count == 0) { continue; } - const auto size = Libs::LibKernel::Memory::ClampRangeSize(address, requested_size); - prepared.buffer_sources.push_back({address, size, cache.FindBuffer(address, size)}); + const auto& resources = layout.descriptors.front().resources; + prepared.buffer_sources.reserve(resources.size()); + for (const auto resource: resources) { + const auto descriptor = DecodeNativeDescriptor(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 stages, std::span colors) { bool uses_dma = false; + FindBuffers(stages); for (auto* stage: stages) { - FindBuffers(*stage); uses_dma |= stage->runtime->program->info.uses_dma; } if (uses_dma) { diff --git a/src/graphics/host_gpu/renderer/render.h b/src/graphics/host_gpu/renderer/render.h index b8db5b081..b8ef32c30 100644 --- a/src/graphics/host_gpu/renderer/render.h +++ b/src/graphics/host_gpu/renderer/render.h @@ -161,7 +161,7 @@ public: uint32_t mode); void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared); - void FindBuffers(PreparedBindings& bindings); + void FindBuffers(std::span stages); void RebindBuffers(PreparedBindings& bindings); void RebindImages(PreparedBindings& bindings); void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point, diff --git a/src/graphics/host_gpu/renderer/renderCompute.cpp b/src/graphics/host_gpu/renderer/renderCompute.cpp index daec7af7f..38edecfab 100644 --- a/src/graphics/host_gpu/renderer/renderCompute.cpp +++ b/src/graphics/host_gpu/renderer/renderCompute.cpp @@ -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), 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) { m_context.PrepareBda(); } @@ -351,7 +352,6 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, RebindBuffers(bindings); auto vk_buffer = buffer.Handle(); - PreparedBindings* descriptor_stage = &bindings; CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline, std::span {&descriptor_stage, 1u}); 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& bindings = m_compute_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; if (program.info.uses_dma) { m_context.PrepareBda(); @@ -469,7 +470,6 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, args_addr, sizeof(vk::DispatchIndirectCommand), false); EXIT_IF(args_buffer == nullptr || (args_offset & 3u) != 0); RebindBuffers(bindings); - PreparedBindings* descriptor_stage = &bindings; CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline, std::span {&descriptor_stage, 1u}); const auto vk_buffer = buffer.Handle(); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index ebce9aa61..7ef06c8d2 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -11770,7 +11770,8 @@ public: value.dword_count = 4; PreparedBindings buffer_bindings; executor.PrepareBindings(buffer_runtime, buffer_bindings); - executor.FindBuffers(buffer_bindings); + std::array buffer_stages{&buffer_bindings}; + executor.FindBuffers(buffer_stages); const auto original_id = buffer_bindings.buffer_sources[0].id; auto &buffer_cache = resources.GetBufferCache(); @@ -11803,7 +11804,8 @@ public: ShaderStageRuntime runtime{&buffer_program, &snapshot}; PreparedBindings bindings; executor.PrepareBindings(runtime, bindings); - executor.FindBuffers(bindings); + std::array stages{&bindings}; + executor.FindBuffers(stages); executor.RebindBuffers(bindings); const auto &owner = resources.GetBufferCache().GetBuffer(bindings.buffer_sources[0].id); 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"); } + { + 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 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 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, Prospero::BufferFormat::k16UNorm}) { const uint64_t alias_address = base + 0xe00000 + @@ -14521,7 +14591,8 @@ public: plane_dependency.memoryBarrierCount = 1; plane_dependency.pMemoryBarriers = &plane_barrier; const auto write_plane = [&] { - executor.FindBuffers(plane_bindings); + std::array stages{&plane_bindings}; + executor.FindBuffers(stages); executor.RebindBuffers(plane_bindings); const auto &plane_buffer = plane_bindings.buffers[0]; const vk::BufferCopy plane_copy{0, plane_buffer.offset, plane_size};