Skip to content

AGC: an indirect indexed draw binds its whole INDEX_BUFFER_SIZE range, so Guardians of the Galaxy (PPSA01748) stops when the title declares 0x10000000 indices over a 54 MiB buffer ("guest memory is not readable") #6526

Closed

Description

@Tamalero

Guardians of the Galaxy stops during its first level load, with music already playing. It issues a DRAW_INDEX_INDIRECT_MULTI after setting INDEX_BUFFER_SIZE to 0x10000000 (16-bit indices, so 512 MiB). For indirect indexed draws, resolveIndirectDraw binds that whole declared range and CheckRange requires all of it to be readable. Only the first ~54 MiB behind the index base is mapped, so the draw fails:

[videoout] presentation failed: AGC driver: guest memory is not readable at 0x386590000 (range 0x383240000+0x20000000)
terminate called after throwing an instance of 'std::runtime_error'
  what():  AGC driver: guest memory is not readable at 0x386590000 (range 0x383240000+0x20000000)

Title: Marvel's Guardians of the Galaxy (PPSA01748 v01.006.000)
Where it stops: the level-1 load, a few minutes after START GAME, on main 03c4cb5e7 plus #6232. #6232 is needed to get past the #6191 stop, which comes earlier on plain main. Linux, RTX 4090. 5 of 5 runs, at the same range each time; the first unreadable page is 0x386590000 (once 0x3865a0000).

What the draw is

A local print before the CheckRange in resolveIndirectDraw (not pushed):

indirect indexed draw: opcode 0x38 INDEX_BUFFER_SIZE 0x10000000 indexType 0 indexBase 0x383240000 records 144 stride 20 args 0x2d18b91d4
packet 10 words: c0083800 00000004 00000090 00000280 40000280 00000090 ...

A native stack from the throw: Driver::draw → Pm4::ResolveDraw (the indirect branch, inlined) → GuestMemory::CheckRange → require.

So the 512 MiB is not a size the title reads. It's the declared upper bound, which the CP uses to clamp firstIndex + indexCount per record. The 144 records, 20 bytes each, are the usual (count, instances, first index, base vertex, first instance) and select ranges inside it. On the console, index fetches past those ranges never happen, so the unmapped tail doesn't matter. Here the driver needs the tail to be readable because it binds the declared range as the index buffer (Pm4.cpp: "the declared range is the hardware's view of the index buffer, so that is what gets bound").

Possible directions (not tried)

  • Bind the readable prefix of the declared range, from the index base to the first unreadable page. Records that reach past it would read past the end of what is mapped on the console too.
  • Or bind the union of the records' ranges, clamped to INDEX_BUFFER_SIZE, when the records are CPU-visible at packet time. That doesn't work if the GPU writes the records, which is usual for indirect draws, so the first option looks safer.

I haven't picked one or written a fix. I can run either on the title the same day.

Details

Tested and written by Claude (AI assistant) on a contributor's machine.

Activity

  1. MarcZX10 commented on Oct 15, 2026

    @MarcZX10
    Contributor

    I traced the suggested readable-prefix approach through current main (e16b2bc). Capping draw.indexCount in Pm4::resolveIndirectDraw would avoid the initial full-range check, but Draw.cpp then uses that count to validate and prepare the index input, while the GPU multi-draw path submits the guest indirect records without applying that cap to each record. Since the records can be GPU-written, a generic fix needs to preserve the valid prefix without letting a record fetch beyond it or copying/scanning the entire declared range. I have not found a safe implementation yet, so I have not opened a PR. The title repro remains a useful target for validating a bounded design.

  2. MarcZX10 commented on Oct 16, 2026

    @MarcZX10
    Contributor

    Update to my earlier note that I had not found a safe generic design: PR #6606 now implements a focused fallback for indexed indirect draws whose declared index range is not fully readable. It preserves the original declared limit, reads the indirect records through the checked CPU path, and validates/prepares only the index ranges each record actually selects; fully readable buffers keep the existing GPU path. The PR reports focused regression coverage for holes, GPU-written records/counts, limit clamping, and an active range that is genuinely unreadable, with its Windows AMD and Linux lavapipe test results recorded there.

    I have not run the title myself, and #6606 explicitly leaves the next Guardians of the Galaxy startup milestone unverified. I found no separate change to prepare for this issue, so I will not duplicate that work.

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