Skip to content

[bug] agc_driver_dcc_keys_first_write fails on main on a real NVIDIA GPU #2188

Closed

Description

@Dextroid17

agc_driver_dcc_keys_first_write fails on main on a real GPU, and I could not find which change is responsible — reporting with the reproduction and with two suspects I was able to rule out.

Reproduction

On a clean upstream/main (d70b899), Linux, BUILD_TESTING=ON:

cmake --build build --target agc_driver_dcc_keys_first_write_tests --parallel 16
ctest --test-dir build -R ^agc_driver_dcc_keys_first_write$ --output-on-failure
1/1 Test #111: agc_driver_dcc_keys_first_write ...***Failed    0.44 sec
AGC graphics: an unchanged pending image was uploaded again
0% tests passed, 1 tests failed out of 1

naming the assertion an unchanged pending image was uploaded again. It fails with and without APS5_WRITE_WATCH_IMPORTS=watch, so the write-watch workaround from #1289 is not masking or causing it.

Environment

  • Linux 7.2.9-1-cachyos, GCC 16.2.1, Release, BUILD_TESTING=ON
  • NVIDIA GeForce RTX 3080 Ti, driver 615.71.09 (proprietary)
  • The test's own log lines just before the failure:
    [gpu] storage image 0x…: guest memory changed while GPU results were pending; keeping the CPU's blocks

Two suspects ruled out, by experiment

The test's introducing commit is b8d30603 ("fix(agc): store uncompressed DCC keys at the first write of a fast-cleared image", 2026-10-06), and the three most recent changes to that machinery are mine, so I tested them instead of leaving them as a suspicion:

So the cause is somewhere between b8d30603 and main, and it is not the recently merged Dextroid17 PRs.

Why it may be going unnoticed

The linux CI job installs lavapipe and runs the execution tests on it. If this failure needs a real NVIDIA GPU — which the "guest memory changed while GPU results were pending" line suggests, since import watching behaves differently on that driver — then a green CI run would not contradict this report.

What I did not do

A bisect. Reverting the two files wholesale to their pre-merge state does not compile against current main (the interfaces moved), and I stopped there rather than rebuilding an older tree and guessing. If it would help, I can run one from any commit you point at.

Written with AI assistance.

Activity

  1. Dextroid17 commented on Oct 9, 2026

    @Dextroid17
    ContributorAuthor

    Confirmed device-specific, as the body suspected. On lavapipe — the same software device the linux CI job installs, Mesa 26.2.4 / LLVM 23.1.1 / 256 bits — the test passes; on the RTX 3080 Ti it fails with the same message. So the lavapipe job cannot see this.

    To re-check on a machine with both: install or extract vulkan-swrast, then

    VK_ICD_FILENAMES=<…>/lvp_icd.json \
    LD_LIBRARY_PATH=<…>/usr/lib \
    ANYPS5_REQUIRE_VULKAN=1 GALLIUM_OVERRIDE_CPU_CAPS=avx \
    ctest --test-dir build -R ^agc_driver_dcc_keys_first_write$ --output-on-failure
    

    That reproduces the CI device on a workstation without touching the NVIDIA install.

    Written with AI assistance.

  2. Dextroid17 commented on Oct 9, 2026

    @Dextroid17
    ContributorAuthor

    Closing this myself, because by this project's own ground rules it should not have been filed: the failure reproduces only on the NVIDIA proprietary driver, that path is not expected to work on Linux yet, and the same test passes on lavapipe, which is the device CI actually runs. My framing above — a CI blind spot — was wrong; this is an unsupported device, not a defect.

    Leaving it visible rather than deleting it, since the lavapipe half is the useful part: on the CI device the test passes, so nothing is hidden from CI.

    Written with AI assistance.

  3. SleglJan commented on Oct 9, 2026

    @SleglJan
    Contributor

    The bisect the report left open. git bisect run between f145139 (the base of #1979, assumed good; the assertion does not exist there) and d70b899, 95 commits, 9 steps, 7 of them built and tested once each on an RTX 3080 (615.71.09, Linux, CachyOS kernel 7.2.9, GCC 16.2.1, Release) and 2 skipped because they do not build (6cc0503, 0ab2a04): the first bad commit is 0e1d25a ("fix(agc): avoid redundant transfers for untracked imported textures", merged through #2096), the commit that added the assertion an unchanged pending image was uploaded again to DccKeysFirstWrite.cpp. Tested directly with the same build and GPU, its parent a8b032b passes 3 of 3 and 0e1d25a fails. At d70b899 the test fails 3 of 3 runs on the RTX 3080 and passes 3 of 3 on RADV (Mesa 26.2.4, Ryzen 7950X3D iGPU) on the same machine. On Windows, popcoder565's run on #2096 (RTX 3060 Laptop, 616.92, head 3ed0d03) has agc_driver_dcc_keys_first_write passing 30 of 30, and #2155 attributes the storageRefreshTests failure of agc_driver_recorder_tests on that machine to the same commit.

    AI-assisted: yes (Claude Code).

  4. fma-rc commented on Oct 10, 2026

    @fma-rc
    Contributor

    This reproduces on main 0c0dc1e with an RTX 5080 (615.71.09, Linux 7.2.9, GCC 16.2.1, Release), and the trigger is not the NVIDIA driver as such: it is host imports staying write-watched.

    Device Setting main main + patch below
    RTX 5080 default fails, 3 of 3 passes, 3 of 3
    RTX 5080 APS5_WRITE_WATCH_IMPORTS=watch fails, 3 of 3 passes, 3 of 3
    RTX 5080 APS5_WRITE_WATCH_IMPORTS=unwatch passes, 3 of 3 passes, 3 of 3
    RADV (Radeon 610M, Mesa 26.1.4) default passes, 3 of 3 passes, 3 of 3
    RADV APS5_WRITE_WATCH_IMPORTS=watch fails, 3 of 3, same message fails, 3 of 3, at "the first write: the write-back stored 0 written and 0 cleared bytes"

    The probe reports 0 of 4 scratch pages written after a GPU read ... imported ranges stay watched on NVIDIA and 4 of 4 ... imported ranges are compared on RADV.

    In the code: GuestMemory::ImportWatched stamps the blocks with StampKind::ImportWindow, which ChangedBlocks reports as BlockMaybeWritten. StorageTexture::Refresh treats any non-zero stamp as changed (Texture.cpp L1289), a changed pending unit blocks the equalsOriginal() rescue (L1326), and it is not droppable because it is not BlockWritten (L1388). So the unit is written back and the image uploaded again. The unwatched path instead compares against original (L1271).

    Candidate (7 lines, in scanBlocks): for a tracked block unit stamped BlockMaybeWritten, compare it against original with compareUntracked and mark it unchanged when equal.

    With it, on the RTX 5080 the full ctest goes from 564 of 565 to 565 of 565; on RADV ctest -R ^agc_ is 266 of 266 before and after. tools/check_conventions.py passes.

    Retested on main ee50132: same results in the table; full ctest on the RTX 5080 583 of 584 → 584 of 584, RADV ctest -R ^agc_ 269 of 269 before and after.

    Not established: any effect on a title; the non-block-unit branch (L1298–1305); Windows. The RADV forced-watch row still fails with the patch, at a later assertion. compareUntracked calls FlushGpuWrites, which this adds to the tracked path.

    Patch against main 0c0dc1e
    diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
    index 26911542..4b626008 100644
    --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
    +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
    @@ -1270,6 +1270,13 @@ bool StorageTexture::Refresh() {
                 tracked = GuestMemory::ChangedBlocks(descriptor.baseAddress, static_cast<std::size_t>(guestBytes), generations, stampedBlocks, cpuBlocks);
                 compared = !tracked && compareUntracked(descriptor.baseAddress, static_cast<std::size_t>(guestBytes), stampedBlocks, true);
                 if (compared) cpuBlocks = stampedBlocks;
    +            for (std::uint32_t unit = 0; tracked && blockUnits && unit < trackedLayers; ++unit) {
    +                if (stampedBlocks[unit] != GuestMemory::BlockMaybeWritten) continue;
    +                std::array<std::uint8_t, 1> block{};
    +                if (!compareUntracked(layerBegin(unit), static_cast<std::size_t>(layerBytes(unit)), block) || block[0] != GuestMemory::BlockUnchanged) continue;
    +                stampedBlocks[unit] = GuestMemory::BlockUnchanged;
    +                cpuBlocks[unit] = 0;
    +            }
             };
             const auto keysStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
             keys = ProvedKeys();

    AI-assisted: yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions