diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index e1457dcea..1726e415b 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -72,7 +72,7 @@ uint32_t EmitBuiltinU32(ValueEmitContext& ctx, IR::StageInputKind kind, uint32_t return bits; } if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex || - kind == IR::StageInputKind::Layer) { + kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId) { const auto value = state.builder.AllocateId(); const auto bits = state.builder.AllocateId(); state.builder.AddFunction({OpLoad, TypeI32(state), value, variable}); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index a96920e72..b7e16504e 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -43,6 +43,7 @@ enum : uint32_t { CapabilityImageGatherExtended = 25, CapabilityClipDistance = 32, CapabilityCullDistance = 33, + CapabilitySampleRateShading = 35, CapabilitySampled1D = 43, CapabilityImage1D = 44, CapabilityImageQuery = 50, @@ -92,6 +93,7 @@ enum : uint32_t { BuiltInLayer = 9, BuiltInFragCoord = 15, BuiltInFrontFacing = 17, + BuiltInSampleId = 18, BuiltInSampleMask = 20, BuiltInFragDepth = 22, BuiltInWorkgroupId = 26, diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index 743ccfc32..e48ed03a1 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -512,6 +512,7 @@ uint32_t BuiltInForInput(IR::StageInputKind kind) { case IR::StageInputKind::FragCoord: return BuiltInFragCoord; case IR::StageInputKind::FrontFacing: return BuiltInFrontFacing; case IR::StageInputKind::Layer: return BuiltInLayer; + case IR::StageInputKind::SampleId: return BuiltInSampleId; case IR::StageInputKind::BaryCoordSmooth: return BuiltInBaryCoordKHR; case IR::StageInputKind::BaryCoordNoPerspective: return BuiltInBaryCoordNoPerspKHR; case IR::StageInputKind::WorkgroupId: return BuiltInWorkgroupId; @@ -535,7 +536,7 @@ void AddInputAnnotationsAndNames(EmitterState& state) { } for (const auto& input: state.inputs) { state.builder.AddName(input.variable_id, input.debug_name.c_str()); - if (input.kind == IR::StageInputKind::Layer) { + if (input.kind == IR::StageInputKind::Layer || input.kind == IR::StageInputKind::SampleId) { state.builder.AddAnnotation({OpDecorate, input.variable_id, DecorationFlat}); } if (input.kind == IR::StageInputKind::Parameter) { @@ -711,6 +712,9 @@ void DefineModule(EmitterState& state) { state.builder.RequireVersion(0x00010500u); state.builder.RequireCapability(CapabilityShaderLayer); } + if (InputVariableForKind(state, IR::StageInputKind::SampleId) != 0) { + state.builder.RequireCapability(CapabilitySampleRateShading); + } if (state.requirements.image_gather_extended) { state.builder.RequireCapability(CapabilityImageGatherExtended); } @@ -803,6 +807,7 @@ void DefineModule(EmitterState& state) { case IR::StageInputKind::VertexIndex: case IR::StageInputKind::InstanceIndex: case IR::StageInputKind::Layer: + case IR::StageInputKind::SampleId: ptr_type = TypePointer(state, StorageClassInput, TypeI32(state)); break; case IR::StageInputKind::WorkgroupId: diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 60d90bacb..9cefb95d0 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -162,6 +162,7 @@ enum class StageInputKind { FrontFacing, PackedAncillary, Layer, + SampleId, BaryCoordSmooth, BaryCoordNoPerspective, WorkgroupId, diff --git a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp index 223e5e954..69a5b04ae 100644 --- a/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ConstantPropagation.cpp @@ -310,17 +310,24 @@ void FoldInstruction(Block& block, Block::iterator instruction) { const auto* source = value.TryInstruction(); if (source != nullptr && source->GetOpcode() == ValueOpcode::GetBuiltin && source->Arg(0) == Value(static_cast(StageInputKind::PackedAncillary)) && - IsImmediate(offset, Type::U32) && IsImmediate(count, Type::U32) && - offset.U32() >= 16u && offset.U32() < 27u && count.U32() != 0u && - count.U32() <= 27u - offset.U32()) { - // Preserve the extraction (including sign extension) while exposing only its - // used hardware field. Other live ancillary fields remain unsupported. - const auto layer = block.PrependNewInst( - instruction, ValueOpcode::GetBuiltin, - {Value(static_cast(StageInputKind::Layer)), Value(0u)}); - inst.SetArg(0, Value(&*layer)); - inst.SetArg(1, Value(offset.U32() - 16u)); - return; + IsImmediate(offset, Type::U32) && IsImmediate(count, Type::U32) && count.U32() != 0u) { + constexpr struct { + uint32_t start; + uint32_t end; + StageInputKind kind; + } fields[] = {{8u, 12u, StageInputKind::SampleId}, {16u, 27u, StageInputKind::Layer}}; + for (const auto& field: fields) { + if (offset.U32() >= field.start && offset.U32() < field.end && + count.U32() <= field.end - offset.U32()) { + // Preserve extraction and sign extension while exposing only the used field. + const auto input = block.PrependNewInst( + instruction, ValueOpcode::GetBuiltin, + {Value(static_cast(field.kind)), Value(0u)}); + inst.SetArg(0, Value(&*input)); + inst.SetArg(1, Value(offset.U32() - field.start)); + return; + } + } } if (!IsImmediate(value, Type::U32) || !IsImmediate(offset, Type::U32) || !IsImmediate(count, Type::U32) || offset.U32() > 32u || diff --git a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp index 8a846a26f..3237672d9 100644 --- a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp @@ -118,8 +118,9 @@ void ValidateValueReferences(const Program& program, const ShaderInfoOptions& op case StageInputKind::PackedAncillary: return Fail("packed pixel ancillary input has an unsupported live use"); case StageInputKind::Layer: + case StageInputKind::SampleId: if (program.stage != ShaderType::Pixel || component != 0u) { - return Fail("typed layer input is invalid"); + return Fail("typed pixel scalar input is invalid"); } break; case StageInputKind::VertexIndex: @@ -259,6 +260,7 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) { AddInput(info, kind, 0, 1, "gl_FrontFacing"); break; case StageInputKind::Layer: AddInput(info, kind, 0, 1, "gl_Layer"); break; + case StageInputKind::SampleId: AddInput(info, kind, 0, 1, "gl_SampleID"); break; case StageInputKind::BaryCoordSmooth: AddInput(info, kind, 0, 3, "gl_BaryCoordKHR"); break; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index f530b171f..c1b36dd4a 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1048,6 +1048,7 @@ struct GraphicsCase { bool pixel_ancillary = false; bool pixel_front_face = false; u32 layers = 1; + vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1; }; struct CompiledShader { @@ -10252,7 +10253,8 @@ public: u32 dwords_per_pixel, vk::ImageLayout final_layout, vk::ImageType image_type, vk::ImageViewType view_type, u32 layers, u32 view_base_layer = 0, - u32 view_layers = 0) { + u32 view_layers = 0, + vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1) { Image ret; ret.format = format; ret.width = width; @@ -10270,7 +10272,7 @@ public: image_info.extent.depth = 1; image_info.mipLevels = ret.mip_levels; image_info.arrayLayers = layers; - image_info.samples = vk::SampleCountFlagBits::e1; + image_info.samples = samples; image_info.tiling = vk::ImageTiling::eOptimal; image_info.usage = usage | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc; @@ -10914,7 +10916,15 @@ public: vk::ImageUsageFlagBits::eColorAttachment, {}, 4, vk::ImageLayout::eGeneral, vk::ImageType::e2D, test.layers > 1 ? vk::ImageViewType::e2DArray : vk::ImageViewType::e2D, - test.layers); + test.layers, 0, 0, test.samples); + Image resolved; + if (test.samples != vk::SampleCountFlagBits::e1) { + resolved = CreateImageMips( + test.name, 1, 1, target.format, vk::ImageUsageFlagBits::eColorAttachment, + {}, 4, vk::ImageLayout::eGeneral, vk::ImageType::e2D, + test.layers > 1 ? vk::ImageViewType::e2DArray : vk::ImageViewType::e2D, + test.layers); + } const std::vector default_vertices = { 0xbf800000u, 0xbf800000u, 0x3e800000u, 0x3f000000u, 0x3f400000u, 0x3f800000u, 0x40400000u, 0xbf800000u, 0x3e800000u, 0x3f000000u, @@ -11028,7 +11038,7 @@ public: vk::PipelineMultisampleStateCreateInfo multisample{}; multisample.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo; - multisample.rasterizationSamples = vk::SampleCountFlagBits::e1; + multisample.rasterizationSamples = test.samples; vk::PipelineColorBlendAttachmentState color_attachment{}; color_attachment.colorWriteMask = @@ -11069,6 +11079,11 @@ public: color.imageLayout = vk::ImageLayout::eGeneral; color.loadOp = vk::AttachmentLoadOp::eClear; color.storeOp = vk::AttachmentStoreOp::eStore; + if (resolved.image != nullptr) { + color.resolveMode = vk::ResolveModeFlagBits::eAverage; + color.resolveImageView = resolved.view; + color.resolveImageLayout = vk::ImageLayout::eGeneral; + } vk::RenderingInfo rendering{}; rendering.sType = vk::StructureType::eRenderingInfo; rendering.renderArea.extent = {1, 1}; @@ -11091,7 +11106,7 @@ public: EndSubmitAndFree(test.name, "graphics", cmd); target.layout = vk::ImageLayout::eGeneral; - auto pixel = ReadImage(test.name, &target); + auto pixel = ReadImage(test.name, resolved.image != nullptr ? &resolved : &target); pixel.resize(4 * test.layers); m_device.destroyPipeline(pipeline, nullptr); @@ -11099,6 +11114,7 @@ public: m_device.destroyShaderModule(fragment_module, nullptr); m_device.destroyShaderModule(vertex_module, nullptr); DestroyBuffer(&vertex_buffer); + DestroyImage(&resolved); DestroyImage(&target); return pixel; } @@ -22449,6 +22465,29 @@ GraphicsCase GraphicsAncillaryLayer(bool front_face) { return test; } +GraphicsCase GraphicsAncillarySampleId() { + GraphicsCase test; + test.name = "GraphicsAncillarySampleId"; + test.pixel_ancillary = true; + test.samples = vk::SampleCountFlagBits::e4; + AppendVop3(&test.fragment_code, 0x148, 4, Vgpr(2), InlineU32(8), InlineU32(4)); + AppendVop3(&test.fragment_code, 0x149, 5, Vgpr(2), InlineU32(8), InlineU32(2)); + AppendVop3(&test.fragment_code, 0x148, 6, Vgpr(2), InlineU32(9), InlineU32(1)); + test.fragment_code.push_back(EncodeVop1(0x06, 0, Vgpr(4))); + test.fragment_code.push_back(EncodeVop1(0x05, 1, Vgpr(5))); + test.fragment_code.push_back(EncodeVop1(0x06, 2, Vgpr(6))); + AppendVMovLiteral(&test.fragment_code, 3, 0x3f800000u); + test.fragment_code.push_back(EncodeExp0(0x00, 0xf)); + test.fragment_code.push_back(EncodeExp1(0, 1, 2, 3)); + AppendEnd(&test.fragment_code); + test.opcodes = {ShaderOpcode::V_BFE_U32, ShaderOpcode::V_BFE_I32, + ShaderOpcode::V_CVT_F32_U32, ShaderOpcode::V_CVT_F32_I32, + ShaderOpcode::V_MOV_B32, ShaderOpcode::EXP, ShaderOpcode::S_ENDPGM}; + // Average samples 0..3, their signed low two bits, and their second bit. + test.expected_pixel = {0x3fc00000u, 0xbf000000u, 0x3f000000u, 0x3f800000u}; + return test; +} + GraphicsCase GraphicsFlatInterpolatorExport() { using O = ShaderOpcode; @@ -23012,6 +23051,7 @@ std::vector MakeGraphicsCases() { GraphicsInterpolationExport(), GraphicsAncillaryLayer(false), GraphicsAncillaryLayer(true), + GraphicsAncillarySampleId(), GraphicsFlatInterpolatorExport(), GraphicsDsAddtidScratchExport(), GraphicsDirectSgprPushConstantExport(), diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index aed8ce5d7..7a57d04bc 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -6011,8 +6011,22 @@ void TestPixelAncillaryLayerInput() { unused_result.spirv[1] == 0x00010300u && !SpirvContainsCapability(unused_result.spirv, 69u), "unused ancillary input changed the module requirements"); -#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + shader[1] = EncodeVop3Word1(5 + 256, 128 + 8, 128 + 4); + auto sample_result = RecompileForTest(shader, options); + Check(ProgramHasInput(sample_result.program, StageInputKind::SampleId) && + ProgramHasInput(sample_result.program, StageInputKind::Layer) && + !ProgramHasInput(sample_result.program, StageInputKind::PackedAncillary), + "sample index extraction discarded another live ancillary field"); + Check(SpirvHasDecorationValueWithDecoration(sample_result.spirv, 11u, 18u, 14u) && + SpirvContainsCapability(sample_result.spirv, 35u), + "sample index lacks its flat builtin or sample-rate capability"); + const auto sample_source = DisassembleSpirvBinary(sample_result.spirv); + Check(SpirvSourceHasInstructionUsing(sample_source, "OpLoad", "%int %gl_SampleID"), + "sample index was not loaded from its scalar integer input"); + CheckSpirvBinaryValidates(sample_result.spirv); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + shader[1] = EncodeVop3Word1(5 + 256, 128 + 12, 128 + 4); ExpectFatal([&] { (void)RecompileForTest(shader, options); }, "unsupported live ancillary field was silently replaced"); #endif