diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index 6f4952c28..0df4e0ec7 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -114,6 +114,7 @@ struct EmitterState { uint32_t bda_pagetable_variable = 0; uint32_t fault_buffer_variable = 0; uint32_t bda_pointer_function = 0; + uint32_t indirect_format_function = 0; uint32_t bvh_intersect_function = 0; uint32_t gds_variable = 0; uint32_t gds_length = 0; @@ -522,6 +523,7 @@ uint32_t EmitF16BitsToF32(EmitterState& state, uint32_t bits); void EmitProgram(EmitterState& state); void DefineGetBdaPointer(EmitterState& state); +void DefineConvertIndirectBufferFormat(ValueEmitContext& ctx); void DefineBvhIntersect(EmitterState& state); uint32_t GetBdaPointer(EmitterState& state, uint32_t address); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp index e119ca463..88c1ff17c 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp @@ -1065,32 +1065,11 @@ uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) { Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte, ConstantU32(state, 3))); }); - return EmitIndexSwitch( - state, buffer.format, format_count, TypeU32(state), [&](uint32_t format) { - const auto info = Format::GetFormatInfo(static_cast(format)); - if (info.type == Format::ComponentType::Unknown) return ConstantU32(state, 0); - const auto constant = Select( - 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); - }); - }); + const auto result = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpFunctionCall, TypeU32(state), result, + state.indirect_format_function, buffer.format, buffer.selector, + component, raw); + return result; }); } @@ -1197,6 +1176,64 @@ uint32_t GetBdaPointer(EmitterState& state, uint32_t address) { 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(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(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) { if (!state.program.info.uses_dma) { return; diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp index f7111c87b..0952ffb9d 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterProgram.cpp @@ -665,6 +665,7 @@ void EmitProgram(EmitterState& state) { } } DefineGetBdaPointer(state); + DefineConvertIndirectBufferFormat(ctx); DefineBvhIntersect(state); if (program.info.uses_swizzle) { ctx.scratch_u32_variable = state.builder.AllocateId(); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index b45a5c012..530d50a61 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -30388,6 +30388,7 @@ TestCase BufferLoadGpuSelectedDescriptors(u32 wave, ShaderOpcode load) { test.compute_info.wave_size = wave; test.has_compute_info = true; 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, O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32, O::S_LSHL_B32, O::S_BUFFER_LOAD_DWORDX8, load, @@ -36591,16 +36592,31 @@ void CheckBoundedFormattedBufferCopy(VulkanHarness &vulkan) { if (count == 8u) { std::set nonuniform; std::vector 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();) { const auto words = std::span(compiled.spirv).subspan(offset, compiled.spirv[offset] >> 16u); const auto opcode = static_cast(words[0] & 0xffffu); + if (opcode == spv::OpName) { + const std::string_view name(reinterpret_cast(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) nonuniform.insert(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(); } + 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", std::ranges::count_if(stores, [&](u32 pointer) { return nonuniform.contains(pointer); }) == (wave > vulkan.SubgroupSize() ? 2 : 1), @@ -36715,7 +36731,7 @@ void CheckBoundedRecordBufferAccess(VulkanHarness &vulkan) { } if (count == 2u) { 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"}; auto compiled = CompileCase(test, vulkan.SubgroupSize()); Require(test.name, "mixed native aliases", count != 6u ||