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 <cursoragent@cursor.com>
This commit is contained in:
notaxeltvandCursor committed 2026-10-16 19:14:53 +02:00
1 parent 66e290f1c0
commit 377301ef36
3 files changed
+36 -1

No files matched your search

@@ -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);
@@ -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<ShaderPixelInputInfo&>(*inputInfo.pixel));
}
ValidateOptions(program, inputInfo);
ValidateValueReferences(program, inputInfo);
auto next = program.Info();
+1
View File
@@ -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