From 377301ef36c13fe4dc44f4c816b8aeffdfd765e3 Mon Sep 17 00:00:00 2001 From: notaxeltv Date: Fri, 16 Oct 2026 00:32:24 +0200 Subject: [PATCH] fix(recompiler): fill missing pixel interpolators as DEFAULT PPSA03541 prepares a pixel shader that still reads attribute 0 while SPI_PS_IN_CONTROL.NUM_INTERP is 0; extend the CNTL table with OFFSET 0x20 defaults instead of throwing. Co-authored-by: Cursor --- .../tests/InterpolationReferenceRange.cpp | 4 ++- .../Optimization/src/ShaderInfoCollector.cpp | 32 +++++++++++++++++++ docs/dev/debt/shader.md | 1 + 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/core/libs/tests/InterpolationReferenceRange.cpp b/core/libs/tests/InterpolationReferenceRange.cpp index 7bf9c80ad3..594adbd1d2 100644 --- a/core/libs/tests/InterpolationReferenceRange.cpp +++ b/core/libs/tests/InterpolationReferenceRange.cpp @@ -42,7 +42,9 @@ int main() { } }; const auto attribute = Collect(5u, 1u, 0u, 2u); - expect(attribute.find("interpolation parameter reference is out of range (attribute 5 component 1 mode 0, 2 inputs)") != std::string::npos, "an attribute past the pixel inputs", attribute); + expect(attribute.find("interpolation parameter reference") == std::string::npos, "an attribute past the pixel inputs becomes a default", attribute); + const auto empty = Collect(0u, 0u, 0u, 0u); + expect(empty.find("interpolation parameter reference") == std::string::npos, "attribute 0 with no SPI inputs becomes a default", empty); const auto component = Collect(0u, 4u, 2u, 1u); expect(component.find("(attribute 0 component 4 mode 2, 1 inputs)") != std::string::npos, "a component past w", component); const auto mode = Collect(1u, 3u, 3u, 3u); diff --git a/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp b/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp index fac95cb5d4..d89e500cb6 100644 --- a/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp +++ b/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp @@ -73,6 +73,35 @@ void ValidateOptions(const IrProgram& program, ShaderStageInputInfo inputInfo) { } } +void EnsurePixelInterpolatorCoverage(const IrProgram& program, ShaderPixelInputInfo& pixel) { + std::uint32_t needed = pixel.inputNum; + const auto consider = [&](std::uint32_t attribute) { + if (attribute < 32u && attribute + 1u > needed) needed = attribute + 1u; + }; + for (const auto& block : program.Blocks()) { + for (const IrValue* inst : block->Instructions()) { + switch (inst->Opcode()) { + case IrOpcode::GetAttribute: + case IrOpcode::GetInterpolationParameter: + case IrOpcode::GetInterpolationParameterF16: + case IrOpcode::InterpolateHostP1: + case IrOpcode::InterpolateHostP2: { + const IrValue* attribute = inst->Argument(0)->Resolve(); + if (attribute->HasImmediate() && attribute->Type() == IrType::U32) consider(attribute->ImmediateU32()); + break; + } + default: + break; + } + } + } + if (needed <= pixel.inputNum) return; + for (std::uint32_t input = pixel.inputNum; input < needed; ++input) { + pixel.interpolatorSettings[input] = 0x20u; + } + pixel.inputNum = needed; +} + void ValidateValueReferences(const IrProgram& program, ShaderStageInputInfo inputInfo) { for (const auto& block : program.Blocks()) { for (const IrValue* inst : block->Instructions()) { @@ -525,6 +554,9 @@ void ShaderInfoCollector::Collect(IrProgram& program, const ShaderStageInputInfo if (!program.Resources().resourceTrackingComplete || program.Metadata().shaderInfoComplete) { return Fail(!program.Resources().resourceTrackingComplete ? "shader resources were not tracked" : "shader info already collected"); } + if (program.Resources().stage == IrShaderStage::Pixel && inputInfo.pixel != nullptr) { + EnsurePixelInterpolatorCoverage(program, const_cast(*inputInfo.pixel)); + } ValidateOptions(program, inputInfo); ValidateValueReferences(program, inputInfo); auto next = program.Info(); diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index dea88ed54f..0e37433597 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -22,6 +22,7 @@ ### Functional +- [Pixel interpolators past `SPI_PS_IN_CONTROL.NUM_INTERP`](../../../core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp) (shader recompiler) - when the IR reads an attribute at or past the guest interpolator count, the missing `SPI_PS_INPUT_CNTL` slots are filled as DEFAULT (`OFFSET` 0x20, constant 0/1) before info collection, matching Mesa's treatment of a PS input with no VS output. Component and mode bounds still throw. PPSA03541 (UFC 5) prepares pixel shader `0x114073b400` with `NUM_INTERP` 0 while it still issues `v_interp` on attribute 0. Console behaviour for that pairing is not measured. - [Unlinked calls](../../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) - the `s_swappc_b64` a table call's compare chain falls back to, when its target matches no linked copy, records the `UnlinkedCall` fault with the target and the program counter and stops the invocation, or continues after the call in programs whose faults do not stop invocations (see `bdaStopsInvocations`). The driver lists those calls in the request (`linkedCallFallbacks`), and it reports the fault like a BDA fault, naming the target: a dispatch that checks its faults when it completes throws, one whose check runs in a deferred write-back logs it and continues. The hardware would jump to the target, which is not modelled. Any other `s_swappc_b64` whose target is not resolved still throws when the shader is compiled. - [Cached source IR](../../../core/shader/recompiler/Recompiler.cpp) releases lazily allocated SSA construction tables after resource preparation and reclaims values unreachable from instructions, metadata and resource plans when retaining a pending source program. Immediate shader preparation skips reclamation because compilation consumes the program. Reclamation must account for every new IR pointer owner; a remaining external use throws. Live IDs are retained. Source and variant entries remain cached without an eviction bound. A pending source program is released after its first variant is obtained; memory beyond the measured title checkpoint and Linux performance remain unmeasured. - [Vertex user-data tail jumps](../../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) link a linear raw vertex prolog's terminal `s_setpc_b64` through an unchanged user-data pair to its continuation. The instruction semantics follow AMD RDNA2 ISA section 12.3; the prolog convention follows Mesa `aco_select_vs_prolog.cpp` at `0b2d6d1a61d9bbf89cf8beb88a696144f67c61f8`. Console hardware has not been measured. Branching prologs, computed low pointers and continuations reading `s_getpc_b64` remain unsupported