mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 03:16:54 +02:00
graphics: bound unbounded buffer reads and writes (#1066)
Co-authored-by: João Lobo <joaodlobo@gmail.com>
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
Reference in New Issue
Block a user