Skip to content

feat(agc): rasterize a multisampled draw into one-sample targets at sample 0's locations - #5907

Merged
adriadam10 merged 3 commits into
boykopovar:mainfrom
simonbronk:feat/mixed-sample-truncate
Oct 16, 2026
Merged

adriadam10 merged 3 commits into
boykopovar:mainfrom
simonbronk:feat/mixed-sample-truncate

Conversation

@simonbronk

@simonbronk simonbronk commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

What

With #5861 (merged), Onimusha: Way of the Sword (PPSA27836) gets past its checkerboard sample pattern, then stops at the next part of the same technique. It rasterizes with two samples into one-sample targets:

Draw PA_SC_AA_CONFIG Color targets Depth target SPI_PS_INPUT_ENA / ADDR PA_SC_MODE_CNTL_0
first 0x00108001 (2 samples) one, CB_COLOR_ATTRIB 0 (1 sample) none 0x302 (PERSP_CENTER, POS_X, POS_Y) 0x3
second same 1 sample stencil only, 1 sample (DB_DEPTH_CONTROL 0x002002f1) 0x302 0x3

The sample locations are the checkerboard pattern from #5861: (+4, 0)/16 and (-4, 0)/16, swapped on odd rows. DecodeState threw "a color target's sample count differs from the rasterizer's" and then "the depth target's sample count differs from the rasterizer's". Resident Evil Village (PPSA01556, #5169) uses the same two-sample rasterization into one-sample depth and color targets; its tester approximated it locally.

A one-sample target can only keep the coverage of one sample. Vulkan defines this case through VK_AMD_mixed_attachment_samples: color sample i is covered when raster sample i is covered, and the other raster samples are dropped. That extension exposes how AMD hardware treats a target with fewer samples than the rasterizer. The PS5 GPU is AMD, but this is not measured on PS5 hardware.

When nothing else reads the other samples, that is the same as rasterizing one sample at sample 0's location. With the checkerboard grid, that location is (+4, 0)/16 on even rows and (-4, 0)/16 on odd rows. So:

  • rasterizesSampleZeroOnly admits a multisampled rasterizer when every target has one sample, MSAA_ENABLE is set, and there is no alpha-to-coverage and no sample-coverage pixel input:
    • without PS_ITER_SAMPLE, centroid inputs still throw. PERSP_SAMPLE, LINEAR_SAMPLE and ANCILLARY stay: a pixel shaded once is taken to get sample 0's barycentrics and sample index 0 (inferred, not measured). This rule follows fdfawef324's proposal for PPSA07411 below.
    • with PS_ITER_SAMPLE (feat(agc): run the pixel program per sample for PS_ITER_SAMPLE #5673), only sample 0's invocation reaches the targets, so sample and centroid inputs are taken at sample 0 and the draw is shaded once (perSampleShading is cleared). PPSA27836 needs this: during its first scene load it draws with PA_SC_MODE_CNTL_1 0x06030000 and SPI_PS_INPUT_ENA 0x2, 0xb01 (PERSP_SAMPLE, POS_X, POS_Y, POS_W), and without iteration 0xf01.
    • ANCILLARY with POS_X_FLOAT or POS_Y_FLOAT throws, because reading the sample index makes the host shade per sample, which moves FragCoord to sample 0's location.
    • The 0x1b3/0x1b4 reads stay behind the early conditions, as one-sample color draws may lack them.
  • rasterizeSampleZero keeps sample 0's location in each pixel of the grid, sets one sample, and marks the pattern custom or grid as it decodes. A one-sample depth target or the first one-sample color target triggers it. Any other mix still throws the same messages.
  • The device also queries sample locations and the grid for one sample (VK_SAMPLE_COUNT_1_BIT). vkGetPhysicalDeviceMultisamplePropertiesEXT reports a 2x2 grid for one sample on both GPUs here, as for two.
  • ValidateSampleLocations only enables sample locations for a one-sample draw when it has custom locations, so other one-sample pipelines are unchanged.
  • SampleLocationsCompatibleDepth now also answers for one sample. One-sample depth images are created with SAMPLE_LOCATIONS_COMPATIBLE_DEPTH where the device allows it, as multisampled ones already were. Vulkan requires that for a depth attachment drawn with custom locations.
  • The technical debt entry for custom sample locations is extended.

Tested

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 — not applicable
  • 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 — not applicable
  • Generic behaviour, not specific to one title
  • Depends on: none (feat(agc): rasterize sample locations that vary between pixels through a 2x2 grid #5861 is merged)
  • Unblocks: PPSA27836 Onimusha: Way of the Sword (PPSA27836) #5769, PPSA01556 Resident Evil Village (PPSA01556) #5169 (its second checkerboard approximation)
  • GPUs tested: NVIDIA GeForce RTX 5070, driver 617.42 (32.0.16.1742); AMD Radeon(TM) Graphics, Ryzen iGPU gfx1036, driver 32.0.21036.18; both Windows 11 Pro 26200
  • AI-assisted: yes

🤖 Generated with Claude Code

@simonbronk

Copy link
Copy Markdown
Contributor Author

Rebased onto main ed835aa now that #5861 is merged; only this PR's commit is left (2ce4e57). It applied without changes.

Tested again on Windows 11 Pro 26200, Release:

@simonbronk
simonbronk force-pushed the feat/mixed-sample-truncate branch from 81a8bd5 to 2ce4e57 Compare October 14, 2026 18:12
@github-actions

github-actions Bot commented Oct 14, 2026 •

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
PR 5915 core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp L416-417, L1037, L1051
docs/dev/debt/libSceAgcDriver.md L121
PR 6451 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp L122-123
docs/dev/debt/libSceAgcDriver.md L121
PR 6530 core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp L1051, L1116-1117
PR 6571 docs/dev/debt/libSceAgcDriver.md L120-121

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 1368 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 1395 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 1896 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
PR 4040 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 4072 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4448 docs/dev/debt/libSceAgcDriver.md
PR 4472 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4650 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 4891 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5021 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5390 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 5431 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5441 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 5480 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5499 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 5500 core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 5609 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
PR 5761 docs/dev/debt/libSceAgcDriver.md
PR 5925 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 5939 core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 5987 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6077 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
PR 6146 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6177 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6188 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6215 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6222 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6235 core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6252 docs/dev/debt/libSceAgcDriver.md
PR 6259 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6262 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
PR 6292 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
PR 6297 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6312 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6320 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6379 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
PR 6412 docs/dev/debt/libSceAgcDriver.md
PR 6457 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6479 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6481 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6523 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6539 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
PR 6548 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6553 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6615 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
PR 6635 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6678 docs/dev/debt/libSceAgcDriver.md
PR 6683 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6718 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6730 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6808 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6869 core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md

@fdfawef324

Copy link
Copy Markdown
Contributor

With #5907, PPSA07411 (Resident Evil 4) gets past its color-target draws. It now stops on a depth-only draw of the same checkerboard kind, with "the depth target's sample count differs from the rasterizer's". The draw's registers:

  • PA_SC_AA_CONFIG 0x00108001 (2x, MAX_SAMPLE_DIST 4, 2 exposed samples), PA_SC_MODE_CNTL_0 0x3 (MSAA_ENABLE), PA_SC_MODE_CNTL_1 0 (no PS_ITER_SAMPLE), DB_EQAA 0, PA_SC_AA_MASK all ones.
  • PA_SC_AA_SAMPLE_LOCS X0Y0 and X1Y0 0x0000040c, X0Y1 and X1Y1 0x00000c04. This is your PPSA27836 pattern with the rows swapped: sample 0 is at (-4, 0)/16 in the first row and at (+4, 0)/16 in the second.
  • DB_Z_INFO 0x20000183 (Z_32_FLOAT, one sample), DB_STENCIL_INFO 0x20000181, DB_DEPTH_SIZE_XY 0x077706a3 (1700x1912), DB_DEPTH_CONTROL 0x007007e7 (GEQUAL test and write, stencil ALWAYS), DB_RENDER_CONTROL 0.
  • No color target: CB_COLOR0..7_INFO 0, CB_TARGET_MASK 0xffffffff, CB_SHADER_MASK 0xf.
  • Pixel program: SPI_PS_INPUT_ENA = SPI_PS_INPUT_ADDR = 0x00002001 (PERSP_SAMPLE and ANCILLARY, no center pair), DB_SHADER_CONTROL 0x60 (KILL_ENABLE, Z_ORDER 2), SPI_SHADER_Z_FORMAT 0.

depthKeepsSampleZero already accepts a one-sample depth target with no color target, so depth-only is not the problem. The second Require throws because 0x2001 hits SampleDependentInputs.

I think PERSP_SAMPLE, LINEAR_SAMPLE and ANCILLARY can stay on this path:

  • Console: with no PS_ITER_SAMPLE and DB_EQAA.PS_ITER_SAMPLES 0, a pixel is shaded once. I take its sample barycentrics and its ANCILLARY sample index to be those of sample 0. This is inferred, not measured. The registers above point the same way: a half-width target whose program reads only sample barycentrics, with sample 0 moved left or right a quarter pixel by row, only makes sense if those barycentrics are sample 0's.
  • Host: the Vulkan Interpolation Decorations rule says: "If a sample count of 1 is used, and the sampleLocationSampleCounts limit includes VK_SAMPLE_COUNT_1_BIT, the interpolation position must be at the position of the sample being shaded". It applies to every input, not only Sample-decorated ones. ValidateSampleLocations already requires that bit for custom locations, so the recompiler's barycentrics land on sample 0's location. SampleId is 0 in a one-sample pipeline.
  • Centroid inputs still depend on sample 1's coverage, and SAMPLE_COVERAGE has a bit for sample 1, so both keep throwing.
  • One exception: reading the sample index makes Vulkan enable sample shading. FragCoord's x and y then sit at sample 0's location instead of the pixel center. So ANCILLARY together with POS_X_FLOAT or POS_Y_FLOAT should keep throwing.
constexpr std::uint32_t SampleDependentInputs = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PerspectiveCentroid) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::LinearCentroid) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage);
constexpr std::uint32_t PixelCenterPositionInputs = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY);
bool rasterizesSampleZeroOnly(const Registers& cx, const State& state) {
    if (state.samples <= 1 || state.alphaToCoverage || state.perSampleShading || (read(cx, 0x292) & 1u) == 0) return false;
    const auto inputs = read(cx, 0x1b3) & read(cx, 0x1b4);
    const bool sampleIndexMovesPosition = (inputs & ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary)) != 0 && (inputs & PixelCenterPositionInputs) != 0;
    return (inputs & SampleDependentInputs) == 0 && !sampleIndexMovesPosition;
}

The early return keeps the 0x1b3/0x1b4 reads behind the same conditions as before. Reading them unconditionally makes agc_driver_graphics fail with "missing register in context bank at DWORD 0x1b3" on one-sample color draws.

I tested it in multisampleTests (agc_driver_covered_centroid_state):

  • Your input loop now refuses PerspectiveCentroid, LinearCentroid and SampleCoverage.
  • A new block sets the registers above on queue and requires: one sample, a one-sample 1700x1912 depth target, no color target, sample 0 at (0.25, 0.5) in the first row and (0.75, 0.5) in the second, and an empty DrawRejection.
  • The same draw must still throw with a centroid, coverage or POS_Y input added, and with PS_ITER_SAMPLE.
  • PerspectiveSample, LinearSample and Ancillary each decode to one sample on your color case.
  • Ancillary with POS_X or POS_Y throws.

Without the change, the new block fails with exactly the game's message. With it, all eight tests backed by agc_driver_graphics_tests pass. I can paste the block if useful.

Title run with this on top of your PR (Windows Server 2025, RTX 3060, driver 591.86, on a local stack of main 8cd937e29 + #6126 + #5884 + two local recompiler fixes): Resident Evil 4 no longer stops on this draw. It ran for the whole 1,485 s run window without a fatal error, and its frame dumps show the brightness calibration screen with both reference images. Before this, every dump of the title was black.

Debt wording I used in place of "no sample, centroid, ancillary or coverage pixel inputs": "no centroid or coverage pixel inputs ... Such a draw keeps PERSP_SAMPLE, LINEAR_SAMPLE and ANCILLARY inputs: a pixel shaded once is taken to get sample 0's barycentrics and sample index 0, which is inferred, not measured. ... ANCILLARY with POS_X_FLOAT or POS_Y_FLOAT throws, since reading the sample index makes the host shade per sample, which moves FragCoord from the pixel center to sample 0's location."

A side point, which I recorded in the debt entry but did not change: by the same Vulkan rule, the PERSP_CENTER and LINEAR_CENTER inputs this path already accepts (for example SPI_PS_INPUT_ENA 2 in the PPSA27836 test) are also interpolated at sample 0's location on the host. The console uses the pixel center for them, so for these patterns they are a quarter pixel off horizontally.

None of the other open sample-count PRs cover this draw. #5882, #6014 and #6196 handle MSAA_ENABLE clear, and #5915 handles a one-sample scan into a multisampled depth target; this draw has MSAA_ENABLE set, a 2x raster and a one-sample depth target.


AI-assisted: yes

@milescoviello

Copy link
Copy Markdown
Contributor

A different one-sample-target case from a GL homebrew. It's data only, in case it matters for this rule.

OliSe Player (PPSA01153, catalog 02.001.000) draws through a radeonsi-based GL driver (ps5-gallium in its log). With its MilkDrop preset "Rovastar - Dark Ritual (Star Of Destiny Mix)" it issues this draw (a throwaway print at the sample-count check):

Register Value
PA_SC_AA_CONFIG 0x0020c002 (4 samples, 4 exposed)
Color target one, 1 sample, VK_FORMAT_R8G8B8A8_UNORM; no depth target
PA_SC_MODE_CNTL_0 / _1 0x23 (MSAA_ENABLE) / 0x0
DB_EQAA 0x02130000 (OVERRASTERIZATION_AMOUNT 2, HIGH_QUALITY_INTERSECTIONS, INCOHERENT_EQAA_READS, STATIC_ANCHOR_ASSOCIATIONS)
SPI_PS_INPUT_ENA / ADDR 0x4002 / 0x4002 (PERSP_CENTER, SAMPLE_COVERAGE)

This matches radeonsi's polygon and line smoothing. With a one-sample framebuffer, si_emit_msaa_config rasterizes with SI_NUM_SMOOTH_AA_SAMPLES = 4 coverage samples and sets OVERRASTERIZATION_AMOUNT(log2 4). si_shader.c then enables SAMPLE_COVERAGE_ENA for nir_lower_poly_line_smooth, which scales alpha by the number of covered samples. So for this draw the pixel is shaded when any of the four samples is covered, and the shader needs the four-bit mask. Rasterizing sample 0 alone would drop edge pixels and give a one-bit mask, so refusing a sample-coverage input here, as this PR does, looks right to me.

On main (0029e62) this draw stops earlier, at the pixel input decode: unsupported SPI_PS_INPUT_ENA/ADDR bit combination (ena 0x4002 addr 0x4002), 2 of 2 runs with only that preset installed. I have a local branch that loads SAMPLE_COVERAGE from gl_SampleMaskIn. With it, the same draw stops at a color target's sample count differs from the rasterizer's.

Not checked: whether VK_NV_framebuffer_mixed_samples with VK_NV_coverage_reduction_mode's merge mode (both reported on this RTX 4070 Ti) reproduces the overrasterized case. That would be a separate change, and I'm not asking for it here.

AI-assisted: yes

@milescoviello

Copy link
Copy Markdown
Contributor

Follow-up to my note above: #6530 now handles the overrasterized draw. It takes only draws whose DB_EQAA.OVERRASTERIZATION_AMOUNT matches the raster samples (radeonsi smoothing) and renders them through VK_NV_framebuffer_mixed_samples with merged coverage. Your sample-0 rule is untouched, and the two sets of draws don't overlap. Whichever of us lands second has a small rebase at the sample-count check.

AI-assisted: yes

@EagleBearerYS

EagleBearerYS commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Resident Evil Village (PPSA01556) data point for the ANCILLARY + POS_X/Y rule in 7ad8cb8.

The setup: #5907 with fdfawef324's rule (as pushed in 7ad8cb8), on main 6ea62f9 with #6366 and #5703. RTX 3070 Laptop GPU.

What happens: Resident Evil Village gets past the 0x2001 pass. It then stops at its next 2x depth-only pass, whose pixel program reads PERSP_SAMPLE, POS_X/Y_FLOAT and ANCILLARY (SPI_PS_INPUT_ENA 0x2301, PS_ITER_SAMPLE clear):

[gpu] driver failure: AGC graphics: the depth target's sample count differs from the rasterizer's

That's the rule refusing ANCILLARY with POS_X/Y.

Where the rule comes from: what it guards against, the ancillary's sample field forcing sample shading and moving FragCoord, comes from the recompiler giving that field SampleId in every pixel shader. #6470 changes that. A pixel shader the guest doesn't iterate reads sample number 0, runs once per pixel, and keeps POS_X/Y where SPI_BARYC_CNTL puts them (the pixel centre here).

The combination: with #6470 merged into 7ad8cb8, and the ANCILLARY + POS rule and its two refusal checks dropped:

  • Resident Evil Village's 0x2301 state decodes to one sample, once per pixel, at each pixel's sample 0 location (04 04 0c 0c);
  • ctest -R '^(agc|shader|recompiler)' passes 608/608 on that merge (lavapipe).

Either merge order works. A Resident Evil Village boot with the combination follows.

@fdfawef324

Copy link
Copy Markdown
Contributor

Here is the RE4 (PPSA07411) re-run on 7ad8cb8 I promised. The machine and stack are the same as in my runs above, with 2ce4e57 and my proposed change replaced by your two commits (409f8b6 and 7ad8cb8, cherry-picked unchanged). The stack still has the local diagnostic that adds the decoded sample state to any sample-count refusal message.

  • Cold shader cache: stopped 886 s in on the abort that fix(agc): promote a held import window to its whole allocation #5870 fixes, which is unrelated to this PR (an address-based build copies 452 MiB of 1 registered ranges ...). Every frame dump from that run is black, so it may not have reached the 2x draws.
  • The same build plus fix(agc): promote a held import window to its whole allocation #5870, using the cache the first run filled: ran until the harness's timed dump at 2,986 s. Frame dumps show the CAPCOM logo, the brightness calibration screen and the "About the Settings for Sending Play Data" screen, so it still reaches the brightness screen and gets as far as the 2,096 s run did.

Apart from that abort, neither run logged a sample-count refusal or any other driver failure. On RE4, 7ad8cb8 behaves the same as the change I proposed.

AI-assisted: yes

@simonbronk

Copy link
Copy Markdown
Contributor Author

@fdfawef324 thanks for the two re-runs. Good to know 7ad8cb8 still gets RE4 to the brightness and play-data screens, and that the cold-cache stop is #5870's.

@EagleBearerYS agreed. The ANCILLARY + POS_X/Y refusal is there only because the recompiler gives the ancillary sample field SampleId in every pixel shader, and #6470 removes that. Whichever lands second drops the rule and its two refusal checks: if #6470 merges first I'll remove them here, otherwise it belongs in #6470's rebase. Onimusha's draws on this path don't read ANCILLARY, so it isn't affected either way. I'll wait for your Village boot with the combination before changing anything.

@EagleBearerYS

Copy link
Copy Markdown
Contributor

@simonbronk here is the combined boot. Resident Evil Village (PPSA01556), Windows 11, RTX 3070 Laptop GPU (driver 617.42):

It reaches the lit title menu with no driver failure.

  • The 2x depth-only pass that the ANCILLARY + POS_X/Y rule refused (SPI_PS_INPUT_ENA 0x2301) now draws, as does the 0x2001 pass before it.
  • No sample-count refusal is logged.
  • The menu looks as it does on our working tree with our local experiments in place of this PR. In a capture at the same point, it's the same lit scene, with an average brightness of 48.2 against 49.9.

The tree (local, not pushed):

One note from after the run: when the window closed, the presenter thread faulted inside NVIDIA's driver (nvoglv64.dll) during teardown. It's the first such fault in six clean closes today, and it came after the last frame. I'll report it separately if it happens again.

simonbronk and others added 3 commits October 16, 2026 08:58
…ample 0's locations

A draw whose rasterizer has more samples than its color or depth targets
threw "a color target's sample count differs from the rasterizer's" or
"the depth target's sample count differs from the rasterizer's". When
every target has one sample, MSAA_ENABLE is set, and there is no
alpha-to-coverage, no PS_ITER_SAMPLE and no sample-dependent pixel input,
only sample 0's coverage reaches the targets, so draw it with one sample
at sample 0's location in each pixel of the grid. One-sample depth images
are created sample-location compatible when the device allows it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mple-zero path

A multisampled rasterizer drawing into one-sample targets keeps
PERSP_SAMPLE, LINEAR_SAMPLE and ANCILLARY inputs, and with PS_ITER_SAMPLE
also centroid inputs, since only sample 0's coverage reaches the targets.
Coverage inputs, and the sample index with POS_X_FLOAT or POS_Y_FLOAT,
still throw. The input rule for the non-iterated case follows
fdfawef324's proposal on boykopovar#5907 (PPSA07411).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…get under a 2x scan without MSAA_ENABLE

boykopovar#5882 lets a multisampled scan without MSAA_ENABLE through the earlier
check, so the draw is now refused where its one-sample color target meets
the rasterizer's sample count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@simonbronk
simonbronk force-pushed the feat/mixed-sample-truncate branch from 7ad8cb8 to 2c1b825 Compare October 16, 2026 07:04
@simonbronk

Copy link
Copy Markdown
Contributor Author

Rebased onto main 5d9dcc4 (no textual conflict) and added 2c1b825.

#5882 (merged last night) lets a multisampled scan without MSAA_ENABLE through the earlier DecodeState check when there is no depth target. One of this PR's cases, a one-sample color target under a 2x scan with MSAA_ENABLE clear (singleScan in multisampleTests), is therefore no longer refused with "without MSAA_ENABLE" but at the color sample-count check. The draw is still refused, as before: rasterizesSampleZeroOnly requires MSAA_ENABLE. The commit only changes the expected message. Without it, agc_driver_graphics and agc_driver_covered_centroid_state fail on main + this PR.

ctest -R "agc|shader|video_out" -j 8 on Windows 11 26200: RTX 5070 (617.42) all pass; AMD gfx1036 iGPU fails only #5271's three tests. On both, agc_driver_gpu_indirect_arguments_misaligned_writer_ahead does not build with WinLibs GCC (#6452).

@EagleBearerYS thanks for the Village boot with #6470 and the rule dropped. Reaching the lit title menu with no refusal is what I wanted to see before touching it. I'll drop the ANCILLARY + POS_X/Y rule and its two checks here as soon as #6470 is merged.

@adriadam10

Copy link
Copy Markdown
Collaborator

Re-checked against current main: the diff is unchanged from the version I reviewed (build and libSceAgcDriver ctest passed on the PR merged onto main, agc_driver_graphics fails with the source changes reverted), and the title runs by @fdfawef324 (PPSA07411) and @EagleBearerYS (PPSA01556) are consistent with it. Current main does not build on Linux independently of this PR (PipelineJournal.cpp size asserts); merging once the full build is green.

@adriadam10

Copy link
Copy Markdown
Collaborator

Re-checked against current main: the diff is unchanged from the version I reviewed (build and libSceAgcDriver ctest passed on the PR merged onto main, agc_driver_graphics fails with the source changes reverted), and the title runs by @fdfawef324 (PPSA07411) and @EagleBearerYS (PPSA01556) are consistent with it. Current main does not build on Linux independently of this PR (PipelineJournal.cpp size asserts, fix in #6784); merging once the full build is green.

@adriadam10

Copy link
Copy Markdown
Collaborator

Re-checked against current main: build and libSceAgcDriver ctest pass on the PR merged onto it (only agc_driver_tessellation segfaults, as on main), and agc_driver_graphics fails with the source changes reverted. The title runs by @fdfawef324 (PPSA07411) and @EagleBearerYS (PPSA01556) are consistent with it. Merging once the full build is green.

@adriadam10
adriadam10 merged commit 24528f4 into boykopovar:main Oct 16, 2026
8 checks passed
SandstromPL added a commit to SandstromPL/AnyPS5 that referenced this pull request Oct 16, 2026
…povar#5907's tests

boykopovar#5907 added a case to multisampleTests that clears MSAA_ENABLE on its
2x rasterizer over a one-sample color target, and expected the color
sample-count refusal. With the previous commit, that draw rasterizes
the one-sample target as one sample with the standard location, so the
case now requires that instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants