On Windows every direct host import leaves the write watch, and a storage
image over it compares its whole surface with its guest-byte copy at every
collect epoch. #4432 measured 7-8.6 s of every 10 s in those compares on a
GTA V scene, with 98% of the compared surfaces unchanged.
Tracker blocks that leave the watch now keep their write-watch marks as a
hint. Leaving the watch gives a block a fresh hint stamp, and
UntrackedHint walks a range of such blocks, at most once per collect epoch
and thread, stamping the blocks whose pages a CPU store marked; it
returns the newest stamp of the range. compareUntracked puts that stamp in
its memo key in place of the collect epoch, so an image is compared again
after a mark in its range, a driver store, a mapping change or a recorded
GPU write. The byte compare still decides whether anything changed: a mark
the driver makes without a CPU store costs one compare and drops nothing.
Nothing else reads the hint. Collects, Watched, UnchangedSince,
ChangedBlocks and NewestStamp still treat those blocks as untracked, and
Linux is unchanged.
agc_driver_dcc_keys_first_write refreshes an imported storage image over
50 collect epochs with no store: it compared the image 50 times before and
0 times now. A CPU store is still uploaded on the next refresh, and a store
of the bytes already there costs one compare and keeps the pending
results. agc_write_tracking checks the hint itself.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A queued store run records each group's plain stores first and its
occlusion counter dumps after them. A store started a new group only
when it overlapped an earlier dump, so a label the title writes after
its dumps was recorded before them. A title that polls that label then
reads counters from before the dumps land. Any store after a dump now
starts a new group.
Epic Mickey: Rebrushed (PPSA15657) read stale or zero occlusion results
every other frame and left visible geometry undrawn on alternate frames.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reused program hands out its compiled information through
IrProgram::CompiledInfo() const&, while compileVariant still moves it out
with TakeCompiledInfo(). The layout-pair fixtures are pixel shaders whose
user-data base, scratch size and parameter export mask are all zero, so
the comparison with isolated compiles could not tell whether the copy
kept those fields: a copy that zeroed them still passed.
Translate a compute program with a user-data base of 2 and 64 scratch
dwords and require the copied and the moved information to agree on
every field. Zeroing those fields in the copy now fails
agc_shader_layout_pair and agc_shader_layout_pair_off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The layout-pair check compared a subset of the prepared artifact: the
fragment parameters only by count, and not the barycentric emulation,
the runtime ABI version or the vertex interface fields. Its pixel fixture
also had no interpolated inputs, so the fragment parameter list was empty
on both sides.
Compare every artifact field except the variant id, the fragment
parameters element by element, and add a fifth configuration: registration
modules at the fragment slot offset with two interpolated inputs, which
must declare two fragment parameters in both layouts.
With this, flipping the barycentric emulation flag, a fragment parameter's
flat flag or the runtime ABI version on the reused path fails
agc_shader_layout_pair, where the previous check passed all three.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
docs/dev/CONVENTIONS.md asks for C#-style template parameter names
(TKey, TValue). recordEmissionFailure, added with the split of variant
compilation, named its callable parameter Step; rename it to TStep and
write the template header the way the neighbouring recompiler sources do.
No behaviour change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Add a --layout-pair mode to agc_shader_memory_tests, registered as
agc_shader_layout_pair and agc_shader_layout_pair_off (the latter with
APS5_NO_PERF_LAYOUT_PAIR=1), next to the frontend-pair tests.
A pixel shader with a buffer load is prepared with a 128-byte push range
and with none through one context, in both orders: without registration
modules, with them at push offset 0 and at the fragment slot offset 128
that devices with graphics pipeline libraries use, and with a registration
word limit that forces the fallback to plain modules. Each pair must build
one program with reuse and two without, and each layout must match a
compile of the same code made on its own source without a context:
SPIR-V, binding layout, push range and push-data use, artifact fields and
compiled shader information. The materialized result must also match an
uncached Recompile.
The context must not reuse a program translated at another code address
or one built for another source at the same address, and it must release
the kept program when it builds another one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PrepareRegistered prepares every registered pixel shader twice: with the
plan's layout, then with pushConstantSizeBytes = 0. The layout is not part
of the source key, so both requests share one SourceEntry, but the first
variant consumes the entry's front-end program. On a cold cache the second
layout therefore ran PrepareResourceProgram again (translation, SSA, the
fold and eliminator passes, SrtWalker, ResourceTracker) and the interface
passes after it, although none of them reads the layout. Only decode, CFG
and structurization were shared through the context (#2526).
A ShaderPreparationContext now keeps the program it last emitted from,
after the interface step, together with its source entry. Another layout of
the same source at the same code address resets the binding allocation and
emits from that program, as the registration word-limit fallback already
does within one variant, and copies the compiled information out instead of
moving it. A different source or code address builds a program as before.
Building another program through the context releases the kept one first,
and the program dies with the context, so no program outlives the
registration that built it (the rule of #471). ResourcePrograms() counts
the programs built through a context.
Disk-cache loads and stores, the emission-failure memo and the variant
order are unchanged. APS5_NO_PERF_LAYOUT_PAIR=1 restores the previous path;
it is a neutral switch, so it does not change disk-cache keys.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
compileVariant ran the layout-independent interface passes
(ApplyStaticInterface, identity and dead-code removal, ShaderInfoCollector)
and the layout-dependent emission (binding allocation, SPIR-V emission, the
registration word-limit fallback, validation) as one step on a program it
consumed. Split it into prepareInterface and emitVariant, which take the
program by reference, and keep compileVariant as their composition with the
same emission-failure recording, so a later change can emit another layout
from a program whose interface is already prepared.
No behaviour change: compileVariant still runs both steps once per variant
and still moves the compiled information out of the program.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Build SDL's Wayland backend next to X11 and ask SDL for "wayland,x11"
on Linux, so the window runs natively under a Wayland compositor instead
of through XWayland. SDL loads libwayland, xkbcommon and libdecor at run
time, so systems without them, or sessions without WAYLAND_DISPLAY, keep
using X11. SDL_VIDEODRIVER still overrides the choice.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
shadPS4 looks the request up through its library and user contexts and
returns the matching NOT_FOUND error. This library keeps no request
table: CreateRequest hands out counter values and the other request calls
accept any id, so the checks wait for request tracking across all of
them. The entry moves to Silent stubs, as main's conventions check now
requires.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The timeout setter takes (request id, timeout) and succeeds without
checking the request, like the other request setters: no request reaches
a service, so no timeout can expire. PPSA14632 imports it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A shared range larger than one chunk now takes the sparse path only when
the device binds sparse memory, udmabuf is open, the range's backing
offset is on the sparse block size, the sparse buffer accepts every
memory type a chunk import can have and the backing file covers the
last chunk. All of that is known before a chunk is imported. Once the
path is chosen, a failed chunk import, fence creation, bind, fence wait or device
address throws instead of going on to the whole-range udmabuf and copies.
agc_host_import_sparse checks the import against a mocked device with
the real udmabuf: a range above size_limit_mb becomes one sparse buffer
bound to one chunk import per 64 MiB, an unaligned backing offset
imports no chunk, and failures of UDMABUF_CREATE and vkQueueBindSparse
after the decision throw.
Charge each recorded protection ranges 2 MiB granules to the gpu
pool when the range has GPU-access protection bits and to the cpu
pool otherwise, so the available counts reflect committed usage
instead of staying at the 2048-entry budget. Follows KytyPS5s
counting model.
A draw made outside the GPU lock (agc_driver_recorded_kept_bytes) imports a target's memory and leaves the watch there; the hook threw instead of saving. It now takes the lock, as FlushPending does.
Use program headers when the section table starts at or beyond EOF. Keep rejecting tables that start inside the file but are truncated.
Cover imports, exports, full module audits, and extended section counts.
radeonsi exports MRT0 as 32_AR when alpha-to-coverage is on and no color
buffer is bound, so a depth-only draw can depend on that alpha. The
decoder now accepts 32_AR and an absent MRT0 target, and reads the MRT0
export format from SPI_SHADER_COL_FORMAT, which it masked to 0 for
draws without a color target.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pixel export to an MRT whose SPI_SHADER_COL_FORMAT is 32_AR read its
alpha from export channel 3 (EN mask 0x9), the layout before GFX10. On
GFX10, which the PS5's gfx1013 is, the alpha is channel 1: Mesa's
emit_ps_color_export (ac_nir_lower_ps_late.c, index
gfx_level >= GFX10 ? 1 : 3), ACO and radeonsi in Mesa 22.0
(enabled_channels 0x3 with the alpha in channel 1) and LLPC's
FragmentColorExport.cpp (EXP_FORMAT_32_AR, exports[1]) all emit it
there. A console shader's `exp mrt0 v0, v1, off, off` therefore lost
its alpha: it stayed at the 1.0 default, so an alpha-only COLOR_32
ALT_REV target stored 1.0 and an R32 target blended with SRC_ALPHA
blended as if the alpha were 1. With channel 3 enabled, channel 3 was
taken instead.
ExportVector now reads channels 0 and 1 for 32_AR and puts channel 1
in the alpha component; channels 2 and 3 are ignored. The layout comes
from the compilers, not from a hardware measurement, and is recorded in
docs/dev/debt/shader.md.
agc_driver_pixel32_ar_export draws 32_AR exports with EN 0x3, 0xb and
0xf, as wave32 and wave64, into a COLOR_32 FLOAT ALT_REV target and a
COLOR_32 FLOAT target blended with SRC_ALPHA and ONE_MINUS_SRC_ALPHA,
and compares every stored value bit for bit. Without the fix every row
fails: the stored value uses an alpha of 1.0 or of channel 3.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On Linux a shared-backed guest range is imported through one udmabuf
covering the whole range, and udmabuf refuses buffers above its
size_limit_mb (64 MiB by default). Larger ranges fell back to copies; an
address-based dispatch then synced its whole mirror, storing back every
pending storage image inside it each time.
Such a range is now one sparse buffer bound to the existing 64 MiB chunk
imports. The bind runs on a sparse binding queue of its own when the
device has one, so imports made at map time (without the GPU mutex) can
use it; otherwise only under the GPU mutex on the main queue.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On Windows, singly mapped direct memory is private since #5596, so its host
import is a direct import that leaves the write watch instead of a shared
alias that stays watched. A storage image whose results were pending when
its memory was imported had no saved guest bytes, and storing them threw.
GuestMemory now calls a hook before Unwatch and before an ImportWatched
that does not stay watched. Every image with results pending over the
range saves the guest bytes of its pending units that have none, then
collects and records the pieces the keep rule leaves to the CPU for the
writes the watch saw. The next store and Refresh leave those pieces to the
CPU and compare the rest with the saved bytes. A map-time import made
without the GPU lock is deferred to the locked retry when pending results
overlap the range.