mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
shader: bind dispatch spans while keeping GPU record counts native
Co-authored-by: TheCruZ <thecruzwtf@gmail.com>
This commit is contained in:
@@ -805,7 +805,8 @@ void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
|
void RenderExecutor::FindBuffers(PreparedBindings& prepared, uint64_t dispatch_threads_x,
|
||||||
|
uint32_t group_size_x) {
|
||||||
KYTY_PROFILER_FUNCTION();
|
KYTY_PROFILER_FUNCTION();
|
||||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||||
const auto& program = *prepared.runtime->program;
|
const auto& program = *prepared.runtime->program;
|
||||||
@@ -822,8 +823,23 @@ void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
|
|||||||
for (const auto resource: resources) {
|
for (const auto resource: resources) {
|
||||||
auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(snapshot.buffers[resource]);
|
auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(snapshot.buffers[resource]);
|
||||||
const auto address = descriptor.Base48();
|
const auto address = descriptor.Base48();
|
||||||
const auto requested_size = descriptor.GetSize();
|
if (address == 0u) {
|
||||||
if (address == 0 || requested_size == 0) {
|
prepared.buffer_sources.push_back({});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto requested_size = descriptor.GetSize();
|
||||||
|
const auto& buffer = program.info.buffers[resource];
|
||||||
|
if (buffer.dispatch_stride != 0u) {
|
||||||
|
if (dispatch_threads_x == 0u || dispatch_threads_x > uint64_t{UINT32_MAX} + 1u ||
|
||||||
|
buffer.dispatch_stride != group_size_x) {
|
||||||
|
EXIT("GPU-sized buffer requires a known matching X dispatch extent\n");
|
||||||
|
}
|
||||||
|
requested_size = (dispatch_threads_x - 1u) * descriptor.Stride() + buffer.max_byte_extent;
|
||||||
|
if (requested_size > uint64_t{UINT32_MAX} + 1u) {
|
||||||
|
EXIT("GPU-sized buffer dispatch wraps its 32-bit byte address\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (requested_size == 0) {
|
||||||
prepared.buffer_sources.push_back({});
|
prepared.buffer_sources.push_back({});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,9 @@ 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(PreparedBindings& bindings,
|
||||||
|
uint64_t dispatch_threads_x = 0,
|
||||||
|
uint32_t group_size_x = 0);
|
||||||
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,
|
||||||
|
|||||||
@@ -306,7 +306,10 @@ 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);
|
const auto dispatch_threads_x = use_thread_dimensions
|
||||||
|
? uint64_t{input_info.dispatch_threads_num[0]}
|
||||||
|
: uint64_t{thread_group_x} * input_info.threads_num[0];
|
||||||
|
FindBuffers(bindings, dispatch_threads_x, input_info.threads_num[0]);
|
||||||
if (program.info.uses_dma) {
|
if (program.info.uses_dma) {
|
||||||
m_context.PrepareBda();
|
m_context.PrepareBda();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -641,6 +641,7 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
|
|||||||
for (auto& inst: *block) {
|
for (auto& inst: *block) {
|
||||||
const auto op = inst.GetOpcode();
|
const auto op = inst.GetOpcode();
|
||||||
uint32_t first = 0;
|
uint32_t first = 0;
|
||||||
|
bool native_buffer_count = false;
|
||||||
if (op == IR::ValueOpcode::GetBufferResource) {
|
if (op == IR::ValueOpcode::GetBufferResource) {
|
||||||
if (std::ranges::any_of(inst.Uses(), [&](const IR::Use& use) {
|
if (std::ranges::any_of(inst.Uses(), [&](const IR::Use& use) {
|
||||||
return ir.memory_info[use.user->Flags<IR::MemoryFlags>().index].kind ==
|
return ir.memory_info[use.user->Flags<IR::MemoryFlags>().index].kind ==
|
||||||
@@ -649,6 +650,8 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto resource = inst.Flags<uint32_t>();
|
const auto resource = inst.Flags<uint32_t>();
|
||||||
|
native_buffer_count = resource < ir.info.buffers.size() &&
|
||||||
|
ir.info.buffers[resource].dispatch_stride != 0u;
|
||||||
first = resource < ir.info.buffers.size() &&
|
first = resource < ir.info.buffers.size() &&
|
||||||
ir.info.buffers[resource].indirect_root == resource
|
ir.info.buffers[resource].indirect_root == resource
|
||||||
? 1u
|
? 1u
|
||||||
@@ -661,6 +664,7 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (size_t index = first; index < inst.NumArgs(); index++) {
|
for (size_t index = first; index < inst.NumArgs(); index++) {
|
||||||
|
if (native_buffer_count && index == 2u) continue;
|
||||||
inst.SetArg(index, IR::Value(0u));
|
inst.SetArg(index, IR::Value(0u));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -506,7 +506,15 @@ void StoreLocalFlat(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
void StoreWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
void StoreWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||||
const MemoryResourceAccess& resource, uint32_t data) {
|
const MemoryResourceAccess& resource, uint32_t data) {
|
||||||
const auto index = EmitMemoryElementIndex(ctx.state, resource, DwordIndex(ctx, inst, mem));
|
const auto index = EmitMemoryElementIndex(ctx.state, resource, DwordIndex(ctx, inst, mem));
|
||||||
EmitIfCondition(ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
|
auto in_bounds = EmitMemoryElementInBounds(ctx.state, resource, index);
|
||||||
|
if (mem.kind == IR::ResourceKind::Buffer &&
|
||||||
|
ctx.state.program.info.buffers[mem.resource].dispatch_stride != 0u) {
|
||||||
|
const auto& handle = *inst.Arg(0).ResolveInstruction();
|
||||||
|
in_bounds = AndCondition(
|
||||||
|
ctx.state, in_bounds,
|
||||||
|
Binary(ctx.state, spv::OpULessThan, TypeBool(ctx.state), ctx.Arg(inst, 1), ctx.Arg(handle, 2)));
|
||||||
|
}
|
||||||
|
EmitIfCondition(ctx.state, in_bounds, [&]() {
|
||||||
StoreWordInBounds(ctx, resource, index, data);
|
StoreWordInBounds(ctx, resource, index, data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,9 @@ struct BufferResource {
|
|||||||
uint32_t source = 0;
|
uint32_t source = 0;
|
||||||
uint32_t first_use_pc = 0;
|
uint32_t first_use_pc = 0;
|
||||||
uint32_t max_byte_extent = 0;
|
uint32_t max_byte_extent = 0;
|
||||||
|
// Nonzero proves index = local_x + workgroup_x * dispatch_stride. NUM_RECORDS
|
||||||
|
// stays on the GPU; host binding capacity comes from the known dispatch extent.
|
||||||
|
uint32_t dispatch_stride = 0;
|
||||||
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);
|
||||||
|
|||||||
@@ -776,8 +776,8 @@ static Value FillValue(Value value, Value guard) {
|
|||||||
|
|
||||||
// Nonnegative affine coefficients for constant, local and workgroup coordinates. Reject modular
|
// Nonnegative affine coefficients for constant, local and workgroup coordinates. Reject modular
|
||||||
// arithmetic that could wrap; runtime coverage also bounds the largest invocation index.
|
// arithmetic that could wrap; runtime coverage also bounds the largest invocation index.
|
||||||
static std::optional<std::array<uint64_t, 3>> FillIndex(Value value, uint32_t axis, Value guard,
|
std::optional<std::array<uint64_t, 3>> AffineIndex(Value value, uint32_t axis, Value guard,
|
||||||
uint32_t depth = 0) {
|
uint32_t depth) {
|
||||||
value = FillValue(value, guard);
|
value = FillValue(value, guard);
|
||||||
if (depth > 32 || value.GetType() != Type::U32) {
|
if (depth > 32 || value.GetType() != Type::U32) {
|
||||||
return {};
|
return {};
|
||||||
@@ -802,8 +802,8 @@ static std::optional<std::array<uint64_t, 3>> FillIndex(Value value, uint32_t ax
|
|||||||
op != ValueOpcode::ShiftLeftLogical32) {
|
op != ValueOpcode::ShiftLeftLogical32) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
auto left = FillIndex(inst->Arg(0), axis, guard, depth + 1);
|
auto left = AffineIndex(inst->Arg(0), axis, guard, depth + 1);
|
||||||
auto right = FillIndex(inst->Arg(1), axis, guard, depth + 1);
|
auto right = AffineIndex(inst->Arg(1), axis, guard, depth + 1);
|
||||||
if (!left || !right) {
|
if (!left || !right) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -870,7 +870,7 @@ static UniformFillPlan AnalyzeUniformFill(const Program& program) {
|
|||||||
const auto* address = store->Arg(1).ResolveInstruction();
|
const auto* address = store->Arg(1).ResolveInstruction();
|
||||||
if (address == nullptr || address->GetOpcode() != ValueOpcode::MakeImageAddress) return {};
|
if (address == nullptr || address->GetOpcode() != ValueOpcode::MakeImageAddress) return {};
|
||||||
for (uint32_t axis = 0; axis < 3; ++axis) {
|
for (uint32_t axis = 0; axis < 3; ++axis) {
|
||||||
const auto index = FillIndex(address->Arg(axis), axis, guard);
|
const auto index = AffineIndex(address->Arg(axis), axis, guard);
|
||||||
if (!index || (*index)[0] != 0) return {};
|
if (!index || (*index)[0] != 0) return {};
|
||||||
if (axis < 2) {
|
if (axis < 2) {
|
||||||
if ((*index)[1] != 1 || (*index)[2] == 0) return {};
|
if ((*index)[1] != 1 || (*index)[2] == 0) return {};
|
||||||
@@ -900,7 +900,7 @@ static UniformFillPlan AnalyzeUniformFill(const Program& program) {
|
|||||||
memory.data_bits != 32 ||
|
memory.data_bits != 32 ||
|
||||||
memory.data_dwords != static_cast<uint32_t>(store_op - stores.begin() + 1))
|
memory.data_dwords != static_cast<uint32_t>(store_op - stores.begin() + 1))
|
||||||
return {};
|
return {};
|
||||||
const auto address = FillIndex(store->Arg(1), 0, guard);
|
const auto address = AffineIndex(store->Arg(1), 0, guard);
|
||||||
if (!address || (*address)[0] != 0 || (*address)[1] != 1 || (*address)[2] == 0) return {};
|
if (!address || (*address)[0] != 0 || (*address)[1] != 1 || (*address)[2] == 0) return {};
|
||||||
result.fill.kind = UniformFillKind::Buffer;
|
result.fill.kind = UniformFillKind::Buffer;
|
||||||
result.fill.group_stride[0] = static_cast<uint32_t>((*address)[2]);
|
result.fill.group_stride[0] = static_cast<uint32_t>((*address)[2]);
|
||||||
@@ -1138,6 +1138,12 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
|||||||
auto packed_stride = descriptor.PackedStride();
|
auto packed_stride = descriptor.PackedStride();
|
||||||
const auto stride = packed_stride & 0x3fffu;
|
const auto stride = packed_stride & 0x3fffu;
|
||||||
const bool swizzle = stride != 0u && ((packed_stride >> 14u) & 1u) != 0u;
|
const bool swizzle = stride != 0u && ((packed_stride >> 14u) & 1u) != 0u;
|
||||||
|
if (base.dispatch_stride != 0u && descriptor.Base48() != 0u &&
|
||||||
|
(descriptor.OutOfBounds() != 0u || stride == 0u || swizzle ||
|
||||||
|
(packed_stride & (1u << 20u)) != 0u || base.max_byte_extent > stride)) {
|
||||||
|
return SpecializationFail(fmt::format(
|
||||||
|
"dispatch-sized buffer {} requires an unswizzled structured DWORD layout", i));
|
||||||
|
}
|
||||||
if (stride == 0u) {
|
if (stride == 0u) {
|
||||||
packed_stride &= ~((1u << 14u) | (3u << 16u));
|
packed_stride &= ~((1u << 14u) | (3u << 16u));
|
||||||
} else if (!swizzle) {
|
} else if (!swizzle) {
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ struct ResourceSpecialization {
|
|||||||
// its values and is independent of the translated shader CFG.
|
// its values and is independent of the translated shader CFG.
|
||||||
ResourcePlan ExtractResourcePlan(const Program& program);
|
ResourcePlan ExtractResourcePlan(const Program& program);
|
||||||
|
|
||||||
|
// Nonnegative constant/local/workgroup coefficients under the instruction's active guard.
|
||||||
|
std::optional<std::array<uint64_t, 3>> AffineIndex(Value value, uint32_t axis, Value guard,
|
||||||
|
uint32_t depth = 0);
|
||||||
|
|
||||||
// Refreshes cached resources and specialization in place. A failed refresh must not be used.
|
// Refreshes cached resources and specialization in place. A failed refresh must not be used.
|
||||||
bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime,
|
bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||||
ResourceSnapshot& snapshot, ResourceSpecialization& specialization);
|
ResourceSnapshot& snapshot, ResourceSpecialization& specialization);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||||
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
|
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
|
||||||
|
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -1955,9 +1956,32 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) {
|
bool TryMakeDispatchBuffer(const Inst& inst, const Inst& handle, uint32_t& source,
|
||||||
|
uint32_t& dispatch_stride) {
|
||||||
|
const auto flags = inst.Flags<MemoryFlags>();
|
||||||
|
const auto& memory = m_program.memory_info[flags.index];
|
||||||
|
if (m_program.stage != ShaderType::Compute || inst.GetOpcode() != ValueOpcode::StoreBufferU32 ||
|
||||||
|
memory.kind != ResourceKind::Buffer || memory.typed || memory.formatted ||
|
||||||
|
!memory.idxen || memory.offen || memory.data_bits != 32u || memory.data_dwords != 1u ||
|
||||||
|
inst.Arg(2).Resolve() != Value(0u) || inst.Arg(3).Resolve() != Value(0u)) return false;
|
||||||
|
const auto index = AffineIndex(inst.Arg(1), 0, inst.Arg(5).Resolve());
|
||||||
|
if (!index || (*index)[0] != 0u || (*index)[1] != 1u || (*index)[2] == 0u) return false;
|
||||||
|
DescriptorSource descriptor;
|
||||||
|
MakeSource(handle, 4u, false, false, memory.resource * 4u, descriptor, flags.pc);
|
||||||
|
// Binding capacity is derived from the dispatch. The native handle keeps its count.
|
||||||
|
descriptor.dwords[2] = Value(0u);
|
||||||
|
uint32_t bad_dword = 0;
|
||||||
|
if (!ValidateSource(descriptor, bad_dword)) return false;
|
||||||
|
source = InternSource(descriptor);
|
||||||
|
dispatch_stride = static_cast<uint32_t>((*index)[2]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc,
|
||||||
|
uint32_t dispatch_stride) {
|
||||||
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 &&
|
||||||
|
m_info.buffers[i].dispatch_stride == dispatch_stride) {
|
||||||
Merge(m_info.buffers[i], memory, op, pc);
|
Merge(m_info.buffers[i], memory, op, pc);
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@@ -1968,6 +1992,7 @@ private:
|
|||||||
BufferResource resource;
|
BufferResource resource;
|
||||||
resource.source = source;
|
resource.source = source;
|
||||||
resource.first_use_pc = pc;
|
resource.first_use_pc = pc;
|
||||||
|
resource.dispatch_stride = dispatch_stride;
|
||||||
Merge(resource, memory, op, 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);
|
||||||
@@ -2123,12 +2148,14 @@ private:
|
|||||||
uint32_t resource = 0;
|
uint32_t resource = 0;
|
||||||
|
|
||||||
if (buffer != BufferAccess::None) {
|
if (buffer != BufferAccess::None) {
|
||||||
|
uint32_t dispatch_stride = 0;
|
||||||
handle = inst.Arg(0).Resolve().TryInstruction();
|
handle = inst.Arg(0).Resolve().TryInstruction();
|
||||||
const auto* indirect = handle == nullptr ? nullptr : FindIndirectDescriptor(*handle);
|
const auto* indirect = handle == nullptr ? nullptr : FindIndirectDescriptor(*handle);
|
||||||
if (indirect != nullptr) {
|
if (indirect != nullptr) {
|
||||||
source = indirect->source;
|
source = indirect->source;
|
||||||
} else if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc,
|
} else if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc,
|
||||||
memory.resource * 4u, handle, source)) {
|
memory.resource * 4u, handle, source) &&
|
||||||
|
!TryMakeDispatchBuffer(inst, *handle, source, dispatch_stride)) {
|
||||||
if (memory.kind != (op == ValueOpcode::ReadConstBuffer ? ResourceKind::ScalarBuffer
|
if (memory.kind != (op == ValueOpcode::ReadConstBuffer ? ResourceKind::ScalarBuffer
|
||||||
: ResourceKind::Buffer) ||
|
: ResourceKind::Buffer) ||
|
||||||
!memory.SupportsIndirectBufferLoad(op)) {
|
!memory.SupportsIndirectBufferLoad(op)) {
|
||||||
@@ -2140,7 +2167,7 @@ private:
|
|||||||
m_info.uses_dma = true;
|
m_info.uses_dma = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resource = AddBuffer(source, memory, op, flags.pc);
|
resource = AddBuffer(source, memory, op, flags.pc, dispatch_stride);
|
||||||
if (resource == UINT32_MAX) {
|
if (resource == UINT32_MAX) {
|
||||||
Fail(flags.pc, "buffer resource limit exceeded");
|
Fail(flags.pc, "buffer resource limit exceeded");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,6 +196,45 @@ void TestRawScalarAddressPreservesHighBits() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestDispatchBufferKeepsOnlyBindingLayoutOnHost() {
|
||||||
|
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||||
|
auto plan = UserDataBufferPlan();
|
||||||
|
plan.info.buffers[0].dispatch_stride = 64;
|
||||||
|
plan.info.buffers[0].max_byte_extent = 20;
|
||||||
|
auto &source = plan.descriptor_sources[0];
|
||||||
|
source.dwords[1] = Value(20u << 16u);
|
||||||
|
source.dwords[3] = Value(0x16204u);
|
||||||
|
std::array<uint32_t, 1> user_data{0x1000u};
|
||||||
|
uint32_t reads = 0;
|
||||||
|
const SrtRuntime runtime{.user_data = user_data,
|
||||||
|
.read_memory = RejectSpecializationRead,
|
||||||
|
.userdata = &reads,
|
||||||
|
.read_specialization_memory = RejectSpecializationRead};
|
||||||
|
ResourceSnapshot snapshot;
|
||||||
|
ResourceSpecialization specialization;
|
||||||
|
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||||
|
snapshot.buffers[0].dwords[2] == 0 &&
|
||||||
|
specialization.buffers[0].packed_stride == 20 && reads == 0,
|
||||||
|
"native record count was required to materialize its stable binding layout");
|
||||||
|
for (const auto words : {std::array<uint32_t, 2>{0u, 0x16204u},
|
||||||
|
{16u << 16u, 0x16204u},
|
||||||
|
{(20u << 16u) | (1u << 31u), 0x16204u},
|
||||||
|
{20u << 16u, 0x16204u | (1u << 23u)},
|
||||||
|
{20u << 16u, 0x16204u | (1u << 28u)}}) {
|
||||||
|
source.dwords[1] = Value(words[0]);
|
||||||
|
source.dwords[3] = Value(words[1]);
|
||||||
|
Check(!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||||
|
"dispatch buffer accepted a layout outside its native bounds proof");
|
||||||
|
}
|
||||||
|
user_data[0] = 0;
|
||||||
|
source.dwords[1] = Value(0u);
|
||||||
|
source.dwords[3] = Value(0u);
|
||||||
|
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||||
|
specialization.buffers[0].packed_stride == 0,
|
||||||
|
"inactive dispatch buffer did not preserve null binding semantics");
|
||||||
|
Check(reads == 0, "dispatch buffer layout validation read GPU data on the host");
|
||||||
|
}
|
||||||
|
|
||||||
void TestIntegerRuntimeValueFollowsSrtReads() {
|
void TestIntegerRuntimeValueFollowsSrtReads() {
|
||||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||||
auto plan = SrtPlan(0x10000);
|
auto plan = SrtPlan(0x10000);
|
||||||
@@ -488,6 +527,7 @@ void DbgExit(int) { std::abort(); }
|
|||||||
int main() {
|
int main() {
|
||||||
TestMappedSrtUsesDirectReaderByDefault();
|
TestMappedSrtUsesDirectReaderByDefault();
|
||||||
TestRawScalarAddressPreservesHighBits();
|
TestRawScalarAddressPreservesHighBits();
|
||||||
|
TestDispatchBufferKeepsOnlyBindingLayoutOnHost();
|
||||||
TestIntegerRuntimeValueFollowsSrtReads();
|
TestIntegerRuntimeValueFollowsSrtReads();
|
||||||
TestUnbasedFlatCacheHitMaterializes();
|
TestUnbasedFlatCacheHitMaterializes();
|
||||||
TestWrittenDescriptorUsesStrictReaderOnce();
|
TestWrittenDescriptorUsesStrictReaderOnce();
|
||||||
|
|||||||
@@ -31282,6 +31282,82 @@ void CheckComputeThreadDimensions(VulkanHarness &vulkan) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CheckGpuComputedBufferRecords(VulkanHarness &vulkan) {
|
||||||
|
using namespace ShaderRecompiler::IR;
|
||||||
|
TestCase test;
|
||||||
|
test.name = "GpuComputedBufferRecords";
|
||||||
|
// Captured b71ef7c579835a0d: read a GPU counter, divide it into chunks,
|
||||||
|
// construct a structured descriptor, then clear each live record's last DWORD.
|
||||||
|
test.code = {
|
||||||
|
0xbfa00003u, 0xd7460000u, 0x04010c0eu, 0x8805ff0du, 0x00040000u, 0xbe84030cu,
|
||||||
|
0xbe860381u, 0xbe8703ffu, 0x00016204u, 0xb06a0100u, 0xe0300000u, 0x80010400u,
|
||||||
|
0x92049281u, 0x7e0a026au, 0x996a9480u, 0xb0062000u, 0xb00a0080u, 0x380212f9u,
|
||||||
|
0x8686066au, 0x7e040208u, 0x7e0602ffu, 0x00016204u, 0xbf8c3f70u, 0x7d8808ffu,
|
||||||
|
0x00000200u, 0x7d8808f9u, 0x06868404u, 0x7d8808f9u, 0x06868606u, 0x020d80f9u,
|
||||||
|
0x8686060au, 0xd5010005u, 0x00120affu, 0x00000200u, 0xd5010006u, 0x001a0d05u,
|
||||||
|
0x7e0a0d06u, 0x7e0c0506u, 0xd76d0004u, 0x041a08c1u, 0x7e0a5705u, 0x81ea0680u,
|
||||||
|
0x7e080504u, 0x100a0affu, 0x4f7ffffeu, 0x7e0a0f05u, 0x7ed60505u, 0x936a6b6au,
|
||||||
|
0x9aea6b6au, 0x816a6a6bu, 0x9a87046au, 0x936a0607u, 0x4e08086au, 0x4e0a0806u,
|
||||||
|
0x7d8608f9u, 0x06868406u, 0x7d860a06u, 0x87ea046au, 0x020902f9u, 0x86860680u,
|
||||||
|
0xd5286a04u, 0x00120807u, 0xbeea047eu, 0x7da40080u, 0xbf880003u, 0x7e0a0280u,
|
||||||
|
0xe0700000u, 0x80000500u, 0xbefe046au, 0x7e0a0280u, 0x7e000502u, 0x7e020501u,
|
||||||
|
0x7e040504u, 0x7e060503u, 0xe0702010u, 0x80000500u, 0xbf810000u,
|
||||||
|
};
|
||||||
|
constexpr u32 guest_base = 0x1000;
|
||||||
|
test.user_data[0] = guest_base + 16;
|
||||||
|
test.user_data[1] = 4u << 16;
|
||||||
|
test.user_data[2] = 1;
|
||||||
|
test.user_data[3] = 0x5204;
|
||||||
|
test.user_data[8] = guest_base + 20;
|
||||||
|
test.user_data[12] = guest_base;
|
||||||
|
test.has_user_data = true;
|
||||||
|
test.storage_buffer_offsets = {0, 16, 20};
|
||||||
|
test.initial.assign(5 + 5 * 130, 0xdeadbeefu);
|
||||||
|
test.initial[0] = 72;
|
||||||
|
test.has_compute_info = true;
|
||||||
|
test.compute_info.threads_num[0] = 64;
|
||||||
|
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
|
||||||
|
test.compute_info.lds_size_dwords = 0;
|
||||||
|
test.compute_info.wave_size = 64;
|
||||||
|
test.compute_info.float_mode = 0xc0;
|
||||||
|
test.compute_info.group_id[0] = true;
|
||||||
|
test.compute_info.thread_ids_num = 1;
|
||||||
|
test.compute_info.workgroup_register = 14;
|
||||||
|
test.compute_info.dispatch_thread_dimensions = true;
|
||||||
|
test.compute_info.dispatch_threads_num[0] = 2;
|
||||||
|
test.compute_info.dispatch_threads_num[1] = test.compute_info.dispatch_threads_num[2] = 1;
|
||||||
|
test.required_spirv = {"OpFDiv", "OpUMulExtended"};
|
||||||
|
test.forbidden_spirv = {"get_bda_pointer", "flattened_srt"};
|
||||||
|
const auto compiled = CompileCase(test, vulkan.SubgroupSize());
|
||||||
|
Require(test.name, "native record count",
|
||||||
|
compiled.program.info.buffers.size() == 3 &&
|
||||||
|
compiled.program.info.buffers[2].dispatch_stride == 64 &&
|
||||||
|
compiled.resources.buffers[2].dwords[2] == 0 &&
|
||||||
|
compiled.resources.specialization_reads.empty(),
|
||||||
|
"GPU count entered the host descriptor snapshot or lost its dispatch bound");
|
||||||
|
// Reuse the same compiled shader as both GPU data and dispatch coverage change.
|
||||||
|
for (const u32 count : {0u, 72u, 512u, 8192u, 262144u}) {
|
||||||
|
for (const u32 threads : {2u, 3u, 65u}) {
|
||||||
|
test.initial[0] = count;
|
||||||
|
test.compute_info.dispatch_threads_num[0] = threads;
|
||||||
|
test.dispatch_x = (threads + 63) / 64;
|
||||||
|
const u32 chunk = count < 512 ? 64 : count < 8192 ? 128 : count < 262144 ? 256 : 512;
|
||||||
|
const u32 records = (count + chunk - 1) / chunk;
|
||||||
|
test.expected = test.initial;
|
||||||
|
test.expected[4] = 0;
|
||||||
|
for (u32 index = 0; index < std::min(threads, records); ++index) {
|
||||||
|
test.expected[9 + index * 5] = 0;
|
||||||
|
}
|
||||||
|
auto output = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size());
|
||||||
|
vulkan.Dispatch(test, compiled, output);
|
||||||
|
const auto actual = vulkan.ReadBuffer(test.name, output, test.expected.size());
|
||||||
|
vulkan.DestroyBuffer(&output);
|
||||||
|
CompareWords(test, "GPU records and padded invocation bounds", test.expected, actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::printf("[compute] %-32s ok\n", test.name);
|
||||||
|
}
|
||||||
|
|
||||||
void CheckIndirectBufferStore(VulkanHarness &vulkan) {
|
void CheckIndirectBufferStore(VulkanHarness &vulkan) {
|
||||||
using namespace ShaderRecompiler::IR;
|
using namespace ShaderRecompiler::IR;
|
||||||
TestCase test;
|
TestCase test;
|
||||||
@@ -38378,6 +38454,7 @@ int main(int argc, char **argv) {
|
|||||||
RunCase(&vulkan, ScalarBufferFromLoopReadlane(32));
|
RunCase(&vulkan, ScalarBufferFromLoopReadlane(32));
|
||||||
RunCase(&vulkan, ScalarBufferFromLoopReadlane(64));
|
RunCase(&vulkan, ScalarBufferFromLoopReadlane(64));
|
||||||
CheckIndirectBufferStore(vulkan);
|
CheckIndirectBufferStore(vulkan);
|
||||||
|
CheckGpuComputedBufferRecords(vulkan);
|
||||||
RunCase(&vulkan, BufferLoadsGpuSelectedDescriptors());
|
RunCase(&vulkan, BufferLoadsGpuSelectedDescriptors());
|
||||||
RunCase(&vulkan, BufferLoadDwordGpuSelectedDescriptors());
|
RunCase(&vulkan, BufferLoadDwordGpuSelectedDescriptors());
|
||||||
RunCase(&vulkan, BufferLoadDwordx3GpuSelectedDescriptors());
|
RunCase(&vulkan, BufferLoadDwordx3GpuSelectedDescriptors());
|
||||||
@@ -38953,6 +39030,7 @@ int main(int argc, char **argv) {
|
|||||||
CheckPixelParameterAliases();
|
CheckPixelParameterAliases();
|
||||||
CheckRectListShaders();
|
CheckRectListShaders();
|
||||||
CheckIndirectBufferStore(vulkan);
|
CheckIndirectBufferStore(vulkan);
|
||||||
|
CheckGpuComputedBufferRecords(vulkan);
|
||||||
CheckComputeThreadDimensions(vulkan);
|
CheckComputeThreadDimensions(vulkan);
|
||||||
CheckIndirectImageKeySwitch();
|
CheckIndirectImageKeySwitch();
|
||||||
CheckWave64WholeWaveResults();
|
CheckWave64WholeWaveResults();
|
||||||
|
|||||||
Reference in New Issue
Block a user