mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-29 06:16:55 +01:00
shader: share formatted indirect buffer load emission
This commit is contained in:
@@ -118,7 +118,7 @@ struct EmitterState {
|
||||
uint32_t bda_pointer_function = 0;
|
||||
uint32_t indirect_format_function = 0;
|
||||
uint32_t indirect_format_geometry_function = 0;
|
||||
std::map<std::array<uint32_t, 3>, uint32_t> indirect_buffer_load_functions;
|
||||
std::map<std::array<uint32_t, 4>, uint32_t> indirect_buffer_load_functions;
|
||||
uint32_t bvh_intersect_function = 0;
|
||||
uint32_t gds_variable = 0;
|
||||
uint32_t gds_length = 0;
|
||||
@@ -540,7 +540,6 @@ uint32_t EmitF32BitsToF16RtzBits(EmitterState& state, uint32_t bits);
|
||||
void EmitProgram(EmitterState& state);
|
||||
|
||||
void DefineGetBdaPointer(EmitterState& state);
|
||||
void DefineIndirectBufferFormats(ValueEmitContext& ctx);
|
||||
void DefineIndirectBufferLoads(ValueEmitContext& ctx);
|
||||
void DefineBvhIntersect(EmitterState& state);
|
||||
uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
|
||||
|
||||
@@ -1026,10 +1026,8 @@ uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IndirectBufferAccess& b
|
||||
return ConstructU32Composite(state, components, values);
|
||||
}
|
||||
|
||||
uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IndirectBufferAccess& buffer) {
|
||||
auto& state = ctx.state;
|
||||
return EmitValueOrZeroIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto buffer = PrepareIndirectBuffer(state, IndirectBufferArgs(ctx, inst), true);
|
||||
const auto geometry = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry,
|
||||
state.indirect_format_geometry_function, buffer.format);
|
||||
@@ -1072,7 +1070,6 @@ uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
state.indirect_format_function, buffer.format, buffer.selector,
|
||||
component, raw);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
@@ -1135,15 +1132,12 @@ uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
|
||||
uint32_t LoadBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
|
||||
auto& state = ctx.state;
|
||||
const auto mem = ctx.Memory(inst);
|
||||
if (mem.kind == IR::ResourceKind::IndirectBuffer && mem.formatted) {
|
||||
return LoadIndirectFormattedX(ctx, inst);
|
||||
}
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components),
|
||||
ConstantU32CompositeZero(state, components), [&]() {
|
||||
if (mem.kind == IR::ResourceKind::IndirectBuffer) {
|
||||
const auto function = state.indirect_buffer_load_functions.at(
|
||||
{mem.data_bits, components, uint32_t(mem.coherent)});
|
||||
{mem.data_bits, components, uint32_t(mem.coherent), uint32_t(mem.formatted)});
|
||||
const auto args = IndirectBufferArgs(ctx, inst);
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, components),
|
||||
@@ -1231,62 +1225,8 @@ void StoreWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU64(state), result,
|
||||
state.bda_pointer_function, address);
|
||||
return result;
|
||||
}
|
||||
|
||||
void DefineIndirectBufferLoads(ValueEmitContext& ctx) {
|
||||
auto& state = ctx.state;
|
||||
for (const auto* block: state.program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
const auto components = IR::BufferComponentCount(inst.GetOpcode());
|
||||
if (components == 0u || inst.GetOpcode() == IR::ValueOpcode::ReadConstBuffer) continue;
|
||||
const auto& memory = ctx.Memory(inst);
|
||||
if (memory.kind != IR::ResourceKind::IndirectBuffer || memory.formatted ||
|
||||
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) continue;
|
||||
auto [entry, inserted] = state.indirect_buffer_load_functions.try_emplace(
|
||||
std::array {memory.data_bits, components, uint32_t(memory.coherent)}, 0u);
|
||||
if (!inserted) continue;
|
||||
const auto function = entry->second = state.builder.AllocateId();
|
||||
const auto type = TypeU32(state);
|
||||
const auto result_type = TypeU32Composite(state, components);
|
||||
const auto signature = state.builder.Type(
|
||||
spv::OpTypeFunction, result_type, type, type, type, type, type, type, type, type);
|
||||
const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" +
|
||||
std::to_string(components) + (memory.coherent ? "_coherent" : "");
|
||||
state.builder.AddName(function, name.c_str());
|
||||
state.builder.AddFunction(spv::OpFunction, result_type, function,
|
||||
spv::FunctionControlMaskNone, signature);
|
||||
std::array<uint32_t, 8> args;
|
||||
for (auto& arg: args) {
|
||||
arg = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionParameter, type, arg);
|
||||
}
|
||||
EmitLabel(state, state.builder.AllocateId());
|
||||
const auto result = LoadIndirectBuffer(ctx, PrepareIndirectBuffer(state, args, false),
|
||||
memory.data_bits, components, memory.coherent);
|
||||
state.builder.AddFunction(spv::OpReturnValue, result);
|
||||
state.builder.AddFunction(spv::OpFunctionEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
|
||||
auto& state = ctx.state;
|
||||
const auto needed = std::ranges::any_of(state.program.blocks, [&](const IR::Block* block) {
|
||||
return std::ranges::any_of(*block, [&](const IR::Inst& inst) {
|
||||
if (inst.GetOpcode() != IR::ValueOpcode::LoadBufferU32) return false;
|
||||
const auto& memory = ctx.Memory(inst);
|
||||
return memory.kind == IR::ResourceKind::IndirectBuffer && memory.formatted;
|
||||
});
|
||||
});
|
||||
if (!needed) return;
|
||||
|
||||
const auto type = TypeU32(state);
|
||||
constexpr auto format_count =
|
||||
static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u;
|
||||
@@ -1358,6 +1298,57 @@ void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
|
||||
state.builder.AddFunction(spv::OpFunctionEnd);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionCall, TypeU64(state), result,
|
||||
state.bda_pointer_function, address);
|
||||
return result;
|
||||
}
|
||||
|
||||
void DefineIndirectBufferLoads(ValueEmitContext& ctx) {
|
||||
auto& state = ctx.state;
|
||||
for (const auto* block: state.program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
const auto components = IR::BufferComponentCount(inst.GetOpcode());
|
||||
if (components == 0u || inst.GetOpcode() == IR::ValueOpcode::ReadConstBuffer) continue;
|
||||
const auto& memory = ctx.Memory(inst);
|
||||
if (memory.kind != IR::ResourceKind::IndirectBuffer ||
|
||||
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) continue;
|
||||
auto [entry, inserted] = state.indirect_buffer_load_functions.try_emplace(
|
||||
std::array {memory.data_bits, components, uint32_t(memory.coherent),
|
||||
uint32_t(memory.formatted)}, 0u);
|
||||
if (!inserted) continue;
|
||||
if (memory.formatted && state.indirect_format_function == 0u) {
|
||||
DefineIndirectBufferFormats(ctx);
|
||||
}
|
||||
const auto function = entry->second = state.builder.AllocateId();
|
||||
const auto type = TypeU32(state);
|
||||
const auto result_type = TypeU32Composite(state, components);
|
||||
const auto signature = state.builder.Type(
|
||||
spv::OpTypeFunction, result_type, type, type, type, type, type, type, type, type);
|
||||
const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" +
|
||||
std::to_string(components) + (memory.coherent ? "_coherent" : "") +
|
||||
(memory.formatted ? "_formatted" : "");
|
||||
state.builder.AddName(function, name.c_str());
|
||||
state.builder.AddFunction(spv::OpFunction, result_type, function,
|
||||
spv::FunctionControlMaskNone, signature);
|
||||
std::array<uint32_t, 8> args;
|
||||
for (auto& arg: args) {
|
||||
arg = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFunctionParameter, type, arg);
|
||||
}
|
||||
EmitLabel(state, state.builder.AllocateId());
|
||||
const auto buffer = PrepareIndirectBuffer(state, args, memory.formatted);
|
||||
const auto result = memory.formatted ? LoadIndirectFormattedX(ctx, buffer)
|
||||
: LoadIndirectBuffer(ctx, buffer, memory.data_bits, components, memory.coherent);
|
||||
state.builder.AddFunction(spv::OpReturnValue, result);
|
||||
state.builder.AddFunction(spv::OpFunctionEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DefineGetBdaPointer(EmitterState& state) {
|
||||
if (!state.program.info.uses_dma) {
|
||||
return;
|
||||
|
||||
@@ -675,7 +675,6 @@ void EmitProgram(EmitterState& state) {
|
||||
}
|
||||
DefineGetBdaPointer(state);
|
||||
DefineIndirectBufferLoads(ctx);
|
||||
DefineIndirectBufferFormats(ctx);
|
||||
DefineBvhIntersect(state);
|
||||
if (program.info.uses_swizzle) {
|
||||
ctx.scratch_u32_variable = state.builder.AllocateId();
|
||||
|
||||
@@ -33965,10 +33965,12 @@ TestCase BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) {
|
||||
TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u, bool formatted = false) {
|
||||
const bool scalar = width == 1u;
|
||||
const bool xyz = width == 3u;
|
||||
using O = ShaderOpcode;
|
||||
const u32 descriptor_format = formatted
|
||||
? (u32(Prospero::BufferFormat::k32UInt) << 12u) | 0x204u : 0x5204u;
|
||||
constexpr uint64_t GuestBase = 0x0000000110000000ull;
|
||||
struct DescriptorCase {
|
||||
u32 stride, records, mode, soffset;
|
||||
@@ -33994,7 +33996,8 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) {
|
||||
// OFFEN byte offset 5 aligns to DWORD; swizzled offsets use eight-index groups.
|
||||
constexpr u32 scalar_expected[] = {2, 2, 2, 2, 2, 0, 3, 0, 10, 10, 0};
|
||||
TestCase test;
|
||||
test.name = scalar ? "BufferLoadDwordGpuSelectedDescriptors"
|
||||
test.name = formatted ? "BufferLoadFormatXLaneAddressing"
|
||||
: scalar ? "BufferLoadDwordGpuSelectedDescriptors"
|
||||
: xyz ? "BufferLoadDwordx3GpuSelectedDescriptors"
|
||||
: "BufferLoadsGpuSelectedDescriptors";
|
||||
test.initial.resize(2048);
|
||||
@@ -34013,7 +34016,7 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) {
|
||||
const std::array<u32, 4> descriptor{
|
||||
static_cast<u32>(GuestBase + data_offset),
|
||||
(input.stride << 16u) | (input.swizzle ? 1u << 31u : 0u) | 1u,
|
||||
input.records, (input.bound ? 0x5204u : 0x204u) | (input.mode << 28u)};
|
||||
input.records, (input.bound ? descriptor_format : 0x204u) | (input.mode << 28u)};
|
||||
std::copy(descriptor.begin(), descriptor.end(), test.initial.begin() + 130 + i * 30);
|
||||
for (u32 word = 0; word < 32; ++word) {
|
||||
test.initial[data_offset / 4 + word] = i * 100 + word + 1;
|
||||
@@ -34030,7 +34033,7 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) {
|
||||
test.code.push_back(EncodeSmem1(520, 20));
|
||||
AppendSMovLiteral(&test.code, 22, cases[selected].soffset);
|
||||
AppendVMovU32(&test.code, 21, scalar ? 5u : 1u);
|
||||
test.code.push_back(EncodeMubuf0(scalar ? 0x0c : xyz ? 0x0f : 0x0e, 0, !scalar, scalar));
|
||||
test.code.push_back(EncodeMubuf0(formatted ? 0x00 : scalar ? 0x0c : xyz ? 0x0f : 0x0e, 0, !scalar, scalar));
|
||||
test.code.push_back(EncodeMubuf1(0, 2, 21, 22));
|
||||
if (!scalar) {
|
||||
test.code.push_back(EncodeMubuf0(xyz ? 0x0f : 0x0d, xyz ? 12 : 16, true, false));
|
||||
@@ -34046,7 +34049,7 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) {
|
||||
constexpr u32 payload = 6144u, output = 8192u, canary = 0xa5a5a5a5u;
|
||||
const u32 row = std::size(cases);
|
||||
const std::array<u32, 4> descriptor{u32(GuestBase + payload), (32u << 16u) | 1u,
|
||||
wave, 0x5204u | (1u << 23u)};
|
||||
wave, descriptor_format | (1u << 23u)};
|
||||
std::copy(descriptor.begin(), descriptor.end(), test.initial.begin() + 130u + row * 30u);
|
||||
test.initial[64u + row] = row;
|
||||
for (u32 lane = 0; lane < wave; ++lane)
|
||||
@@ -34065,7 +34068,7 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) {
|
||||
AppendSMovLiteral(&test.code, 127, wave == 64u ? UINT32_MAX : 0u);
|
||||
test.code.insert(test.code.end(), {EncodeVop2(0x1a, 24, InlineU32(5), 25),
|
||||
EncodeVop2(0x25, 24, 255, 24), output});
|
||||
const u32 opcode = scalar ? 0x0cu : xyz ? 0x0fu : 0x0eu;
|
||||
const u32 opcode = formatted ? 0x00u : scalar ? 0x0cu : xyz ? 0x0fu : 0x0eu;
|
||||
for (u32 component = width; component < 4u; ++component)
|
||||
AppendVMovLiteral(&test.code, 4u + component, canary);
|
||||
AppendVMovU32(&test.code, 21, 0u);
|
||||
@@ -34079,10 +34082,10 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) {
|
||||
AppendVMovU32(&test.code, 22, 0u);
|
||||
AppendSMovLiteral(&test.code, 126, 0x55555555u);
|
||||
AppendSMovLiteral(&test.code, 127, wave == 64u ? 0xaaaaaaaau : 0u);
|
||||
test.code.insert(test.code.end(), {EncodeMubuf0(opcode, 4u, true, true, true), EncodeMubuf1(4, 2, 21)});
|
||||
test.code.insert(test.code.end(), {EncodeMubuf0(opcode, 4u, true, true, !formatted), EncodeMubuf1(4, 2, 21)});
|
||||
AppendSMovLiteral(&test.code, 126, 0u);
|
||||
AppendSMovLiteral(&test.code, 127, 0u);
|
||||
test.code.insert(test.code.end(), {EncodeMubuf0(opcode, 0u, true, true, true), EncodeMubuf1(4, 2, 21)});
|
||||
test.code.insert(test.code.end(), {EncodeMubuf0(opcode, 0u, true, true, !formatted), EncodeMubuf1(4, 2, 21)});
|
||||
AppendSMovLiteral(&test.code, 126, UINT32_MAX);
|
||||
AppendSMovLiteral(&test.code, 127, wave == 64u ? UINT32_MAX : 0u);
|
||||
test.code.insert(test.code.end(), {EncodeMubuf0(0x1e, 16u, false, true), EncodeMubuf1(4, 12, 24)});
|
||||
@@ -34100,7 +34103,9 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) {
|
||||
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
|
||||
O::V_READFIRSTLANE_B32, O::S_MUL_I32, O::S_BUFFER_LOAD_DWORDX4,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
if (scalar) {
|
||||
if (formatted) {
|
||||
test.opcodes.push_back(O::BUFFER_LOAD_FORMAT_X);
|
||||
} else if (scalar) {
|
||||
test.decoded_counts = {{"BUFFER_LOAD_DWORD ", 2u * std::size(cases) + 4u}};
|
||||
} else if (xyz) {
|
||||
test.opcodes.push_back(O::BUFFER_LOAD_DWORDX3);
|
||||
@@ -34108,7 +34113,8 @@ TestCase BufferLoadsGpuSelectedDescriptors(u32 width, u32 wave = 32u) {
|
||||
test.opcodes.insert(test.opcodes.end(), {O::BUFFER_LOAD_DWORDX4, O::BUFFER_LOAD_DWORDX2});
|
||||
}
|
||||
test.opcodes.insert(test.opcodes.end(), {O::V_LSHLREV_B32, O::V_ADD_NC_U32, O::BUFFER_STORE_DWORDX4});
|
||||
test.required_spirv = {"OpConvertUToPtr", "PhysicalStorageBuffer", "Volatile"};
|
||||
test.required_spirv = {"OpConvertUToPtr", "PhysicalStorageBuffer"};
|
||||
if (!formatted) test.required_spirv.push_back("Volatile");
|
||||
return test;
|
||||
}
|
||||
|
||||
@@ -34325,6 +34331,8 @@ void CheckSharedIndirectBufferLoads(VulkanHarness &vulkan) {
|
||||
using namespace ShaderRecompiler::IR;
|
||||
for (const u32 wave : {32u, 64u}) {
|
||||
for (const auto &test : {BufferLoadsGpuSelectedDescriptors(1u, wave),
|
||||
BufferLoadsGpuSelectedDescriptors(1u, wave, true),
|
||||
BufferLoadGpuSelectedDescriptors(wave, ShaderOpcode::BUFFER_LOAD_FORMAT_X),
|
||||
BufferLoadsGpuSelectedDescriptors(3u, wave),
|
||||
BufferLoadsGpuSelectedDescriptors(4u, wave),
|
||||
BufferLoadGpuSelectedDescriptors(wave, ShaderOpcode::BUFFER_LOAD_UBYTE),
|
||||
@@ -34337,9 +34345,10 @@ void CheckSharedIndirectBufferLoads(VulkanHarness &vulkan) {
|
||||
const auto components = BufferComponentCount(inst.GetOpcode());
|
||||
if (components == 0u || inst.GetOpcode() == ValueOpcode::ReadConstBuffer) continue;
|
||||
const auto &memory = compiled.program.memory_info[inst.Flags<MemoryFlags>().index];
|
||||
if (memory.kind != ResourceKind::IndirectBuffer || memory.formatted) continue;
|
||||
if (memory.kind != ResourceKind::IndirectBuffer) continue;
|
||||
const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" +
|
||||
std::to_string(components) + (memory.coherent ? "_coherent" : "");
|
||||
std::to_string(components) + (memory.coherent ? "_coherent" : "") +
|
||||
(memory.formatted ? "_formatted" : "");
|
||||
auto &profile = profiles[name];
|
||||
profile.components = components;
|
||||
++profile.uses;
|
||||
@@ -34367,16 +34376,16 @@ void CheckSharedIndirectBufferLoads(VulkanHarness &vulkan) {
|
||||
}
|
||||
offset += words.size();
|
||||
}
|
||||
Require(test.name, "raw load function scope", functions.size() == profiles.size() && inline_reads == 0u,
|
||||
"a raw load profile was unused, missing or still emitted inline");
|
||||
Require(test.name, "indirect load function scope", functions.size() == profiles.size() && inline_reads == 0u,
|
||||
"an indirect load profile was unused, missing or still emitted inline");
|
||||
for (const auto &[name, profile] : profiles)
|
||||
Require(test.name, name.c_str(), profile.uses != 0u && profile.definitions == 1u &&
|
||||
profile.calls == profile.uses * (wave == 64u && vulkan.SubgroupSize() == 32u ? 2u : 1u) &&
|
||||
profile.reads == profile.components,
|
||||
"raw load helpers duplicated definitions, calls or component reads");
|
||||
"indirect load helpers duplicated definitions, calls or component reads");
|
||||
auto buffer = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size(), true);
|
||||
vulkan.Dispatch(test, compiled, buffer);
|
||||
CompareWords(test, "shared raw loads, lane addressing, EXEC and bounds", test.expected,
|
||||
CompareWords(test, "shared indirect loads, lane addressing, EXEC and bounds", test.expected,
|
||||
vulkan.ReadBuffer(test.name, buffer, test.expected.size()));
|
||||
vulkan.DestroyBuffer(&buffer);
|
||||
}
|
||||
@@ -41296,8 +41305,7 @@ void CheckBoundedFormattedBufferCopy(VulkanHarness &vulkan) {
|
||||
offset += words.size();
|
||||
}
|
||||
Require(test.name, "one shared formatted conversion",
|
||||
conversion_definitions == 1u &&
|
||||
conversion_calls == (wave > vulkan.SubgroupSize() ? 2u : 1u),
|
||||
conversion_definitions == 1u && conversion_calls == 1u,
|
||||
"formatted loads must call one conversion function across virtual halves");
|
||||
Require(test.name, "one shared pure format geometry",
|
||||
geometry_definitions == 1u && geometry_calls == conversion_calls &&
|
||||
|
||||
Reference in New Issue
Block a user