mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 13:56:59 +01:00
shader: share runtime buffer format conversion across loads
This commit is contained in:
@@ -114,6 +114,7 @@ struct EmitterState {
|
|||||||
uint32_t bda_pagetable_variable = 0;
|
uint32_t bda_pagetable_variable = 0;
|
||||||
uint32_t fault_buffer_variable = 0;
|
uint32_t fault_buffer_variable = 0;
|
||||||
uint32_t bda_pointer_function = 0;
|
uint32_t bda_pointer_function = 0;
|
||||||
|
uint32_t indirect_format_function = 0;
|
||||||
uint32_t bvh_intersect_function = 0;
|
uint32_t bvh_intersect_function = 0;
|
||||||
uint32_t gds_variable = 0;
|
uint32_t gds_variable = 0;
|
||||||
uint32_t gds_length = 0;
|
uint32_t gds_length = 0;
|
||||||
@@ -522,6 +523,7 @@ uint32_t EmitF16BitsToF32(EmitterState& state, uint32_t bits);
|
|||||||
void EmitProgram(EmitterState& state);
|
void EmitProgram(EmitterState& state);
|
||||||
|
|
||||||
void DefineGetBdaPointer(EmitterState& state);
|
void DefineGetBdaPointer(EmitterState& state);
|
||||||
|
void DefineConvertIndirectBufferFormat(ValueEmitContext& ctx);
|
||||||
void DefineBvhIntersect(EmitterState& state);
|
void DefineBvhIntersect(EmitterState& state);
|
||||||
uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
|
uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
|
||||||
|
|
||||||
|
|||||||
@@ -1065,32 +1065,11 @@ uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte,
|
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte,
|
||||||
ConstantU32(state, 3)));
|
ConstantU32(state, 3)));
|
||||||
});
|
});
|
||||||
return EmitIndexSwitch(
|
const auto result = state.builder.AllocateId();
|
||||||
state, buffer.format, format_count, TypeU32(state), [&](uint32_t format) {
|
state.builder.AddFunction(spv::OpFunctionCall, TypeU32(state), result,
|
||||||
const auto info = Format::GetFormatInfo(static_cast<Prospero::BufferFormat>(format));
|
state.indirect_format_function, buffer.format, buffer.selector,
|
||||||
if (info.type == Format::ComponentType::Unknown) return ConstantU32(state, 0);
|
component, raw);
|
||||||
const auto constant = Select(
|
return result;
|
||||||
state, TypeU32(state),
|
|
||||||
Binary(state, spv::OpIEqual, TypeBool(state), buffer.selector, ConstantU32(state, 1)),
|
|
||||||
FormattedConstant(ctx, info, FormattedSourceKind::One), ConstantU32(state, 0));
|
|
||||||
return EmitValueOrDefaultIfCondition(
|
|
||||||
state, Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), buffer.selector,
|
|
||||||
ConstantU32(state, 4)),
|
|
||||||
TypeU32(state), constant, [&]() {
|
|
||||||
const auto emit_component = [&](uint32_t component) {
|
|
||||||
return LoadFormattedComponent(
|
|
||||||
ctx, info, {FormattedSourceKind::Memory, component},
|
|
||||||
[&](uint32_t) { return raw; },
|
|
||||||
[&](uint32_t, uint32_t bits) {
|
|
||||||
return Binary(state, spv::OpBitwiseAnd, TypeU32(state), raw,
|
|
||||||
ConstantU32(state, bits == 8u ? 0xffu : 0xffffu));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if (!info.packed_bitfield) return emit_component(0u);
|
|
||||||
return EmitIndexSwitch(state, component, info.component_count, TypeU32(state),
|
|
||||||
emit_component);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1197,6 +1176,64 @@ uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DefineConvertIndirectBufferFormat(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);
|
||||||
|
const auto function_type = state.builder.Type(spv::OpTypeFunction, type, type, type, type, type);
|
||||||
|
state.indirect_format_function = state.builder.AllocateId();
|
||||||
|
const auto format = state.builder.AllocateId();
|
||||||
|
const auto selector = state.builder.AllocateId();
|
||||||
|
const auto component = state.builder.AllocateId();
|
||||||
|
const auto raw = state.builder.AllocateId();
|
||||||
|
state.builder.AddName(state.indirect_format_function, "convert_indirect_buffer_format");
|
||||||
|
state.builder.AddFunction(spv::OpFunction, type, state.indirect_format_function,
|
||||||
|
spv::FunctionControlMaskNone, function_type);
|
||||||
|
state.builder.AddFunction(spv::OpFunctionParameter, type, format);
|
||||||
|
state.builder.AddFunction(spv::OpFunctionParameter, type, selector);
|
||||||
|
state.builder.AddFunction(spv::OpFunctionParameter, type, component);
|
||||||
|
state.builder.AddFunction(spv::OpFunctionParameter, type, raw);
|
||||||
|
EmitLabel(state, state.builder.AllocateId());
|
||||||
|
constexpr auto format_count =
|
||||||
|
static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u;
|
||||||
|
const auto result = EmitIndexSwitch(
|
||||||
|
state, format, format_count, TypeU32(state), [&](uint32_t format) {
|
||||||
|
const auto info = Format::GetFormatInfo(static_cast<Prospero::BufferFormat>(format));
|
||||||
|
if (info.type == Format::ComponentType::Unknown) return ConstantU32(state, 0);
|
||||||
|
const auto constant = Select(
|
||||||
|
state, TypeU32(state),
|
||||||
|
Binary(state, spv::OpIEqual, TypeBool(state), selector, ConstantU32(state, 1)),
|
||||||
|
FormattedConstant(ctx, info, FormattedSourceKind::One), ConstantU32(state, 0));
|
||||||
|
return EmitValueOrDefaultIfCondition(
|
||||||
|
state, Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), selector,
|
||||||
|
ConstantU32(state, 4)),
|
||||||
|
TypeU32(state), constant, [&]() {
|
||||||
|
const auto emit_component = [&](uint32_t component) {
|
||||||
|
return LoadFormattedComponent(
|
||||||
|
ctx, info, {FormattedSourceKind::Memory, component},
|
||||||
|
[&](uint32_t) { return raw; },
|
||||||
|
[&](uint32_t, uint32_t bits) {
|
||||||
|
return Binary(state, spv::OpBitwiseAnd, TypeU32(state), raw,
|
||||||
|
ConstantU32(state, bits == 8u ? 0xffu : 0xffffu));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (!info.packed_bitfield) return emit_component(0u);
|
||||||
|
return EmitIndexSwitch(state, component, info.component_count, TypeU32(state),
|
||||||
|
emit_component);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
state.builder.AddFunction(spv::OpReturnValue, result);
|
||||||
|
state.builder.AddFunction(spv::OpFunctionEnd);
|
||||||
|
}
|
||||||
|
|
||||||
void DefineGetBdaPointer(EmitterState& state) {
|
void DefineGetBdaPointer(EmitterState& state) {
|
||||||
if (!state.program.info.uses_dma) {
|
if (!state.program.info.uses_dma) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -665,6 +665,7 @@ void EmitProgram(EmitterState& state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DefineGetBdaPointer(state);
|
DefineGetBdaPointer(state);
|
||||||
|
DefineConvertIndirectBufferFormat(ctx);
|
||||||
DefineBvhIntersect(state);
|
DefineBvhIntersect(state);
|
||||||
if (program.info.uses_swizzle) {
|
if (program.info.uses_swizzle) {
|
||||||
ctx.scratch_u32_variable = state.builder.AllocateId();
|
ctx.scratch_u32_variable = state.builder.AllocateId();
|
||||||
|
|||||||
@@ -30388,6 +30388,7 @@ TestCase BufferLoadGpuSelectedDescriptors(u32 wave, ShaderOpcode load) {
|
|||||||
test.compute_info.wave_size = wave;
|
test.compute_info.wave_size = wave;
|
||||||
test.has_compute_info = true;
|
test.has_compute_info = true;
|
||||||
test.required_spirv = {"PhysicalStorageBuffer", "OpLoopMerge", formatted ? "OpSwitch" : "Volatile"};
|
test.required_spirv = {"PhysicalStorageBuffer", "OpLoopMerge", formatted ? "OpSwitch" : "Volatile"};
|
||||||
|
if (!formatted) test.forbidden_spirv = {"convert_indirect_buffer_format"};
|
||||||
test.opcodes = {O::S_MOV_B32, O::V_MOV_B32, O::V_LSHLREV_B32,
|
test.opcodes = {O::S_MOV_B32, O::V_MOV_B32, O::V_LSHLREV_B32,
|
||||||
O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32, O::S_LSHL_B32,
|
O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32, O::S_LSHL_B32,
|
||||||
O::S_BUFFER_LOAD_DWORDX8, load,
|
O::S_BUFFER_LOAD_DWORDX8, load,
|
||||||
@@ -36591,16 +36592,31 @@ void CheckBoundedFormattedBufferCopy(VulkanHarness &vulkan) {
|
|||||||
if (count == 8u) {
|
if (count == 8u) {
|
||||||
std::set<u32> nonuniform;
|
std::set<u32> nonuniform;
|
||||||
std::vector<u32> stores;
|
std::vector<u32> stores;
|
||||||
u32 bda_calls = 0;
|
u32 bda_function = 0, conversion_function = 0;
|
||||||
|
u32 bda_calls = 0, conversion_calls = 0, conversion_definitions = 0;
|
||||||
for (size_t offset = 5; offset < compiled.spirv.size();) {
|
for (size_t offset = 5; offset < compiled.spirv.size();) {
|
||||||
const auto words = std::span(compiled.spirv).subspan(offset, compiled.spirv[offset] >> 16u);
|
const auto words = std::span(compiled.spirv).subspan(offset, compiled.spirv[offset] >> 16u);
|
||||||
const auto opcode = static_cast<spv::Op>(words[0] & 0xffffu);
|
const auto opcode = static_cast<spv::Op>(words[0] & 0xffffu);
|
||||||
|
if (opcode == spv::OpName) {
|
||||||
|
const std::string_view name(reinterpret_cast<const char *>(words.data() + 2));
|
||||||
|
if (name == "get_bda_pointer") bda_function = words[1];
|
||||||
|
if (name == "convert_indirect_buffer_format") conversion_function = words[1];
|
||||||
|
}
|
||||||
if (opcode == spv::OpDecorate && words[2] == spv::DecorationNonUniform)
|
if (opcode == spv::OpDecorate && words[2] == spv::DecorationNonUniform)
|
||||||
nonuniform.insert(words[1]);
|
nonuniform.insert(words[1]);
|
||||||
if (opcode == spv::OpStore) stores.push_back(words[1]);
|
if (opcode == spv::OpStore) stores.push_back(words[1]);
|
||||||
if (opcode == spv::OpFunctionCall) ++bda_calls;
|
if (opcode == spv::OpFunction && words[2] == conversion_function)
|
||||||
|
++conversion_definitions;
|
||||||
|
if (opcode == spv::OpFunctionCall) {
|
||||||
|
if (words[3] == bda_function) ++bda_calls;
|
||||||
|
if (words[3] == conversion_function) ++conversion_calls;
|
||||||
|
}
|
||||||
offset += words.size();
|
offset += words.size();
|
||||||
}
|
}
|
||||||
|
Require(test.name, "one shared formatted conversion",
|
||||||
|
conversion_definitions == 1u &&
|
||||||
|
conversion_calls == (wave > vulkan.SubgroupSize() ? 2u : 1u),
|
||||||
|
"formatted loads must call one conversion function across virtual halves");
|
||||||
Require(test.name, "one shared indirect store per virtual half",
|
Require(test.name, "one shared indirect store per virtual half",
|
||||||
std::ranges::count_if(stores, [&](u32 pointer) { return nonuniform.contains(pointer); }) ==
|
std::ranges::count_if(stores, [&](u32 pointer) { return nonuniform.contains(pointer); }) ==
|
||||||
(wave > vulkan.SubgroupSize() ? 2 : 1),
|
(wave > vulkan.SubgroupSize() ? 2 : 1),
|
||||||
@@ -36715,7 +36731,7 @@ void CheckBoundedRecordBufferAccess(VulkanHarness &vulkan) {
|
|||||||
}
|
}
|
||||||
if (count == 2u) {
|
if (count == 2u) {
|
||||||
test.required_spirv = {"StorageBufferArrayNonUniformIndexing"};
|
test.required_spirv = {"StorageBufferArrayNonUniformIndexing"};
|
||||||
test.forbidden_spirv = {"OpSwitch", "PhysicalStorageBuffer"};
|
test.forbidden_spirv = {"OpSwitch", "PhysicalStorageBuffer", "convert_indirect_buffer_format"};
|
||||||
} else if (count == 6u) test.required_spirv = {"OpSwitch"};
|
} else if (count == 6u) test.required_spirv = {"OpSwitch"};
|
||||||
auto compiled = CompileCase(test, vulkan.SubgroupSize());
|
auto compiled = CompileCase(test, vulkan.SubgroupSize());
|
||||||
Require(test.name, "mixed native aliases", count != 6u ||
|
Require(test.name, "mixed native aliases", count != 6u ||
|
||||||
|
|||||||
Reference in New Issue
Block a user