Skip to content

feat(agc): blend into color targets with a reversed component order - #5200

Merged
adriadam10 merged 4 commits into
boykopovar:mainfrom
nikowolf123:fix/agc-blend-reversed-component-order
Oct 15, 2026
Merged

adriadam10 merged 4 commits into
boykopovar:mainfrom
nikowolf123:fix/agc-blend-reversed-component-order

Conversation

@nikowolf123

@nikowolf123 nikowolf123 commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What

Gran Turismo 7 (PPSA01317) stops at this refusal in #4782 ("Next stop"), once the earlier stops are past:

blending into a color target with a reversed component order is not implemented (CB_BLEND0_CONTROL 0x65010504, component mapping 0x1b)

The register values come from the reporter's local build. On main the same throw in DecodeState (State.cpp) prints no values.

#1425 renders 8_8_8_8 and 16_16_16_16 targets with COMP_SWAP 2 (SWAP_STD_REV) or 3 (SWAP_ALT_REV). It does this by permuting the pixel exports onto an RGBA attachment. Mapping 0x1b leaves the attachment holding (A, B, G, R) and 0x93 leaves it holding (A, R, G, B). Vulkan blends channels 0-2 with the color equation and channel 3 with the alpha equation. A plain Vulkan blend would therefore blend the exported alpha as a color and read source alpha from red or blue. That is why #1425 refused blending.

GT7's CB_BLEND0_CONTROL 0x65010504 has separate alpha. Field layout and factor numbering from Mesa 26.2.2's gfx10.json (the same as CIK pp. 226-228):

  • color: SRC_ALPHA * src + ONE_MINUS_SRC_ALPHA * dst
  • alpha: ONE * src + ONE_MINUS_SRC_ALPHA * dst

Semantics. The color block blends in shader export order and applies COMP_SWAP afterwards. This means:

  • the alpha equation and every source-alpha factor use the exported alpha;
  • the destination alpha is memory component 0, which holds A under both reversed swaps.

Sources:

  • AMD SI (p. 229) and CIK (p. 238) 3D register references. CB_TARGET_MASK is described in "the order of components after blending and before COMP_SWAP is applied". The same CIK reference (p. 233) has BLEND_CLAMP clamp UNORM, SNORM and sRGB sources to the format range before blending.
  • RADV builds CB_BLENDn_CONTROL from the Vulkan state without looking at the swap (radv_pipeline_graphics.c). It renders R4G4B4A4, B4G4R4A4, R5G5B5A1 and B5G5R5A1 through SWAP_STD_REV and SWAP_ALT_REV (vk_format.c, ac_formats.c).
  • Mesa CI's Navi10 (gfx1010) CTS run, job 112381553 and 112381554 (Mesa 3e32b0e1f1, pipeline 1764188). Their results.csv.zst artifacts pass all 400 blend.format.<format>.states cases for each of those four formats. Those cases use separate color and alpha factors, including DST_ALPHA, against a reference blended in API order. Mesa treats the PS5's gfx1013 as GFX10 like Navi10 (ac_gpu_info.c).

This has not been measured on gfx1013 or on 8_8_8_8 and 16_16_16_16 targets. Carrying it over to them relies on COMP_SWAP being the same field for every format. The new entry in docs/dev/debt/libSceAgcDriver.md says so.

Design. TranslateReversedBlend (State.cpp) keeps factor pairs that Vulkan applies per channel as Vulkan factors. These are ZERO/ZERO, ONE/ONE, SRC_COLOR/SRC_ALPHA, DST_COLOR/DST_ALPHA and the ONE_MINUS forms. For other combinations it searches for Vulkan factors that reproduce the guest blend from copies of the export (since 0866138; see the update of 2026-10-14 below):

  • the pixel shader writes a second color at location 0 index 1 whose channels are each the exported alpha, the channel's own export, 0 or 1;
  • a channel of MRT0 whose source factor is ONE may become 1, with its export moved into the factor;
  • the blend forms every factor, 1 - and product through SRC1_COLOR, SRC1_ALPHA and their ONE_MINUS forms. The shader computes nothing.

For GT7 that gives the following in attachment order, which equals the guest blend for both mappings:

  • MRT0 unchanged
  • second color = (1, a, a, a)
  • SRC1_COLOR/ONE_MINUS_SRC1_ALPHA, SRC1_ALPHA/ONE_MINUS_SRC1_ALPHA, ADD

The shader side reuses #1492's fragment output post-pass (FragmentOutputSpecialization.hpp). It adds the location 0 index 1 output and rewrites each whole store to MRT0. The emitter is unchanged. The code is a pipeline specialization constant (ReversedBlend), so it is in the module and draw keys but not in the prepared shader key, and a registered pixel shader is reused. Request serialization goes to version 26. Pipelines with a code need dualSrcBlend, and shader validation accepts the index 1 output for them.

Still refused, each with its own message that now includes the blend control, the mapping and the format:

  • sRGB targets (the order of the sRGB conversion under the swap is not settled), SNORM targets (no title evidence) and integer targets;
  • constant and second-source factors;
  • DST_ALPHA, ONE_MINUS_DST_ALPHA and SRC_ALPHA_SATURATE in the color equation (they need the first channel in the other three);
  • factor combinations that copies of the export cannot reproduce, for example ONE_MINUS_SRC_ALPHA in the alpha equation with SRC_COLOR in the color equation;
  • different color and alpha operations;
  • MIN/MAX with factors other than ONE (CIK p. 228: MIN and MAX do not force the factors to ONE);
  • other color exports when the blend needs the second color;
  • devices without dualSrcBlend.

GT7's CB_COLOR0_INFO and CB_SHADER_MASK for that draw are unknown. If the target is sRGB or the shader exports more than one MRT, GT7 stops at the new, more specific message instead.

Overlaps:

Tested

WSL2 Ubuntu 26.04, GCC 15.2.0, Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON. Tests ran on lavapipe (llvmpipe, LLVM 21.1.8) with CI's VK_ICD_FILENAMES=<lvp_icd>, ANYPS5_REQUIRE_VULKAN=1 and GALLIUM_OVERRIDE_CPU_CAPS=avx.

  • New tests against main 14036cb, with only the declarations they need added (the two reversedBlend fields, the ReversedBlend struct, two ReversedBlendCode constants and a TranslateReversedBlend that throws main's message):
    • agc_driver_reversed_order_blend and agc_driver_graphics stop with blending into a color target with a reversed component order is not implemented;
    • agc_shader_memory fails with "pixel fields or export mappings were lost in serialization".
    • Without those declarations the tests do not compile against main.
  • This change (122bfe6ef):
    • Full ctest: 1050 tests, no failures (1007 pass, 43 skip).
    • main 14036cb: 1049 tests, no failures (1006 pass, 43 skip).
    • A per-test comparison shows no difference apart from the new test.
    • agc_driver_reversed_order_blend takes 0.3 s.
  • Merged onto main e152ba8 without conflicts: 1062 tests, no failures (1019 pass, 43 skip). main e152ba8 alone: 1061 tests, no failures (1018 pass, 43 skip). The only difference is the new test.
  • Exhaustive check outside the tree. Every CB_BLEND_CONTROL with factors 0-10 and 13-20 and functions 0-4, with and without separate alpha, went through TranslateReversedBlend for both mappings, the three accepted formats and FORCE_DST_ALPHA_1 on and off (39 million cases). The 217,620 accepted cases were blended through a model of the post-pass and the Vulkan blend, with 12 inputs each, including UNORM exports outside [0, 1]. Each result equals the CB_BLEND_CONTROL definition applied in export order and then swapped.
    • One difference is not from this PR. With FORCE_DST_ALPHA_1, SRC_ALPHA_SATURATE in the color equation becomes ZERO, as main already does for every target. That differs from min(As, 0) when a float target gets a negative alpha.
    • The post-pass was checked against the same model. Every code it accepts produced a module that spirv-val accepts, and its stores matched the model on 600 inputs each, including NaN and infinities. It accepts exactly the 1,201 valid codes.
  • Mutation check. Each of these makes agc_driver_reversed_order_blend fail:
    • dropping the shader's UNORM clamp;
    • dropping the premultiply;
    • using the color factor for the second color's alpha.

Windows 11 Pro, NVIDIA GeForce RTX 5080 (driver 616.92, Vulkan 1.4.351), WinLibs GCC 15.2.0 r7, Release.

  • At cf4a06cbd, which differs from a64bb63ae only in the specialization id (8234 there): ctest -R agc_ gives 475 tests with no failures. One skip is expected: agc_driver_stencil_ref_export, because the driver has no VK_EXT_shader_stencil_export.
  • At a64bb63ae: agc_driver_reversed_order_blend, agc_driver_dual_source_blend, agc_driver_swap_alt_blend, agc_driver_graphics and agc_shader_memory pass with --repeat until-fail:3. agc_driver_reversed_order_blend takes 0.4 s.
  • 122bfe6ef, which keeps the code of a reversed target that needs the shader when a later reversed target needs none, ran on WSL only.

New tests:

  • agc_driver_reversed_order_blend, a GPU execution test.
    • Targets: 8_8_8_8 UNORM and 16_16_16_16 FLOAT, tiled 256x128, with SWAP_STD_REV and SWAP_ALT_REV.
    • The blend state and the code come from DecodeState on a register set. The draw goes through the registered-shader path.
    • Blends: 0x65010504 (with all four channels written and with only red, green and blue), 0x65810584, 0x40000501, 0x61000104, 0x61020302, 0x61010504 (since 1d6edc4), 0x60060008, 0x40000101 and 0x40000141.
    • Exports: 32_ABGR and FP16_ABGR. For UNORM, exports outside [0, 1]; for FLOAT, an alpha of 1.5.
    • Every pixel is compared with a separate reference. The reference decodes the CB_BLEND_CONTROL fields, blends in logical RGBA with the AMD factor definitions (clamping the source for UNORM), and places the channels in memory by the AMD COMP_SWAP text. Tolerance is ±1 code for UNORM8 and 2^-11 · max(1, |value|) for FP16. Pixels outside the scissor and channels outside the write mask must be unchanged.
  • agc_driver_graphics:
    • decode of every case above for both swaps and three target formats, checked against TranslateReversedBlend called directly;
    • each refusal;
    • the write mask;
    • a second written MRT, including a second reversed target that Vulkan blends alone after one that needs the shader;
    • registered-shader reuse with its own pipeline variant;
    • the location 0 index 1 output;
    • ValidateShaders;
    • invalid codes.
  • agc_shader_memory: version 26 round-trip; the version 25 layout read without the new four bytes, the 23 and 24 layouts without the dither flag as well, and the 19 to 22 layouts without the layer export flag as well.
  • request_serializer: main's fixtures now upgrade to version 26 with the four zero bytes, and its version 24 fixtures are also read as version 25, with and without the dither flag.
  • The main test that expected a ONE/ZERO blend into a reversed target to be refused now expects it to be accepted.

This change was not run on AMD hardware or on a PS5. A request for a reviewer with an RDNA GPU and RADV:

  • Run deqp-vk --deqp-case='dEQP-VK.pipeline.monolithic.blend.format.*_unorm_pack16.states.*'. Its r4g4b4a4, b4g4r4a4, r5g5b5a1 and b5g5r5a1 cases exercise exactly this export-order blend on SWAP_STD_REV/SWAP_ALT_REV targets. The Navi10 run covers GFX10, the PS5's generation in Mesa; a GFX10.3 or later pass would show the behaviour did not change after it.
  • Run ctest -R agc_driver_reversed_order_blend there.

Whether GT7 runs past this stop was not verified by us.

main's tools/check_conventions.py --base origin/main --title ... --body ... passes.

Final branch, one commit (64071f5) on main 759c665, after moving the request serialization from version 22 to 23 because main took 22 for #4114:

  • WSL2 Ubuntu 26.04, lavapipe (Mesa 26.0.8), CI configuration: agc_driver_reversed_order_blend, agc_shader_memory, agc_driver_graphics, agc_driver_dual_source_blend and agc_driver_swap_alt_blend pass, and the full ctest passes 1063 of 1063.
  • Native Windows 11, WinLibs GCC 15.2.0, NVIDIA GeForce RTX 5080 (driver 616.92): the same five tests pass 3 of 3 runs, and ctest -R 'agc_shader|agc_driver|shader|recompiler' passes 386 of 386.

Rebased onto main fdefd76 (66ae603). #5097 had taken request version 23 and specialization id 8234 for the layer export flag, so the reversed-order blend code moved to version 24; the writer emits the layer flag and then the code, the reader reads the flag from version 23 and the code from version 24, and the module key and specialization constants keep the same order. WSL2 Ubuntu 26.04, lavapipe (Mesa 26.0.8), CI configuration: the five blend and serialization tests pass, and the full ctest passes 1074 of 1074.

Rebased on 2026-10-14 onto main 0b6d033 (8c9a939). Two conflicts:

The added and removed lines against the merge base are the same before and after the rebase; only context lines changed. main still writes request version 23 and its highest pixel specialization id is still 8234, so version 24 and id 8235 stay. #5137's version 21 and 22 trailer handling on main reads after the pixel info and is unaffected. The refusal is unchanged on main 0b6d033. #4782 was closed on 2026-10-14 with its earlier stops (#4834, #4809, #1418) on main and this stop left to this PR; we did not run GT7.

  • WSL2 Ubuntu 26.04, GCC 15.2.0, lavapipe (Mesa 26.0.8), CI configuration: agc_driver_reversed_order_blend, agc_shader_memory, agc_driver_graphics, agc_driver_dual_source_blend, agc_driver_swap_alt_blend and request_serializer pass. The full ctest runs 1111 tests with no failures (1068 pass, 43 skip).
  • Native Windows 11, WinLibs GCC 15.2.0, NVIDIA GeForce RTX 5080 (driver 616.92), Release: the first five of those tests pass 3 of 3 runs with ANYPS5_REQUIRE_VULKAN=1.

Rebased again on 2026-10-14 onto main 1015c55 (9a88161). #4201 took request version 24 for the tessellation primitive group size and #4451 took version 25 for a dithered alpha-to-coverage flag written after the layer export flag. That conflicted in RequestSerializer.cpp, Recompiler.hpp, DrawRegisterKey.cpp and ShaderMemory.cpp. The reversed-order blend code moved to version 26: it follows the dither flag in ShaderPixelStageInfo and in the writer, the reader reads it from version 26, and sameDecode compares both fields. main has no specialization id 8235, so it stays; the module key and the specialization constants are unchanged, because #4451 put its flag in the prepared shader key instead. agc_shader_memory now also reads the version 23 to 25 layouts, and main's request_serializer expects version 26 with the four extra bytes. Apart from that, the added and removed lines against the merge base are the same as before. main refuses alpha-to-coverage into a component-swapped MRT0 target, dithered or not, so #4451's coverage never reads a reversed-order export.

  • WSL2 Ubuntu 26.04, GCC 15.2.0, lavapipe (Mesa 26.0.8), CI configuration: agc_driver_reversed_order_blend, agc_shader_memory, agc_driver_graphics, agc_driver_dual_source_blend, agc_driver_swap_alt_blend and request_serializer pass. The full ctest runs 1149 tests: 1102 pass, 46 skip for device or display limits, and agc_driver_waterfall_images fails SPIR-V validation (DerivativeGroupQuadsKHR with an 8x1 local size). main 1015c55 fails that test the same way in its own linux CI job.
  • Reading the code from version 25, or writing it before the dither flag, makes both request_serializer and agc_shader_memory fail.

Update 2026-10-14 (0866138 and 1d6edc4, on top of 9a88161): the reversed-order path now rounds like the plain blend.

  • Cause of the 161 on the Intel iGPU: the previous revision computed 1 - x (and, for some rows, the premultiplied export) in the pixel shader and handed it to the blend as the second color. A plain blend converts the export once and forms 1 - x from that converted value; the split path converted 0.5 for SRC_COLOR and, separately, the shader's 1 - 0.5 for the destination factor. Vulkan only asks the blend for a precision no lower than the destination's (Blending), so the inputs may sit on the 8-bit grid, and a conversion to that grid may go to either neighbour (fundamentals, conversion to normalized fixed-point: "one of the two ... closest", round to nearest only recommended), and 0.5 · 255 = 127.5 is a tie. With 0.5 converted to 128/255 on both inputs, the plain blend gives 64.25 + 192 · 127/255 = 159.87 → 160 and the split path 64.25 + 192 · 128/255 = 160.63 → 161, which matches the reported values; the RTX 5080 rounds this tie down and stored 160 and 159. So it was not Intel-specific: on the RTX 5080 and lavapipe the old split path differed from the same draw into a SWAP_STD target by up to 2 steps on other inputs, and on the RTX 5080 in 27% to 68% of the 16_16_16_16 FLOAT values for every swept control except 0x61000104.
  • Fix: the pixel shader only copies the export, as described under Design, so the blend is asked for the same expression on the same values as a plain blend. The values differ only in arriving partly as the second color (and, for a ONE source factor moved into the factor, as 1 · x instead of x · 1). Vulkan gives the second color no precision rule of its own (Dual-Source Blending), so bit equality with the plain blend rests on the device converting it like the first color. It does on the RTX 5080 and lavapipe, and RADV exports the second color in the first one's format (radv_pipeline_graphics.c); the test checks it on each device instead of widening the bound. Factor combinations that need shader arithmetic throw again, as on main: of the 1,995 combinations of factors 0-10 (color and separate alpha) that the previous revision computed in the shader, 932 are routed (GT7's 0x65010504 among them) and 1,063 throw. No tolerance was widened.
  • New check: agc_driver_reversed_order_blend also draws every case into a SWAP_STD target, which Vulkan blends alone, and requires identical stored bits, besides the ±1 comparison with the exact blend. agc_driver_graphics checks every factor combination TranslateReversedBlend accepts against the guest equations (121 plain, 932 routed) and that the post-pass emits no arithmetic. With the new test on 9a88161, the comparison fails on both devices in the first case, 0x65010504 into SWAP_STD_REV: 160 where SWAP_STD stores 159 (channel 1 on the RTX 5080, channel 0 on lavapipe); the ±1 comparison alone passed there. Letting the test continue past failures on the RTX 5080, 14 cases fail on 9a88161, among them 0x61020302 with exports outside [0, 1]: channel 2 of pixel (3, 0) holds 159 where SWAP_STD holds 160, the pixel of the Intel report rounded the other way. 1d6edc4 adds 0x61010504 (SRC_ALPHA + ONE_MINUS_SRC_ALPHA, alpha ONE + ONE), which moves the export into the factor; it also fails on 9a88161.
  • Results with 0866138 (the six tests named here also with 1d6edc4): on the RTX 5080 (driver 616.92, Windows 11, WinLibs GCC 15.2.0) and on lavapipe (WSL2 Ubuntu 26.04, CI configuration), agc_driver_reversed_order_blend passes 10 of 10 runs and agc_driver_graphics, agc_shader_memory, agc_driver_dual_source_blend, agc_driver_swap_alt_blend and request_serializer pass. A local sweep of seven routed controls (export values on and between the 1/255 grid, outside [0, 1], hashed destinations) found 0 of 1,179,648 channel values different from SWAP_STD per control, format (8_8_8_8 UNORM and 16_16_16_16 FLOAT) and swap on both devices. The full WSL ctest runs 1149 tests: 1102 pass, 46 skip for device or display limits, and agc_driver_waterfall_images fails as on main 1015c55. The full ctest ran at 0866138; 1d6edc4 changes only the execution test. The branch still merges into main 84b01f9 without conflicts, and main still writes request version 25 and has no specialization id 8235.
  • Not run on Intel or AMD hardware by us.

Rebased on 2026-10-14 onto main e27ff7c (313c296). The only conflict was Graphics/include/State.hpp: #4767 gave DecodeColorBuffer an allowMultisampleDcc parameter next to this PR's ReversedBlend declarations; both are kept, and every existing call still matches through the default argument. The added and removed lines against the merge base are identical to before. main still writes request version 25, so this PR's version 26 and specialization id 8235 stay. WSL2 Ubuntu 26.04, GCC 15.2.0, lavapipe (Mesa 26.0.8), CI configuration: request_serializer, agc_shader_memory, agc_driver_graphics, agc_driver_dual_source_blend, agc_driver_reversed_order_blend and agc_driver_swap_alt_blend pass, and agc_driver_reversed_order_blend passes 10 of 10 repeats. The full ctest passes 1189 of 1191; the two failures are the relinker's file_writer_binary and file_writer_text, which stop with coreutils: unknown program 'running' because Ubuntu 26.04 ships coreutils as one multi-call program. They are unrelated to this PR, which does not touch the relinker.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: PPSA01317 Gran Turismo 7 (PPSA01317): ten missing exports, two USB storage libraries, and unaligned shader headers in ABI resolution (patches attached) #4782 (this stop for UNORM or float targets with one color export; the run past it was not verified by us)
  • GPUs tested: lavapipe (Mesa 26.0.8, llvmpipe) on WSL2 Ubuntu 26.04; NVIDIA GeForce RTX 5080, driver 616.92, Windows 11
  • AI-assisted: yes. Written with Claude Code; I reviewed every line and ran the tests above.

🤖 Generated with Claude Code

@kr4y4

kr4y4 commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

agc_driver_reversed_order_blend fails every time on an Intel iGPU, on one value in one case. It passes on NVIDIA.

Setup: Windows 11 (26200), WinLibs GCC 15.2.0 r7, Release, CI configure flags, an RTX 4070 Laptop GPU (NVIDIA 595.97) and an Intel Raptor Lake-S iGPU (32.0.101.6790). Both run the split cases, so neither skips for lack of dual-source blending. I tested the head 66ae603 as pushed, and the earlier 64071f5 merged into main 3b94f92. The rebase did not change the blend code or the test.

  • Intel: 0 of 10 direct runs pass on either head. Each fails with:

    8_8_8_8 UNORM SWAP_STD_REV, SRC_COLOR + ONE_MINUS_SRC_COLOR, alpha SRC_COLOR + ONE, 32_ABGR exports outside [0, 1]: memory component 1 (channel 2) of pixel (3, 0) inside the scissor holds 161, expected 159.75
    

    The full suite on 66ae603 passes 1091 of 1092, and this test is the only failure.

  • NVIDIA: 10 of 10 runs pass on each head, and the full suite on 64071f5 merged into main passes 1087 of 1087.

  • I ran a local copy of the test that reports every mismatch instead of stopping at the first. On Intel this is the only wrong value. It is wrong in both swaps (SWAP_STD_REV and SWAP_ALT_REV), in channel 2 of all 4096 pixels whose destination is (128, 64, 192, 255), and nowhere else. That channel exports 0.5, so the expected value is 0.5 × 0.5 × 255 + (1 − 0.5) × 192 = 159.75.

  • For comparison I added SWAP_STD to that copy. With it reversedBlend is 0, so Vulkan applies the whole blend. Channel 2 of those pixels holds:

    Vulkan alone (SWAP_STD) split (SWAP_STD_REV, SWAP_ALT_REV)
    NVIDIA 160 159
    Intel 160 161

    So on both GPUs the split rounds this value one step away from the result of the native blend, in opposite directions, and only Intel's result falls outside the ±1 tolerance. With the in-range export (0.125), the same channel reads 172 against 171.98 on both paths and both GPUs.

Written with AI assistance.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

agc_driver_reversed_order_blend fails on every one of kr4y4's Intel runs (161 against 159.75, outside the ±1 tolerance). Please make the split blend round like the native one, or show why that case needs a wider tolerance.

It also conflicts with main in State.cpp after #4862; please rebase.

@nikowolf123
nikowolf123 force-pushed the fix/agc-blend-reversed-component-order branch from 66ae603 to 8c9a939 Compare October 14, 2026 07:14
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@adriadam10 adriadam10 self-assigned this Oct 14, 2026

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked on Linux (lavapipe): agc_driver_reversed_order_blend, agc_driver_graphics and agc_shader_memory pass on this head. Two things block the merge:

  • Intel result outside tolerance. As @kr4y4 and @oneandonlydean reported, agc_driver_reversed_order_blend fails on an Intel iGPU (161 vs 159.75, tolerance ±1), while the same blend through Vulkan alone gives 160 on both vendors. Either make the split path round like the native one, or show from the Vulkan blend-precision rules why this case needs a wider tolerance and pin it with that reason. Widening it without that leaves the test unable to catch a real regression on that row.
  • Conflicts with main. main now writes request version 25 (dithered alpha-to-coverage), so version 24 is taken: RequestSerializer.cpp, Recompiler.hpp, DrawRegisterKey.cpp and tests/ShaderMemory.cpp conflict. The code needs to move to the next free version and the legacy-request cases in ShaderMemory.cpp must keep their layouts. Please rebase after the first point, rerun the five tests you list, and note the new version in the body.

@nikowolf123
nikowolf123 force-pushed the fix/agc-blend-reversed-component-order branch from 8c9a939 to 9a88161 Compare October 14, 2026 13:15
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 14, 2026

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the rebase; the version 26 move resolves the conflicts. The first point of the last review is still open:

  • Intel result outside tolerance. agc_driver_reversed_order_blend still compares UNORM8 with <= 1.0 (ReversedOrderBlend.cpp:392), and the blend code is unchanged, so the Intel iGPU run reported by @kr4y4 and @oneandonlydean (161 vs 159.75) should still fail. Either make the split path round like the native one, or show from the Vulkan blend-precision rules why this case needs a wider tolerance and pin it with that reason. Widening it without that leaves the test unable to catch a real regression on that row.

Checked on Linux with the rebased head: agc_driver_graphics, agc_shader_memory and agc_driver_reversed_order_blend pass.

@nikowolf123

Copy link
Copy Markdown
Contributor Author

@adriadam10 Thanks. I took the first option: the split path now rounds like the native blend, and the ±1 bound is unchanged. Thanks also to @kr4y4 for the Intel runs.

Cause of the 161. The previous revision computed the factor in the pixel shader. For SRC_COLOR + ONE_MINUS_SRC_COLOR the shader wrote 1 - 0.5 into the second color. A plain blend converts the export once and forms 1 - x from that converted value. The split path instead converted 0.5 for SRC_COLOR and, separately, the shader's 1 - 0.5 for the destination factor. Vulkan only asks the blend for a precision no lower than the destination's (Blending), so the inputs may sit on the 8-bit grid, and a conversion to that grid may go to either neighbour (round to nearest is only recommended, fundamentals-fpfixedconv). 0.5 · 255 = 127.5 is a tie:

  • With 0.5 converted to 128/255 on both inputs, the plain blend gives 64.25 + 192 · 127/255 = 159.87, stored as 160. The split path gives 64.25 + 192 · 128/255 = 160.63, stored as 161. These are the reported values.
  • The RTX 5080 rounds this tie down, so it stored 160 and 159.

So this was not specific to Intel. On the RTX 5080 and lavapipe, the old split path also differed from the same draw into a SWAP_STD target by up to 2 steps on other inputs.

What changed (0866138 and 1d6edc4, on top of 9a88161). The pixel shader now only copies the export. Each channel of the second color is the exported alpha, the channel's own export, 0 or 1. An MRT0 channel whose source factor is ONE may become 1, with its export moved into the factor. The blend then forms every factor, 1 - and product through SRC1_*/ONE_MINUS_SRC1_*, so it converts the same values a plain blend converts.

  • For this row the state is SRC_COLOR/ONE_MINUS_SRC1_COLOR with the second color (0, own, own, own), so the blend forms 1 - the converted 0.5 itself, as in the plain blend, instead of converting a 1 - 0.5 from the shader.
  • For GT7's 0x65010504 it is SRC1_COLOR/ONE_MINUS_SRC1_ALPHA, SRC1_ALPHA/ONE_MINUS_SRC1_ALPHA with (1, a, a, a).
  • Combinations that would need shader arithmetic throw again, as on main. Of the 1,995 combinations the shader used to compute, 932 are routed and 1,063 throw.

What Vulkan does and does not promise here. The two paths now ask the blend for the same expression on the same values; they differ only in that some values arrive as the second color (and, for the ONE case above, as 1 · x instead of x · 1). Vulkan does not say that the second color is converted like the first (Dual-Source Blending gives it no precision rule of its own), so bit equality rests on the device doing so. It holds on the RTX 5080 and lavapipe, and RADV exports the second color in the first one's format (radv_pipeline_graphics.c). That is why the test compares with the device's own SWAP_STD blend instead of a wider bound.

How it was verified.

  • agc_driver_reversed_order_blend now also draws every case into a SWAP_STD target, which Vulkan blends alone, and requires identical stored bits. The ±1 comparison with the exact blend stays as it was. 1d6edc4 adds 0x61010504 (SRC_ALPHA + ONE_MINUS_SRC_ALPHA, alpha ONE + ONE), the case that moves the export into the factor.
  • Run against 9a88161, the new comparison fails on both devices in the first case (160 where SWAP_STD stores 159). Letting the test continue on the RTX 5080, 14 cases fail, including this row with exports outside [0, 1]: channel 2 of pixel (3, 0) holds 159 where SWAP_STD holds 160, the pixel of the Intel report rounded the other way.
  • With 1d6edc4 it passes 10 of 10 runs on the RTX 5080 (driver 616.92) and on lavapipe.
  • A local sweep (export values on and between the 1/255 grid, outside [0, 1], hashed destinations) found 0 of 1,179,648 values different from SWAP_STD on both devices, for each of seven routed controls, 8_8_8_8 UNORM and 16_16_16_16 FLOAT, and both swaps.
  • agc_driver_graphics checks every factor combination the decoder accepts against the guest equations, and checks that the post-pass adds no arithmetic.
  • The other tests and the full WSL ctest results are in the description's update of 2026-10-14.

@kr4y4, could you re-run ctest -R agc_driver_reversed_order_blend --output-on-failure on the Intel iGPU at 1d6edc4? If the Intel driver converts the second color differently from the first, the SWAP_STD comparison will report the pixel and both values.

AI-assisted: written with Claude Code; I checked the change and ran the tests described.

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-checked 0866138 and 1d6edc4: the pixel shader now only copies the export (alpha, own channel, 0/1) into the second color, so the blend forms 1 - x and the products from the same converted values as a plain Vulkan blend, and the ±1 bound stays. The new agc_driver_reversed_order_blend comparison against the same draw into a SWAP_STD target pins this bit for bit, and the combinations that still needed shader arithmetic throw again.

Checked on Linux with the merged tree: agc_driver_graphics, agc_shader_memory, agc_driver_reversed_order_blend and request_serializer_tests pass; without the change the tests do not compile. Not run here: Intel/AMD hardware (@kr4y4, a rerun of the Intel iGPU on this head would close the original report). Merging once the full Build is green.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@nikowolf123
nikowolf123 force-pushed the fix/agc-blend-reversed-component-order branch from 1d6edc4 to 313c296 Compare October 14, 2026 18:58
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@adriadam10

Copy link
Copy Markdown
Collaborator

Re-checked 313c296 after the rebase: the added and removed lines are identical to 1d6edc4, which we approved. On the merged tree agc_driver_graphics, agc_shader_memory, agc_driver_reversed_order_blend and the prepared-graphics tests pass. Not run here: Intel/AMD hardware. Merging once the full Build is green.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@adriadam10
adriadam10 force-pushed the fix/agc-blend-reversed-component-order branch from 313c296 to db0f4bc Compare October 14, 2026 19:42
@adriadam10

Copy link
Copy Markdown
Collaborator

main took request serialization version 26 (linked call fallbacks), which collided with this PR's version 26. We rebased and moved the reversed-order blend code to version 27: RequestSerializer.cpp (reader, writer, accepted range), the base64 prefixes in ShaderMemory.cpp and the byte offsets in RequestSerializerTests.cpp. We added one commit: the pre-26 fixtures in ShaderMemory.cpp drop the 8-byte fallbacks trailer. Otherwise the added and removed lines are the ones we approved.

On the rebased tree request_serializer, agc_shader_memory, agc_driver_graphics, agc_driver_reversed_order_blend and the other AGC driver tests pass; agc_driver_tessellation segfaults as it does on main. Not run here: Intel/AMD hardware. Merging once the full Build is green.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@adriadam10
adriadam10 force-pushed the fix/agc-blend-reversed-component-order branch from db0f4bc to 899d83e Compare October 14, 2026 21:30
@adriadam10

Copy link
Copy Markdown
Collaborator

main took request serialization version 27 (front-face all-bits flag) and specialization id 8235, which collided with this PR. We rebased and moved the reversed-order blend code to version 28 and id 8236: RequestSerializer.cpp (reader, writer, accepted range), PipelineSpecialization.hpp, the base64 prefixes and the unsupported-version fixture in ShaderMemory.cpp, and the byte offsets in RequestSerializerTests.cpp. The version fixtures in ShaderMemory.cpp now also replay the version 27 and 26 layouts and drop the 8-byte fallbacks trailer for the older ones. Otherwise the added and removed lines are the ones we approved.

On the rebased tree request_serializer, agc_shader_memory, agc_driver_graphics, agc_driver_reversed_order_blend and the other AGC driver tests pass; agc_driver_tessellation segfaults as it does on main. Not run here: Intel/AMD hardware. Merging once the full Build is green.

nikowolf123 and others added 4 commits October 15, 2026 12:51
8_8_8_8 and 16_16_16_16 targets with COMP_SWAP 2 or 3 keep the exported
alpha in their first channel and red or blue in their fourth, so a plain
Vulkan blend would run the alpha equation on a color channel. Such draws
threw "blending into a color target with a reversed component order is
not implemented" (Gran Turismo 7, boykopovar#4782).

The AMD SI and CIK register references describe CB_TARGET_MASK in the
order of components after blending and before COMP_SWAP is applied, and
RADV programs CB_BLENDn_CONTROL without regard to the swap while it
renders R4G4B4A4, B4G4R4A4, R5G5B5A1 and B5G5R5A1 through SWAP_STD_REV
and SWAP_ALT_REV and passes the CTS blend tests on Navi10. The color
block is therefore taken to blend in export order: the alpha equation
and source-alpha factors use the exported alpha and the destination
alpha is the first channel.

TranslateReversedBlend keeps the factor pairs Vulkan applies per channel
in the pipeline. Other source factors are computed by the pixel shader
from its MRT0 export (clamped for UNORM targets, as BLEND_CLAMP says)
and handed to the blend as a dual-source second color, which the
fragment output post-pass adds at location 0 index 1; when both
positions need it, the export is premultiplied by its source factor.
The code is a pipeline specialization, serialized in request version
26, and stays out of the prepared shader key.

sRGB, SNORM and integer targets, constant and second-source factors,
destination alpha in the color equation, destination factors the two
equations do not share, different color and alpha operations, MIN or
MAX with factors other than ONE, other color exports when the shader
computes factors, and devices without dualSrcBlend still throw, now
with the blend control, mapping and format in the message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reversed-order blend computed the factors Vulkan cannot apply per
channel in the pixel shader (1 - x in fp32, a clamp for UNORM targets
and, when both positions needed it, a premultiplied export) and passed
them to the blend as a dual-source second color. A plain blend converts
the export once and forms 1 - x and the products from that converted
value, so the two paths can round differently. On UNORM8 the difference
reaches one step per substituted factor: an Intel iGPU stored 161 where
the exact blend is 159.75 for SRC_COLOR + ONE_MINUS_SRC_COLOR with an
export of 0.5 over 192 (160 through SWAP_STD), and on an RTX 5080 and
lavapipe the split path differed from the same draw into a SWAP_STD
target by up to 2 steps on other inputs.

The pixel shader now only copies values. Each channel of the second
color is the exported alpha, the channel's own export or the constant 0
or 1, and a channel of the first color whose source factor is ONE may
become 1 with its export moved into the factor. Every factor, 1 - x and
product comes from the blend through SRC1_COLOR, SRC1_ALPHA and their
ONE_MINUS forms, applied to copies of the same export values that the
first color carries. TranslateReversedBlend searches for Vulkan factors
and copy routes that reproduce each channel's guest factors and throws
for combinations no routing reproduces, such as ONE_MINUS_SRC_ALPHA in
the alpha equation next to SRC_COLOR in the color equation: of the 1995
factor combinations the shader used to compute, 932 are routed and the
others throw again, as on main. GT7's 0x65010504 is among the routed
ones. The code is now four 2-bit routes for each color, the same for
every format, and the post-pass emits no arithmetic.

agc_driver_reversed_order_blend also draws each case into a SWAP_STD
target, which Vulkan blends alone, and requires identical stored bits;
the comparison with the exact blend keeps its 1-step tolerance.
agc_driver_graphics checks every factor combination TranslateReversedBlend
accepts against the guest equations and that the post-pass adds no
arithmetic instructions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TranslateReversedBlend may write 1 into a channel of the first color
whose source factor is ONE and take that channel's export into the
factor through the second color, when one second color cannot carry
both the source and the destination factor. None of the blends in
agc_driver_reversed_order_blend took that route, so no GPU test ran it.

The test now also blends 0x61010504 (SRC_ALPHA + ONE_MINUS_SRC_ALPHA,
alpha ONE + ONE), whose alpha channel is routed that way, into both
formats with both reversed swaps, and compares it with the exact blend
and bit for bit with the same draw into a SWAP_STD target. It passes on
an RTX 5080 and lavapipe. With 9a88161's shader-computed factors it
fails on the RTX 5080: 160 where SWAP_STD stores 159.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fixture message

Co-Authored-By: Claude <noreply@anthropic.com>
@adriadam10

Copy link
Copy Markdown
Collaborator

The full Build on the previous head failed request_serializer on Windows (abort, 0xc0000409). It was our rebase: the "future version" fixture in RequestSerializerTests.cpp still used 28, which is a valid version since we moved the blend code to 28.

main has also taken request serialization version 28 (quad pixel mask) and 29 (quad sample masks), so we rebased again and moved the reversed-order blend code to version 30, after the quad masks: RequestSerializer.cpp (reader, writer, accepted range), the base64 prefixes and the version 26 to 29 fixtures in ShaderMemory.cpp, and the byte offsets and the future version (31) in RequestSerializerTests.cpp. Otherwise the added and removed lines are the ones we approved.

On the rebased tree request_serializer, agc_shader_memory, agc_driver_graphics, agc_driver_reversed_order_blend and the other AGC driver tests pass; agc_driver_tessellation segfaults as it does on main. The agc_interpolant_mapping timeout in that run is in a test this PR does not touch and passes in 0.01 s here; the next full Build will show whether it repeats. Not run here: Intel/AMD hardware. Merging once the full Build is green.

@adriadam10
adriadam10 force-pushed the fix/agc-blend-reversed-component-order branch from 899d83e to 3e3c3bf Compare October 15, 2026 11:02
@github-actions

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#6222 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp L2995, L3113, L3238-3242, L3252-3254, L3259, L3262-3264
core/shader/recompiler/tests/RequestSerializerTests.cpp L209, L248, L273, L275-276, L397, L399-401, L413, L415
#6235 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp L147
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp L1010-1011
core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp L2995, L3113, L3227, L3234, L3252-3254, L3257-3259
core/shader/recompiler/Recompiler.hpp L158

@github-actions github-actions Bot mentioned this pull request Oct 15, 2026
11 tasks
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 15, 2026
@adriadam10
adriadam10 merged commit 0f0ca7b into boykopovar:main Oct 15, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build docs shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants