fix(shader): preserve packed UINT16 MRT exports

This commit is contained in:
nmzik
2026-07-31 08:20:30 +02:00
parent 6bca35d1f5
commit 212282d693
3 changed files with 130 additions and 36 deletions
@@ -36,8 +36,8 @@ uint32_t EmitExportVec4F32(EmitterState& state, const IR::Instruction& inst) {
} }
} }
const auto vec = state.builder.AllocateId(); const auto vec = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeConstruct, state.vec4_float_type, vec, state.builder.AddFunction({OpCompositeConstruct, state.vec4_float_type, vec, components[0],
components[0], components[1], components[2], components[3]}); components[1], components[2], components[3]});
return vec; return vec;
} }
@@ -50,7 +50,59 @@ uint32_t EmitExportVec4F32(EmitterState& state, const IR::Instruction& inst) {
return vec; 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 || 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()) { inst.export_info.index >= state.pixel_input_info->target_export_mapping.size()) {
return value; return value;
@@ -62,8 +114,8 @@ uint32_t ApplyMrtExportMapping(EmitterState& state, const IR::Instruction& inst,
} }
const auto mapped = state.builder.AllocateId(); const auto mapped = state.builder.AllocateId();
state.builder.AddFunction({OpVectorShuffle, state.vec4_float_type, mapped, value, value, state.builder.AddFunction({OpVectorShuffle, vector_type, mapped, value, value, mapping.Map(0),
mapping.Map(0), mapping.Map(1), mapping.Map(2), mapping.Map(3)}); mapping.Map(1), mapping.Map(2), mapping.Map(3)});
return mapped; return mapped;
} }
@@ -114,11 +166,15 @@ void EmitExport(EmitterState& state, const IR::Instruction& inst) {
return; 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) { if (inst.export_info.kind == IR::ExportTargetKind::Position) {
const auto pointer = state.builder.AllocateId(); const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, state.ptr_output_vec4_float, pointer, variable, state.builder.AddFunction(
ConstantU32(state, 0)}); {OpAccessChain, state.ptr_output_vec4_float, pointer, variable, ConstantU32(state, 0)});
state.builder.AddFunction({OpStore, pointer, value}); state.builder.AddFunction({OpStore, pointer, value});
return; return;
} }
@@ -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) { void AllocateInputVariables(EmitterState& state) {
for (auto& binding: state.inputs) { for (auto& binding: state.inputs) {
binding.variable_id = state.builder.AllocateId(); binding.variable_id = state.builder.AllocateId();
@@ -323,19 +329,31 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) {
Decorate(state.address_memory_variable, "address_memory", Decorate(state.address_memory_variable, "address_memory",
IR::DescriptorBindingKind::AddressMemory); IR::DescriptorBindingKind::AddressMemory);
} }
constexpr const char* SampledNames[] = { constexpr const char* SampledNames[] = {"sampled_1d",
"sampled_1d", "sampled_1d_array", "sampled_2d", "sampled_2d_array", "sampled_1d_array",
"sampled_3d", "sampled_uint_1d", "sampled_uint_1d_array", "sampled_2d",
"sampled_uint_2d", "sampled_uint_2d_array", "sampled_uint_3d"}; "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++) { for (uint32_t i = 0; i < state.sampled_images.size(); i++) {
const auto view = static_cast<ImageViewKind>(i % ImageViewKindCount); const auto view = static_cast<ImageViewKind>(i % ImageViewKindCount);
Decorate(state.sampled_images[i].variable, SampledNames[i], Decorate(state.sampled_images[i].variable, SampledNames[i],
SampledBindingKind(i >= ImageViewKindCount, view)); SampledBindingKind(i >= ImageViewKindCount, view));
} }
constexpr const char* StorageNames[] = { constexpr const char* StorageNames[] = {"storage_1d",
"storage_1d", "storage_1d_array", "storage_2d", "storage_2d_array", "storage_1d_array",
"storage_3d", "storage_uint_1d", "storage_uint_1d_array", "storage_2d",
"storage_uint_2d", "storage_uint_2d_array", "storage_uint_3d"}; "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++) { for (uint32_t i = 0; i < state.storage_images.size(); i++) {
const auto view = static_cast<ImageViewKind>(i % ImageViewKindCount); const auto view = static_cast<ImageViewKind>(i % ImageViewKindCount);
Decorate(state.storage_images[i].variable, StorageNames[i], 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_sample_mask_array = state.builder.AllocateId();
state.ptr_output_float = state.builder.AllocateId(); state.ptr_output_float = state.builder.AllocateId();
state.ptr_output_vec4_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.per_vertex_type = state.builder.AllocateId();
state.ptr_output_per_vertex = state.builder.AllocateId(); state.ptr_output_per_vertex = state.builder.AllocateId();
state.storage_runtime_array_type = state.builder.AllocateId(); state.storage_runtime_array_type = state.builder.AllocateId();
@@ -461,8 +480,7 @@ void EmitHeaderAndTypes(EmitterState& state) {
if (state.needs_image_gather_extended) { if (state.needs_image_gather_extended) {
state.builder.AddCapability({CapabilityImageGatherExtended}); state.builder.AddCapability({CapabilityImageGatherExtended});
} }
if (std::any_of(state.storage_images.begin(), if (std::any_of(state.storage_images.begin(), state.storage_images.begin() + ImageViewKindCount,
state.storage_images.begin() + ImageViewKindCount,
[](const auto& image) { return image.variable != 0; })) { [](const auto& image) { return image.variable != 0; })) {
state.builder.AddCapability({CapabilityStorageImageReadWithoutFormat}); state.builder.AddCapability({CapabilityStorageImageReadWithoutFormat});
state.builder.AddCapability({CapabilityStorageImageWriteWithoutFormat}); state.builder.AddCapability({CapabilityStorageImageWriteWithoutFormat});
@@ -605,6 +623,8 @@ void EmitHeaderAndTypes(EmitterState& state) {
{OpTypePointer, state.ptr_output_int, StorageClassOutput, state.int_type}); {OpTypePointer, state.ptr_output_int, StorageClassOutput, state.int_type});
state.builder.AddType( state.builder.AddType(
{OpTypePointer, state.ptr_output_vec4_float, StorageClassOutput, state.vec4_float_type}); {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) { if (state.per_vertex_variable != 0) {
state.builder.AddType({OpTypeStruct, state.per_vertex_type, state.vec4_float_type}); state.builder.AddType({OpTypeStruct, state.per_vertex_type, state.vec4_float_type});
state.builder.AddType({OpTypePointer, state.ptr_output_per_vertex, StorageClassOutput, state.builder.AddType({OpTypePointer, state.ptr_output_per_vertex, StorageClassOutput,
@@ -615,8 +635,12 @@ void EmitHeaderAndTypes(EmitterState& state) {
for (const auto& binding: state.outputs) { for (const auto& binding: state.outputs) {
if (binding.kind == IR::StageOutputKind::Parameter || if (binding.kind == IR::StageOutputKind::Parameter ||
binding.kind == IR::StageOutputKind::Mrt) { 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( 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) { if (state.depth_variable != 0) {
@@ -703,9 +727,8 @@ void EmitHeaderAndTypes(EmitterState& state) {
const auto view = static_cast<ImageViewKind>(i % ImageViewKindCount); const auto view = static_cast<ImageViewKind>(i % ImageViewKindCount);
const bool integer = i >= ImageViewKindCount; const bool integer = i >= ImageViewKindCount;
const auto component = integer ? state.uint_type : state.float_type; const auto component = integer ? state.uint_type : state.float_type;
state.builder.AddType({OpTypeImage, image.image_type, component, state.builder.AddType({OpTypeImage, image.image_type, component, ImageSpirvDimension(view),
ImageSpirvDimension(view), 0, ImageSpirvArrayed(view), 0, 1, 0, ImageSpirvArrayed(view), 0, 1, ImageFormatUnknown});
ImageFormatUnknown});
state.builder.AddType({OpTypeSampledImage, image.sampled_image_type, image.image_type}); state.builder.AddType({OpTypeSampledImage, image.sampled_image_type, image.image_type});
state.builder.AddType( state.builder.AddType(
{OpTypePointer, image.pointer_type, StorageClassUniformConstant, image.image_type}); {OpTypePointer, image.pointer_type, StorageClassUniformConstant, image.image_type});
@@ -737,9 +760,8 @@ void EmitHeaderAndTypes(EmitterState& state) {
const bool integer = i >= ImageViewKindCount; const bool integer = i >= ImageViewKindCount;
const auto component = integer ? state.uint_type : state.float_type; const auto component = integer ? state.uint_type : state.float_type;
const auto format = integer ? ImageFormatR32ui : ImageFormatUnknown; const auto format = integer ? ImageFormatR32ui : ImageFormatUnknown;
state.builder.AddType({OpTypeImage, image.image_type, component, state.builder.AddType({OpTypeImage, image.image_type, component, ImageSpirvDimension(view),
ImageSpirvDimension(view), 0, ImageSpirvArrayed(view), 0, 2, 0, ImageSpirvArrayed(view), 0, 2, format});
format});
state.builder.AddType( state.builder.AddType(
{OpTypePointer, image.pointer_type, StorageClassUniformConstant, image.image_type}); {OpTypePointer, image.pointer_type, StorageClassUniformConstant, image.image_type});
if (image.variable != 0) { if (image.variable != 0) {
+16
View File
@@ -5950,6 +5950,22 @@ void TestNewShaderRecompilerExpPixelOutputs() {
Check(SpirvContainsOpcode(result.spirv, 81), Check(SpirvContainsOpcode(result.spirv, 81),
"compressed pixel export SPIR-V lacks OpCompositeExtract"); "compressed pixel export SPIR-V lacks OpCompositeExtract");
CheckSpirvBinaryValidates(result.spirv); 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() { void TestRenderTargetReverseFloat16ExportMapping() {