From 212282d693a1f3505e0854ca0f3521daef5ce006 Mon Sep 17 00:00:00 2001 From: nmzik Date: Fri, 31 Jul 2026 08:20:30 +0200 Subject: [PATCH] fix(shader): preserve packed UINT16 MRT exports --- .../recompiler/emitter/spirvEmitterExport.cpp | 76 ++++++++++++++++--- .../recompiler/emitter/spirvEmitterModule.cpp | 74 +++++++++++------- tests/shaderCfgTests.cpp | 16 ++++ 3 files changed, 130 insertions(+), 36 deletions(-) diff --git a/src/graphics/shader/recompiler/emitter/spirvEmitterExport.cpp b/src/graphics/shader/recompiler/emitter/spirvEmitterExport.cpp index edcce2b0d..94282a8af 100644 --- a/src/graphics/shader/recompiler/emitter/spirvEmitterExport.cpp +++ b/src/graphics/shader/recompiler/emitter/spirvEmitterExport.cpp @@ -24,7 +24,7 @@ uint32_t EmitExportVec4F32(EmitterState& state, const IR::Instruction& inst) { const auto raw = EmitValueLoad(state, inst.src[pair_index]); const auto unpacked = state.builder.AllocateId(); state.builder.AddFunction({OpExtInst, state.vec2_float_type, unpacked, - state.glsl_std450, GlslUnpackHalf2x16, raw}); + state.glsl_std450, GlslUnpackHalf2x16, raw}); for (uint32_t lane = 0; lane < 2u; lane++) { const auto component = pair_index * 2u + lane; if (((inst.export_info.en >> component) & 1u) == 0) { @@ -36,8 +36,8 @@ uint32_t EmitExportVec4F32(EmitterState& state, const IR::Instruction& inst) { } } const auto vec = state.builder.AllocateId(); - state.builder.AddFunction({OpCompositeConstruct, state.vec4_float_type, vec, - components[0], components[1], components[2], components[3]}); + state.builder.AddFunction({OpCompositeConstruct, state.vec4_float_type, vec, components[0], + components[1], components[2], components[3]}); return vec; } @@ -50,7 +50,59 @@ uint32_t EmitExportVec4F32(EmitterState& state, const IR::Instruction& inst) { return vec; } -uint32_t ApplyMrtExportMapping(EmitterState& state, const IR::Instruction& inst, uint32_t value) { +uint32_t EmitExportComponentU32(EmitterState& state, const IR::Instruction& inst, + uint32_t component) { + const bool enabled = ((inst.export_info.en >> component) & 1u) != 0; + if (!enabled || component >= inst.src_count || component >= 4u) { + return ConstantU32(state, component == 3u ? 1u : 0u); + } + return EmitValueLoad(state, inst.src[component]); +} + +uint32_t EmitExportVec4U32(EmitterState& state, const IR::Instruction& inst) { + uint32_t components[4] = { + ConstantU32(state, 0u), + ConstantU32(state, 0u), + ConstantU32(state, 0u), + ConstantU32(state, 1u), + }; + + if (inst.export_info.compr) { + for (uint32_t pair_index = 0; pair_index < 2u && pair_index < inst.src_count; + pair_index++) { + const auto raw = EmitValueLoad(state, inst.src[pair_index]); + for (uint32_t lane = 0; lane < 2u; lane++) { + const auto component = pair_index * 2u + lane; + if (((inst.export_info.en >> component) & 1u) == 0) { + continue; + } + components[component] = state.builder.AllocateId(); + state.builder.AddFunction( + {OpBitFieldUExtract, state.uint_type, components[component], raw, + ConstantU32(state, lane * 16u), ConstantU32(state, 16u)}); + } + } + } else { + for (uint32_t component = 0; component < 4u; component++) { + components[component] = EmitExportComponentU32(state, inst, component); + } + } + + const auto vec = state.builder.AllocateId(); + state.builder.AddFunction({OpCompositeConstruct, state.vec4_uint_type, vec, components[0], + components[1], components[2], components[3]}); + return vec; +} + +static bool MrtUsesUintOutput(const EmitterState& state, const IR::Instruction& inst) { + return inst.export_info.kind == IR::ExportTargetKind::Mrt && + state.pixel_input_info != nullptr && + inst.export_info.index < std::size(state.pixel_input_info->target_output_mode) && + state.pixel_input_info->target_output_mode[inst.export_info.index] == 7u; +} + +uint32_t ApplyMrtExportMapping(EmitterState& state, const IR::Instruction& inst, uint32_t value, + uint32_t vector_type) { if (inst.export_info.kind != IR::ExportTargetKind::Mrt || state.pixel_input_info == nullptr || inst.export_info.index >= state.pixel_input_info->target_export_mapping.size()) { return value; @@ -62,8 +114,8 @@ uint32_t ApplyMrtExportMapping(EmitterState& state, const IR::Instruction& inst, } const auto mapped = state.builder.AllocateId(); - state.builder.AddFunction({OpVectorShuffle, state.vec4_float_type, mapped, value, value, - mapping.Map(0), mapping.Map(1), mapping.Map(2), mapping.Map(3)}); + state.builder.AddFunction({OpVectorShuffle, vector_type, mapped, value, value, mapping.Map(0), + mapping.Map(1), mapping.Map(2), mapping.Map(3)}); return mapped; } @@ -89,7 +141,7 @@ void EmitMrtZExport(EmitterState& state, const IR::Instruction& inst) { const auto ptr = state.builder.AllocateId(); state.builder.AddFunction({OpBitcast, state.int_type, mask, raw}); state.builder.AddFunction({OpAccessChain, state.ptr_output_int, ptr, - state.sample_mask_variable, ConstantU32(state, 0)}); + state.sample_mask_variable, ConstantU32(state, 0)}); state.builder.AddFunction({OpStore, ptr, mask}); } } @@ -114,11 +166,15 @@ void EmitExport(EmitterState& state, const IR::Instruction& inst) { return; } - const auto value = ApplyMrtExportMapping(state, inst, EmitExportVec4F32(state, inst)); + const auto uint_output = MrtUsesUintOutput(state, inst); + const auto vector_type = uint_output ? state.vec4_uint_type : state.vec4_float_type; + const auto value = ApplyMrtExportMapping( + state, inst, uint_output ? EmitExportVec4U32(state, inst) : EmitExportVec4F32(state, inst), + vector_type); if (inst.export_info.kind == IR::ExportTargetKind::Position) { const auto pointer = state.builder.AllocateId(); - state.builder.AddFunction({OpAccessChain, state.ptr_output_vec4_float, pointer, variable, - ConstantU32(state, 0)}); + state.builder.AddFunction( + {OpAccessChain, state.ptr_output_vec4_float, pointer, variable, ConstantU32(state, 0)}); state.builder.AddFunction({OpStore, pointer, value}); return; } diff --git a/src/graphics/shader/recompiler/emitter/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/emitter/spirvEmitterModule.cpp index 0cfe87775..89e7fbefb 100644 --- a/src/graphics/shader/recompiler/emitter/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/emitter/spirvEmitterModule.cpp @@ -174,6 +174,12 @@ uint32_t VertexParameterInputPointerType(const EmitterState& state, VertexInputS } } +static bool MrtUsesUintOutput(const EmitterState& state, uint32_t index) { + return state.stage == ShaderType::Pixel && state.pixel_input_info != nullptr && + index < std::size(state.pixel_input_info->target_output_mode) && + state.pixel_input_info->target_output_mode[index] == 7u; +} + void AllocateInputVariables(EmitterState& state) { for (auto& binding: state.inputs) { binding.variable_id = state.builder.AllocateId(); @@ -323,19 +329,31 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) { Decorate(state.address_memory_variable, "address_memory", IR::DescriptorBindingKind::AddressMemory); } - constexpr const char* SampledNames[] = { - "sampled_1d", "sampled_1d_array", "sampled_2d", "sampled_2d_array", - "sampled_3d", "sampled_uint_1d", "sampled_uint_1d_array", - "sampled_uint_2d", "sampled_uint_2d_array", "sampled_uint_3d"}; + constexpr const char* SampledNames[] = {"sampled_1d", + "sampled_1d_array", + "sampled_2d", + "sampled_2d_array", + "sampled_3d", + "sampled_uint_1d", + "sampled_uint_1d_array", + "sampled_uint_2d", + "sampled_uint_2d_array", + "sampled_uint_3d"}; for (uint32_t i = 0; i < state.sampled_images.size(); i++) { const auto view = static_cast(i % ImageViewKindCount); Decorate(state.sampled_images[i].variable, SampledNames[i], SampledBindingKind(i >= ImageViewKindCount, view)); } - constexpr const char* StorageNames[] = { - "storage_1d", "storage_1d_array", "storage_2d", "storage_2d_array", - "storage_3d", "storage_uint_1d", "storage_uint_1d_array", - "storage_uint_2d", "storage_uint_2d_array", "storage_uint_3d"}; + constexpr const char* StorageNames[] = {"storage_1d", + "storage_1d_array", + "storage_2d", + "storage_2d_array", + "storage_3d", + "storage_uint_1d", + "storage_uint_1d_array", + "storage_uint_2d", + "storage_uint_2d_array", + "storage_uint_3d"}; for (uint32_t i = 0; i < state.storage_images.size(); i++) { const auto view = static_cast(i % ImageViewKindCount); Decorate(state.storage_images[i].variable, StorageNames[i], @@ -409,6 +427,7 @@ void EmitHeaderAndTypes(EmitterState& state) { state.ptr_output_sample_mask_array = state.builder.AllocateId(); state.ptr_output_float = state.builder.AllocateId(); state.ptr_output_vec4_float = state.builder.AllocateId(); + const auto ptr_output_vec4_uint = state.builder.AllocateId(); state.per_vertex_type = state.builder.AllocateId(); state.ptr_output_per_vertex = state.builder.AllocateId(); state.storage_runtime_array_type = state.builder.AllocateId(); @@ -444,15 +463,15 @@ void EmitHeaderAndTypes(EmitterState& state) { image.array_type = state.builder.AllocateId(); image.array_pointer_type = state.builder.AllocateId(); } - state.sampler_type = state.builder.AllocateId(); - state.sampler_array_type = state.builder.AllocateId(); - state.ptr_uniform_sampler = state.builder.AllocateId(); - state.ptr_uniform_sampler_array = state.builder.AllocateId(); - state.ptr_image_uint = state.builder.AllocateId(); - state.func_type = state.builder.AllocateId(); - state.main_func = state.builder.AllocateId(); - state.entry_label = state.builder.AllocateId(); - state.glsl_std450 = state.builder.AllocateId(); + state.sampler_type = state.builder.AllocateId(); + state.sampler_array_type = state.builder.AllocateId(); + state.ptr_uniform_sampler = state.builder.AllocateId(); + state.ptr_uniform_sampler_array = state.builder.AllocateId(); + state.ptr_image_uint = state.builder.AllocateId(); + state.func_type = state.builder.AllocateId(); + state.main_func = state.builder.AllocateId(); + state.entry_label = state.builder.AllocateId(); + state.glsl_std450 = state.builder.AllocateId(); state.builder.AddCapability({CapabilityShader}); state.builder.AddCapability({CapabilitySampled1D}); @@ -461,8 +480,7 @@ void EmitHeaderAndTypes(EmitterState& state) { if (state.needs_image_gather_extended) { state.builder.AddCapability({CapabilityImageGatherExtended}); } - if (std::any_of(state.storage_images.begin(), - state.storage_images.begin() + ImageViewKindCount, + if (std::any_of(state.storage_images.begin(), state.storage_images.begin() + ImageViewKindCount, [](const auto& image) { return image.variable != 0; })) { state.builder.AddCapability({CapabilityStorageImageReadWithoutFormat}); state.builder.AddCapability({CapabilityStorageImageWriteWithoutFormat}); @@ -605,6 +623,8 @@ void EmitHeaderAndTypes(EmitterState& state) { {OpTypePointer, state.ptr_output_int, StorageClassOutput, state.int_type}); state.builder.AddType( {OpTypePointer, state.ptr_output_vec4_float, StorageClassOutput, state.vec4_float_type}); + state.builder.AddType( + {OpTypePointer, ptr_output_vec4_uint, StorageClassOutput, state.vec4_uint_type}); if (state.per_vertex_variable != 0) { state.builder.AddType({OpTypeStruct, state.per_vertex_type, state.vec4_float_type}); state.builder.AddType({OpTypePointer, state.ptr_output_per_vertex, StorageClassOutput, @@ -615,8 +635,12 @@ void EmitHeaderAndTypes(EmitterState& state) { for (const auto& binding: state.outputs) { if (binding.kind == IR::StageOutputKind::Parameter || binding.kind == IR::StageOutputKind::Mrt) { + const auto pointer_type = + binding.kind == IR::StageOutputKind::Mrt && MrtUsesUintOutput(state, binding.index) + ? ptr_output_vec4_uint + : state.ptr_output_vec4_float; state.builder.AddType( - {OpVariable, state.ptr_output_vec4_float, binding.variable_id, StorageClassOutput}); + {OpVariable, pointer_type, binding.variable_id, StorageClassOutput}); } } if (state.depth_variable != 0) { @@ -703,9 +727,8 @@ void EmitHeaderAndTypes(EmitterState& state) { const auto view = static_cast(i % ImageViewKindCount); const bool integer = i >= ImageViewKindCount; const auto component = integer ? state.uint_type : state.float_type; - state.builder.AddType({OpTypeImage, image.image_type, component, - ImageSpirvDimension(view), 0, ImageSpirvArrayed(view), 0, 1, - ImageFormatUnknown}); + state.builder.AddType({OpTypeImage, image.image_type, component, ImageSpirvDimension(view), + 0, ImageSpirvArrayed(view), 0, 1, ImageFormatUnknown}); state.builder.AddType({OpTypeSampledImage, image.sampled_image_type, image.image_type}); state.builder.AddType( {OpTypePointer, image.pointer_type, StorageClassUniformConstant, image.image_type}); @@ -737,9 +760,8 @@ void EmitHeaderAndTypes(EmitterState& state) { const bool integer = i >= ImageViewKindCount; const auto component = integer ? state.uint_type : state.float_type; const auto format = integer ? ImageFormatR32ui : ImageFormatUnknown; - state.builder.AddType({OpTypeImage, image.image_type, component, - ImageSpirvDimension(view), 0, ImageSpirvArrayed(view), 0, 2, - format}); + state.builder.AddType({OpTypeImage, image.image_type, component, ImageSpirvDimension(view), + 0, ImageSpirvArrayed(view), 0, 2, format}); state.builder.AddType( {OpTypePointer, image.pointer_type, StorageClassUniformConstant, image.image_type}); if (image.variable != 0) { diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 26b72008f..1e56230ca 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -5950,6 +5950,22 @@ void TestNewShaderRecompilerExpPixelOutputs() { Check(SpirvContainsOpcode(result.spirv, 81), "compressed pixel export SPIR-V lacks OpCompositeExtract"); CheckSpirvBinaryValidates(result.spirv); + + ShaderPixelInputInfo uint16_info; + uint16_info.target_output_mode[0] = 7; + options.pixel_input_info = &uint16_info; + ShaderRecompiler::CompileResult uint16_result; + Check(ShaderRecompiler::TryRecompile(shader, options, uint16_result, &error), error.c_str()); + const auto uint16_source = DisassembleSpirvBinary(uint16_result.spirv); + Check(Common::ContainsStr(uint16_source, "OpVariable %_ptr_Output_v4uint Output"), + "UINT16 MRT export did not use an unsigned integer output"); + Check(CountSourceOccurrences(uint16_source, "OpBitFieldUExtract") == 4u && + Common::ContainsStr(uint16_source, "%uint_0 %uint_16") && + Common::ContainsStr(uint16_source, "%uint_16 %uint_16"), + "compressed UINT16 MRT export did not extract all low/high 16-bit lanes"); + Check(!SpirvContainsExtInst(uint16_result.spirv, 62), + "compressed UINT16 MRT export was incorrectly decoded as FP16"); + CheckSpirvBinaryValidates(uint16_result.spirv); } void TestRenderTargetReverseFloat16ExportMapping() {