diff --git a/core/libs/tests/PixelAncillary.cpp b/core/libs/tests/PixelAncillary.cpp index 491526ba6..5bc5ba3a3 100644 --- a/core/libs/tests/PixelAncillary.cpp +++ b/core/libs/tests/PixelAncillary.cpp @@ -23,6 +23,19 @@ struct Row { }; constexpr std::array Rows {{{0u, 0u, false}, {0u, 0u, true}, {15u, 0x1FFFu, false}, {15u, 0x1FFFu, true}, {0u, 0x1FFFu, true}, {15u, 0u, false}}}; +static std::uint32_t Evaluate(const IrValue* value, const Row& row); +static void RequireField(const IrValue& user, StageInputKind kind, std::uint32_t fieldOffset, std::uint32_t count) { + if (kind == StageInputKind::Layer) { + for (const Row& row : Rows) Require(Evaluate(&user, row) == 0u); + return; + } + const IrValue* field = user.Argument(0)->Resolve(); + Require(field->Opcode() == IrOpcode::GetBuiltin); + Require(static_cast(field->Argument(0)->Resolve()->ImmediateU32()) == kind); + Require(user.Argument(1)->Resolve()->ImmediateU32() == fieldOffset); + Require(user.Argument(2)->Resolve()->ImmediateU32() == count); +} + static IrBlock& Begin(IrProgram& program) { program.Resources().stage = IrShaderStage::Pixel; program.Resources().resourceTrackingComplete = true; @@ -80,11 +93,7 @@ static void ExtractVector(std::uint32_t offset, std::uint32_t count, StageInputK static_cast(builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&user})); static_cast(builder.Emit(IrOpcode::Return, IrType::Void, {})); Lower(program); - const IrValue* field = user.Argument(0)->Resolve(); - Require(field->Opcode() == IrOpcode::GetBuiltin); - Require(static_cast(field->Argument(0)->Resolve()->ImmediateU32()) == kind); - Require(user.Argument(1)->Resolve()->ImmediateU32() == fieldOffset); - Require(user.Argument(2)->Resolve()->ImmediateU32() == count); + RequireField(user, kind, fieldOffset, count); } static void ExtractVectorMaskedCopy(std::uint32_t offset, std::uint32_t count, StageInputKind kind, std::uint32_t fieldOffset) { @@ -109,21 +118,13 @@ static void ExtractVectorMaskedCopy(std::uint32_t offset, std::uint32_t count, S static_cast(builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {©})); static_cast(builder.Emit(IrOpcode::Return, IrType::Void, {})); ConstantFolder().Fold(program); - const IrValue* field = user.Argument(0)->Resolve(); - Require(field->Opcode() == IrOpcode::GetBuiltin); - Require(static_cast(field->Argument(0)->Resolve()->ImmediateU32()) == kind); - Require(user.Argument(1)->Resolve()->ImmediateU32() == fieldOffset); - Require(user.Argument(2)->Resolve()->ImmediateU32() == count); + RequireField(user, kind, fieldOffset, count); } static void Extract(IrOpcode opcode, std::uint32_t offset, std::uint32_t count, StageInputKind kind, std::uint32_t fieldOffset) { IrProgram program; auto& user = Build(program, opcode, offset, count); Lower(program); - const IrValue* field = user.Argument(0)->Resolve(); - Require(field->Opcode() == IrOpcode::GetBuiltin); - Require(static_cast(field->Argument(0)->Resolve()->ImmediateU32()) == kind); - Require(user.Argument(1)->Resolve()->ImmediateU32() == fieldOffset); - Require(user.Argument(2)->Resolve()->ImmediateU32() == count); + RequireField(user, kind, fieldOffset, count); } static void Refused(IrOpcode opcode, std::uint32_t offset, std::uint32_t count, bool throughSelect = false) { IrProgram program; @@ -171,7 +172,7 @@ static std::uint32_t Evaluate(const IrValue* value, const Row& row) { throw std::runtime_error("packed pixel ancillary evaluation reached an unsupported value"); } static std::uint32_t PackedWord(const Row& row) { - return UnmodelledBits | (row.sample << 8u) | (row.layer << 16u); + return UnmodelledBits | (row.sample << 8u); } static void Values(IrOpcode opcode, std::uint32_t offset, std::uint32_t count, bool throughSelect) { IrProgram program; @@ -193,11 +194,7 @@ static void LateFold() { IrValue& user = builder.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&ancillary, &offset, &count}); End(builder, user); Lower(program); - const IrValue* field = user.Argument(0)->Resolve(); - Require(field->Opcode() == IrOpcode::GetBuiltin); - Require(static_cast(field->Argument(0)->Resolve()->ImmediateU32()) == StageInputKind::Layer); - Require(user.Argument(1)->Resolve()->ImmediateU32() == 0u); - Require(user.Argument(2)->Resolve()->ImmediateU32() == 11u); + RequireField(user, StageInputKind::Layer, 0u, 11u); for (const Row& row : Rows) { Require(Evaluate(&user, row) == BitField(PackedWord(row), 16u, 11u, false)); } @@ -329,8 +326,7 @@ static void HelperLanesOnly(std::uint32_t chain = 0u) { } } Require(built.written->Parent() == nullptr); - const IrValue* field = built.layer->Argument(0)->Resolve(); - Require(field->Opcode() == IrOpcode::GetBuiltin && static_cast(field->Argument(0)->Resolve()->ImmediateU32()) == StageInputKind::Layer); + RequireField(*built.layer, StageInputKind::Layer, 0u, 11u); const IrValue* entryValue = built.exported->Argument(0)->Resolve(); Require(entryValue->HasImmediate() && entryValue->ImmediateU32() == SelectOther); } @@ -416,8 +412,7 @@ static void HelperLoopOnly(LoopRead read) { } } Require(built.written->Parent() == nullptr); - const IrValue* field = built.layer->Argument(0)->Resolve(); - Require(field->Opcode() == IrOpcode::GetBuiltin && static_cast(field->Argument(0)->Resolve()->ImmediateU32()) == StageInputKind::Layer); + RequireField(*built.layer, StageInputKind::Layer, 0u, 11u); const IrValue* entryValue = built.exported->Argument(0)->Resolve(); Require(entryValue->HasImmediate() && entryValue->ImmediateU32() == SelectOther); } diff --git a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp index 079df35fb..fc48ec49d 100644 --- a/core/shader/recompiler/Optimization/src/ConstantFolder.cpp +++ b/core/shader/recompiler/Optimization/src/ConstantFolder.cpp @@ -384,8 +384,9 @@ void lowerPackedAncillary(IrProgram& program, IrBuilder& builder, IrValue& ancil } std::array fields {}; const auto field = [&](std::size_t index) -> IrValue& { + if (fields[index] == nullptr && index == 1u) fields[index] = &builder.Constant(0u); if (fields[index] == nullptr) { - fields[index] = &insertBuiltin(program, builder, ancillary, index == 0u ? StageInputKind::SampleId : StageInputKind::Layer); + fields[index] = &insertBuiltin(program, builder, ancillary, StageInputKind::SampleId); } return *fields[index]; }; diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index c424adcaf..bb76f27c5 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -2,7 +2,7 @@ ### Unknown function info -- [Packed pixel ancillary word](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) (shader recompiler) - the sample index at bits 8-11 and the render target index at bits 16-28 follow #295, and their equality with the SampleId and Layer builtins is inferred, not measured. Mesa reads both fields from those bits, with the layer 14 bits wide only from GFX12 on ([ac_nir_lower_intrinsics_to_args.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/amd/common/nir/ac_nir_lower_intrinsics_to_args.c#L342-L368)). A value passed through a select, move or phi whose every read is an extract inside one of those fields is rebuilt from the two fields alone. Any other read the masked-select pass cannot drop gets the whole word: the primitive class at bits 0-1 (0 point, 1 line, 2 triangle, 3 rectangle), which the draw sets as a pipeline specialization constant ([RasterPrimitiveOf](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp)) in the shaders that read it, plus the sample field, with every other bit zero. That layout comes from the RDNA 3 and 3.5 ISA guides (prim type 1:0, VRS rates 3:2 and 5:4, eye id 7, sample 11:8, render target index 28:16). The GCN register guides give the same prim type and sample fields and put the render target index at 26:16, so the two sources agree for layers below 2048. AMD's LLPC reads the primitive class from bits 1:0 with the same encoding ([LowerGlCompatibility.cpp](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/llpc/lowering/LowerGlCompatibility.cpp#L830-L836)) and without a GFX-level check ([getPrimType](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/lgc/lowering/LowerInOut.cpp#L5307-L5315)). Nobody has checked it on gfx1013. In the whole word only bits 0-1 and 8-11 have a source. Bits 2-7, 12-15 and 29-31 are assumed zero, not measured: the VRS rates (2-5) and the eye id (7) because the driver supports neither variable-rate shading nor multiview, so a title that enables either would read other bits on the console, and bits 6, 12-15 and 29-31 because no source gives them a meaning. The sample field reads SampleId, which is always 0 here because the driver rejects MSAA and per-sample shading (`PA_SC_AA_CONFIG`, `PS_ITER_SAMPLE`). The render target index (bits 16-28) of the whole word is the constant 0 and not the Layer builtin: draws render to a single layer and guest layer exports are ignored, so no stage writes Layer, and a fragment shader that reads the unwritten builtin loses its writes on AMD's Windows driver (#4800). A change that makes draws export a layer must restore the read +- [Packed pixel ancillary word](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) (shader recompiler) - the sample index at bits 8-11 and the render target index at bits 16-28 follow #295. The sample index is the SampleId builtin, an equality inferred, not measured. The render target index is 0: vertex layer exports are ignored, so no pre-rasterization stage writes Layer and the first layer is used. Reading the unwritten Layer builtin instead makes AMD's Windows driver drop the fragment's writes (#4800). Mesa reads both fields from those bits, with the layer 14 bits wide only from GFX12 on ([ac_nir_lower_intrinsics_to_args.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/bffd91947efc1932da99aca69fc540d6d4201b0c/src/amd/common/nir/ac_nir_lower_intrinsics_to_args.c#L342-L368)). A value passed through a select, move or phi whose every read is an extract inside one of those fields is rebuilt from the two fields alone. Any other read the masked-select pass cannot drop gets the whole word: the primitive class at bits 0-1 (0 point, 1 line, 2 triangle, 3 rectangle), which the draw sets as a pipeline specialization constant ([RasterPrimitiveOf](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp)) in the shaders that read it, plus the sample field, with every other bit zero. That layout comes from the RDNA 3 and 3.5 ISA guides (prim type 1:0, VRS rates 3:2 and 5:4, eye id 7, sample 11:8, render target index 28:16). The GCN register guides give the same prim type and sample fields and put the render target index at 26:16, so the two sources agree for layers below 2048. AMD's LLPC reads the primitive class from bits 1:0 with the same encoding ([LowerGlCompatibility.cpp](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/llpc/lowering/LowerGlCompatibility.cpp#L830-L836)) and without a GFX-level check ([getPrimType](https://github.com/GPUOpen-Drivers/llpc/blob/40cb8d95ad8d6f7f1652e3fd47d39667594cce08/lgc/lowering/LowerInOut.cpp#L5307-L5315)). Nobody has checked it on gfx1013. In the whole word only bits 0-1 and 8-11 have a source. Bits 2-7, 12-15 and 29-31 are assumed zero, not measured: the VRS rates (2-5) and the eye id (7) because the driver supports neither variable-rate shading nor multiview, so a title that enables either would read other bits on the console, and bits 6, 12-15 and 29-31 because no source gives them a meaning. The sample field reads SampleId, which is always 0 here because the driver rejects MSAA and per-sample shading (`PA_SC_AA_CONFIG`, `PS_ITER_SAMPLE`). The whole word has the same 0 at bits 16-28. A change that makes draws export a layer must restore the Layer read in both places - [Color comparison descriptor forms](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp): emulated color comparison supports UNORM, SNORM and float formats for 2D, 2D-array and cube views. 8-bit sRGB and UNORM (`Format8Srgb`, `Format8_8Srgb`, `Format8_8_8_8Srgb`, `Format8UNorm`, `Format8_8UNorm`, `Format8_8_8_8UNorm`) return 0 for every compare function, measured with `image_sample_c_lz` on gfx1035 over references -0.5..1.5 and texel codes 0..255. Anisotropic filters are refused, except on those 8-bit formats, where every texel comparison is 0 (not measured with an anisotropic S#). Bilinear cube comparisons of float, snorm and the other unorm formats without `DISABLE_CUBE_WRAP` are refused; on that hardware a 4x4 R32 float cube blends across faces (46 of 90 edge rows differ from a per-face clamp; face 0, ref 0.1, u=3.9 v=2.5 is 0.6016 and u=1.5 v=0.1 is 0.3984), and with `DISABLE_CUBE_WRAP` the per-face clamp matches. An 8-bit sRGB or UNORM bilinear cube without that bit returns 0, since every texel comparison is already 0 for every compare function and the selected face cannot change the result. Cube-edge sampling behavior and console coverage on PS5's gfx1013 remain unverified. - [s_setkill](../../../core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp) (core/shader/recompiler) - decoded as `s_endpgm` for any immediate: measured on RDNA2 hardware (gfx1035), a store before it is visible and nothing after it runs ([#771](https://github.com/boykopovar/AnyPS5/pull/771)); whether the KILL status bit it sets is observable differently from `s_endpgm` is unverified - [s_sethalt](../../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) (core/shader/recompiler) - with SIMM16[0] clear it is a no-op: the RDNA 2 ISA guide describes that value as resuming a halted wave, and a wave that executes it is not halted; measured on a gfx1035 (RDNA 2) wave: `s_sethalt 0` followed by a VALU op completes and gives the normal result. Only SIMM16[0] is read; with it set the instruction throws