Commit Graph
63 Commits
Author SHA1 Message Date
Yunus Shaikh 4cb0512329 Merge main into perf/shader-direct-cpu-evaluable-images
Main's #7101 reads a key buffer's words in one call where this branch
names the reason a narrowing is skipped. Keep main's bulk read and
count both of its unmapped-page skips as unmapped.
2026-10-17 15:58:42 -07:00
Dean b4f05ab328 Merge pull request #6631 from AlxcBTW/perf/dcc-key-presync
perf(agc): pre-sync the DCC key writers of a dispatch's images before the GPU mutex
2026-10-18 11:18:56 +13:00
Dean 0381ac76d1 Merge pull request #6632 from AlxcBTW/perf/handoff-spin
perf(agc): spin before blocking on the GPU mutex and the draw committer hand-offs
2026-10-18 11:18:40 +13:00
ThatsLeo e14a4e5138 fix(shader): stop first-lane reads that reach themselves from recursing
MayEvaluate and the evaluator track the first-lane reads they are inside, so a read reached again through a loop-carried phi or a constant slot is not evaluable instead of recursing.
2026-10-17 18:11:28 +02:00
ThatsLeo 745cdb5bf7 perf(shader): judge first-lane and constant reads by what they read
MayEvaluate took ReadFirstLane and ReadConst as evaluable without looking at
their sources, so plans kept conditions behind per-lane values.
2026-10-17 18:03:33 +02:00
Adrià daf8913128 Merge pull request #5905 from hugodearaujo/fix/agc-prewarm-mapped-dma-buf
fix(agc): settle dma-buf imports on Linux on the settle queue
2026-10-16 21:05:30 +02:00
AlxcBTW 23a804e393 perf(agc): count the processors the process may run on for the hand-off spin guard
The automatic window needed 8 logical processors, which a 4-core CPU with SMT passes while the waited-for thread may run on a sibling thread, and ignored the process affinity. It now needs more than 8 processors in the affinity mask (CpuTopology), falling back to hardware_concurrency.
2026-10-16 19:52:11 +02:00
Adrià FranchandClaude 37db6a80b7 fix(agc): bound the async log's exit flush
On Windows the atexit handlers of a DLL run after the process has ended
its other threads, so the logger thread can be gone with lines still
queued and the unbounded Flush waits forever (guest_shader_link timed
out on 4 of the last 7 completed main builds since #6371). Run the bounded
FlushAtEnd there instead.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-16 14:30:15 +02:00
AlxcBTW e74484e0fb perf(agc): spin before blocking on the GPU mutex and the draw committer hand-offs
At the Minecraft Dungeons II title the queue-0 worker, the draw committer and the compute worker
hand work to each other 49,000 times per 10 s through the GPU mutex, the committer's condition
variable and DrawPipeline::Drain, and each hand-off that has to wait costs a kernel wake-up. The
three workers are busy 21-34 % of the time: the GPU idles because the chain of hand-offs is slow,
not because a thread runs out of CPU.

GpuMutexType::lock retries try_lock for a window before it blocks, the committer polls its item
count for the window before it waits on its condition variable, and Drain polls for twice the
window before it waits for the committer. A hand-off that completes within the window costs no
wake-up. The window is 150 us on machines with at least 8 logical processors and 0 below that;
APS5_HANDOFF_SPIN_US sets it (0 turns it off). Ordering and the lock's semantics are unchanged.
The test agc_handoff_spin covers the window policy, the spin helpers and contended locks, and
docs/dev/ARCHITECTURE.md describes the spin.
2026-10-16 14:19:43 +02:00
AlxcBTW 2a15be09c9 perf(agc): pre-sync the DCC key writers of a dispatch's images before the GPU mutex
The stage-A pre-sync of a dispatch listed only the surfaces that stage B reads on the CPU. The DCC
keys of an image that a recorded kernel writes were not in the list, so stage B waited for that
kernel in WaitForKeyWriters with the GPU mutex held and every other worker queued behind the wait.
At the Minecraft Dungeons II title the pre-sync fired once in 296,000 dispatch checks while
WaitForKeyWriters synchronized 400 times per 10 s.

PresyncSurfaces now also returns the key range of every image binding (PresyncSurface::dccKeys).
presync waits for a key range only while recorded work writes it, the key-store memo does not
already give its value (DccKeyWritersPending) and, under the mutex it takes for a moment anyway, the
pending stores do not give the keys either (DccKeyWriterNeedsSync, the test WaitForKeyWriters makes
before it synchronizes). Which keys the dispatch reads, and when, is unchanged: the locked lookup runs
as before and finds nothing pending.

APS5_NO_DCC_PRESYNC=1 leaves the key ranges out. The [presync] line counts key ranges checked,
pre-syncs that only the keys required and the outcomes of WaitForKeyWriters. The test
agc_driver_dcc_key_presync covers a kernel that writes the keys, a recorded fill of the keys and
nothing pending, and skips with the reasons check_skips.py expects when the device has no timeline
semaphores or no host import.
2026-10-16 14:18:46 +02:00
Adrià 127e5bf826 Merge pull request #5872 from Sam-mon7/perf/lazy-shader-prepare
perf(agc): allow shader preparation on first use
2026-10-16 14:01:24 +02:00
Adrià 0615cd2b82 Merge pull request #5875 from adriadam10/perf/agc-pipeline-journal
perf(agc): journal the pipelines of a run and build them ahead on the next one
2026-10-16 13:19:18 +02:00
Adrià Franch ebc2342d1e docs(architecture): describe dispatches queued as pipeline items 2026-10-16 12:53:38 +02:00
Adrià FranchandClaude Fable 5.1 4aafda8e3b perf(agc): journal the pipelines of a run and build them ahead on the next one
A graphics pipeline the store builds or a compute pipeline a dispatch builds is
appended to pipelines.journal next to the shader cache entries, as the
materialized result of each stage (the specialized SPIR-V, bindings and push
constants) plus the recipe the pipeline was built from: the stages'
identities, vertex input layout, descriptor set layout keys and the state
fields the Pipeline constructor reads. When a VulkanDevice is created with a
window (a headless device waits for its first draw or dispatch, since the
window's device usually replaces it first) the journal is loaded and its
pipelines are built on a pool of threads while the title loads, so the first
draw or dispatch of a configuration seen on an earlier run finds its pipeline
resident instead of compiling it on the queue worker.

The pipeline stores key on RecompileResult::Identity, a hash of the module
words, specialization constants, subgroup width and bindings that is the same
on every run (SharedSpirv::ContentHash, computed once per shared module),
rather than on the process-lifetime variant ids; a generated stage is keyed
like any other. CachedPipeline builds outside the store's mutex and the
library parts and link are built outside the library store's mutex, so replay
threads and queue workers build in parallel; a concurrent build of the same
key keeps the first inserted. A replayed pipeline enters the store at its
least recently used end and a full store takes none, and its optimized relink
waits for the first draw that binds it, so the title's own pipelines come
first in both. The compute pipeline creation moves into
VulkanDevice::createComputePipeline, shared by dispatches and the replay. The
store key also covers the color component mapping, the depth target's sample
count and a depth bias clamp, which the constructor reads.

The file names the device it was written for (the properties the driver's
pipeline cache file is named by) and starts over for another; a torn tail is
cut at a record boundary; two runs sharing the directory take turns on a lock
file and keep each other's records; a read failure never starts the file
over. A device cancels its replay before it is replaced or destroyed, and a
replaced device destroys its pipelines then rather than at the driver's stop.

APS5_NO_PIPELINE_JOURNAL=1 disables it; APS5_PIPELINE_JOURNAL_THREADS,
APS5_PIPELINE_JOURNAL_ENTRIES (the first recorded), APS5_PIPELINE_STORE_ENTRIES
and APS5_PIPELINE_JOURNAL_MAX_MIB bound it; APS5_PROFILE_DRAW prints a
[pipeline-journal] summary. APS5_VERIFY_SET_LAYOUTS=1 now also checks a
dispatch's set layout against the one described from its shader. The lavapipe
test suite, which shares one cache directory, runs with the journal off except
for the journal's own tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P6hw3kRAuQdfcU9YCZgaoK
2026-10-16 12:52:28 +02:00
Yunus Shaikh ca174bc7e6 docs(agc): drop the disk cache's format version from ARCHITECTURE.md
The line said FormatVersion 36 while the code is at 37, as main's says 33 against 36. Without the number it no longer goes stale on each bump or conflicts between PRs that bump it.
2026-10-16 02:39:51 -07:00
AlxcBTW e44ab157e0 docs: describe the log queue, its flush points and the stream order in ARCHITECTURE.md
The logger thread, the bounded queue that blocks its caller, the LogSink
hook, APS5_LOG_SYNC and APS5_LOG_STATS, where the queue is flushed and
what stays outside it: direct fprintf(stderr) and printf calls are written
at once, so they can appear before older queued lines.
2026-10-15 20:42:16 +02:00
Sam 96070188b9 perf(agc): allow shader preparation on first use
APS5_LAZY_SHADER_PREPARE=1 keeps registration validation and decoding but skips queueing the preparation. Draws and dispatches prepare a deferred snapshot on first use and cache it; explicit ABI resolution still prepares its stages. Titles that register thousands of shaders at boot no longer hold every prepared shader in memory before the first frame.

Cover lazy dispatch, metadata and failure cases in both asynchronous and synchronous modes.
2026-10-15 19:26:20 +01:00
hugodearaujo e8b7361a1c fix(agc): settle dma-buf imports on Linux on the settle queue
The kernel maps a dma-buf import's pages for the GPU at the first submission that uses it. On RADV
that took about 2 s for a 3 GiB import, in the second frame of Hades (PPSA03355), and its staging
pool ran out while the frame was held.

Linux now makes the settle queue from #6060 when the device imports dma-buf memory, and settles
every dma-buf import of 1 MiB or more right after it is made with settleImport's 4-byte copy. The
settle queue needs neither the GPU mutex nor the main queue's work to finish. A dma-buf import's
buffer is shared with the settle queue's family instead of aliased, since a sparse import is bound
to many memory objects. Where the sparse queue already takes that family, the settle queue is a
second queue of it.
2026-10-15 10:19:46 -03:00
Yunus Shaikh a34ba0c7c7 Merge branch 'main' into perf/shader-direct-cpu-evaluable-images 2026-10-15 03:54:08 -07:00
Yunus Shaikh 0b099964f0 Merge branch 'main' into perf/shader-direct-cpu-evaluable-images 2026-10-15 03:19:52 -07:00
Adrià FranchandClaude 03401459f9 perf(agc): pipelined draws and exact draw write ranges on by default
Queue 0 now pipelines draw commits unless APS5_PIPELINED_DRAWS=0, and draw write ranges use the exact
color, DCC, CMASK, depth and stencil sizes unless APS5_EXACT_DRAW_WRITES=0. The base tests now run the
pipeline; agc_driver_pm4_failure_inline and agc_driver_draw_cache_reuse_inline keep the inline path
covered.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-15 12:13:26 +02:00
Adrià FranchandClaude 4051ee19c5 docs(architecture): describe the queue-0 draw pipeline
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-15 11:34:29 +02:00
Adrià FranchandClaude 23fccd7632 fix(agc): settle only imports of 1 MiB or more
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-15 09:58:21 +02:00
Josip Parlov 295b1c55f4 fix(agc): drop the import-watch environment switches
The second queue that settles imports is always created on Windows, and a device without one keeps
the comparisons. The other-family settle path is tested through the recorder test device built
with its settle queue on another family (agc_import_watch_other_family) instead of
APS5_SETTLE_OTHER_FAMILY.
2026-10-15 09:46:48 +02:00
Adrià FranchandClaude 1ff6ca7a67 docs(architecture): describe settled watched imports of private memory on Windows
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-15 09:46:48 +02:00
Yunus Shaikh d4b33b0ae8 perf(shader): select the record of a T# whose offset the CPU evaluates
Since #757, a T# loaded from a buffer heap at a runtime offset is an
image table column, and every capture snapshots the whole heap, up to
65,536 records, even when user data or an SRT read fixes the offset.
When the T#'s eight dwords and its offset are runtime values the SRT
walker evaluates, the column is now selected: the offset is kept as a
descriptor source, the shader indexes entry 0, and the snapshot reads
only the record at the evaluated offset, with the heap base moved to
that record.

The column keeps a table's semantics. An invalid T#, an unmapped record
or an offset that carries past 32 bits is poison that faults only when a
lane selects it; an offset the walker cannot read faults the table
instead of throwing; the record's mode is picked per entry, so no
pipeline variant is added; and a dispatch that writes the record faults
it as written by its dispatch. Dwords past the heap read zeros, as the
scalar loads would, and a record past the key cap is read rather than
poisoned.

A column stays a whole table when its offset is chosen per lane, or when
the shader may write the memory the offset is read from at run time: the
buffer it is read from (by V#), or any memory when the read or a write
goes through a pointer or a GPU-only descriptor. Samplers are unchanged:
an S# the walker evaluates is already a direct sampler. The plan stores
the selection, so the disk cache format is 36.

Five agc_shader_memory and agc_shader_bindless checks asserted the old
planning and now assert the selected record:
- "image table SRT key": a key read through the SRT narrowed a 4-key
  table to records 0 and 2. It now selects record 2 alone, captures no
  other record, and a changed key moves the capture without changing the
  module or its disk key.
- "image table key union" and "image table late key": two keys of one
  heap shared one column with two value key domains. Each key now
  selects its own record, as two one-record tables.
- "image table unkeyed access": a read keyed through the SRT shared a
  whole column with a read keyed per lane. The first now selects its
  record; the second keeps the whole, unnarrowed table.
- "uniform table image": the T# at a user data offset was one key of a
  full table. It is now the one selected record, and the record the user
  data does not select is not read.
A value key domain only arises for a key the walker evaluates, so it no
longer narrows an image column; key buffer domains (keys loaded per
lane) still do.
2026-10-15 02:48:55 +00:00
Dean 8ae436891a Merge pull request #5181 from ThatsLeo/perf/shader-compact-resource-plans
perf(shader): keep only evaluable branch conditions in resource plans
2026-10-15 10:56:57 +13:00
Adrià FranchandClaude d8e97a7697 fix(recompiler): move stencil artifacts to cache format 35
Main's flatVolumeLineCompatible change also took format 34 with another layout, so entries of either would load as the other.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-14 22:09:03 +02:00
fdfawef324andCodex 97a0a4d098 refactor(agc): separate stencil replay from its follow-up topics
Move bounded vertex folding and empty-image feedback into separate topics. Keep the replay coverage proof, stage contracts, finite-reference tests and mapped cache regressions; version the stencil layout separately as format 34.

Co-Authored-By: Codex <noreply@openai.com>
2026-10-14 22:08:48 +02:00
Adrià b6406a1715 Merge pull request #5792 from SaloQT/feat/prebuilt-shader-packs
feat(recompiler): load prebuilt shader packs before the writable cache
2026-10-14 21:26:42 +02:00
ThatsLeo 26f0331094 perf(shader): keep only evaluable branch conditions in resource plans
A plan copied every control-flow condition with all its operands, though one
that depends on a vector register is never evaluated. Such conditions are
dropped, and the copied values live in one arena without use lists.
2026-10-15 08:22:09 +13:00
SaloQT 93ab137194 test(recompiler): cover prebuilt shader pack loading
Build packs from EncodeEntry output and check the hit and miss paths,
an entry stored under another key, a truncated entry, a damaged index,
an out-of-range record, a foreign compiler source version and an
unknown format. A child process covers pack discovery on a real disk
load, the ANYPS5_NO_PREBUILT_SHADERS opt out and fall-through to the
writable cache. ARCHITECTURE.md describes where the prebuilt lookup
sits.
2026-10-15 05:44:34 +11:00
Adrià c14fd87044 Merge pull request #4721 from fdfawef324/build/stable-import-libraries
build(libs): keep import libraries stable so a source edit relinks one library
2026-10-14 19:08:35 +02:00
Celegans12 34525fdf7a feat(agc): implement image tables loaded through 64-bit pointers
Plan runtime-indexed T# and S# columns read through a 64-bit pointer.
Preserve the console offset alignment, signed immediate and key bounds.
Track the descriptor dwords consumed by each table in its invocation.
Reject table reads when a writable buffer or storage image overlaps them.
Cover the overlapping-storage fault and isolated second-table behavior.
2026-10-14 16:43:57 +02:00
Celegans12 d9e47d1e98 feat(agc): implement image and sampler tables indexed at runtime offsets
Materialize runtime-indexed image and sampler records into descriptor
tables while keeping descriptor selection in the shader. Report invalid
active entries through the existing deferred fault buffer.

Bind fragment tables in the fragment set when pipeline libraries split it,
and reserve a physical descriptor for empty tables. Reject unsupported
sampler, border, cube and anisotropic pairings per entry. Keep one column
per heap access shape, one T# shared by several image operations included
(milescoviello's #5550 case), and narrow it to the union of its keys'
domains when every key has one (SleglJan's key-domain changes). Plan
columns read at (key << n) & mask over their whole domain (tibzejoker's
fix). Read records in runs through SleglJan's bulk reader, and reject
entries whose surface has no mapped byte, as Tamalero proposed. Store
table columns in disk plans; update the runtime ABI and disk format. Bound
table-sized entries in the resource cache (65,536 table elements) and the
dispatch cache (256 MiB of captured words), evicting LRU.
2026-10-14 16:43:57 +02:00
fdfawef324andClaude Opus 5.5 2251649a7f build(libs): keep import libraries stable so a source edit relinks one library
Every library under core/libs/prx links the libc and libkernel import
libraries, and every NID patch step depended on the unpatched libc.prx.
GNU ld 2.46 rewrites the import library on every link with new archive
timestamps (the member headers and the symbol map header), so the restat
on the link edge never pruned anything: one libc or libkernel .cpp edit
relinked all 124 libraries and re-patched all of them.

On MinGW the shared library link now writes the import library to
<implib>.new with SOURCE_DATE_EPOCH=0 (ld then writes zero member
timestamps), makes the symbol map deterministic with ranlib -D and copies
the result over the import library only when its bytes differ
(cmake -E copy_if_different). The link edge keeps restat, so a link that
keeps the exports leaves the import library untouched and prunes every
dependent link. The SOURCE_DATE_EPOCH also zeroes the export directory
TimeDateStamp of the DLL; the COFF header TimeDateStamp that the unwinder
keys on is unchanged.

The NID patch steps only use the names libc exports. nid_patcher
--write-export-list writes those names, normalized and sorted, to
libc.exports and leaves the file untouched when they did not change; the
patch steps read it with --preserve-export-list and depend on it instead
of libc.prx, on every platform. A patched library is byte-identical to
one patched against libc.prx.

Measured on Windows (GCC 15.2, -j 4, loaded machine) after touching
libc/src/Strings.cpp: with CMAKE_LINK_DEPENDS_NO_SHARED off, 124 links and
124 patches in 74.9 s before, 1 compile, 1 link, the export list and the
libc patch in 4.3 s after; with it on, 124 patches in 7.8 s before and
the same four steps in 5.3 s after. Touching libkernel/Time/Time.cpp
runs 123 links and 123 patches (89.1 s) before and one compile, link and
patch (3.4 s) after. Removing a libc export still relinks and re-patches
all 124 libraries.

nid_patcher_macho covers --write-export-list (sorted normalized names, no
rewrite when unchanged, rewrite when changed) and --preserve-export-list
(the same exports kept as with --preserve-exports; malformed lists and
two references refused). It fails on main, where the option is unknown.

Sources: SOURCE_DATE_EPOCH, https://reproducible-builds.org/specs/source-date-epoch/;
ranlib -D and ld --out-implib, GNU binutils documentation; ninja restat,
https://ninja-build.org/manual.html#ref_rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:43:05 -04:00
Michon e9249a0a47 feat(agc): host import windows, least-recent eviction under the budget
A registered range larger than 64 MiB is imported one 64 MiB window at a
time for buffer and fill accesses instead of whole, and the live imports
stay under a budget: APS5_HOST_IMPORT_MIB, else the larger of 6 GiB and
half the physical memory, at most the largest host heap less 2 GiB
(imports and type-3 mirrors share it; #167's rule). An import past the budget
retires the least recently used imports not used in the last 256
lookups.

- An access across window edges imports the cells it touches, grown to
  the union of the range's imports it overlaps, so uses across
  neighbouring edges share one import instead of replacing each other.
- An address-based build (AcquireRegistered) imports the range whole; a
  window over its first byte does not serve it.
- Every refusal (alignment, a failed run, a held import, the budget with
  what may be evicted) is decided before anything is retired.
- An import made when a range is mapped never evicts: the map observer
  holds no GPU lock.

The window size and the budget have setters for the tests.
2026-10-14 14:51:48 +13:00
Michon 086f7d5091 feat(agc): depth planes sampled as 2D arrays, cubes and integer views
A texture over a depth surface could only sample one 2D slice of the
plane's own format; 2D arrays, cubes and the R16_UINT view of a D16
plane were refused ("sampling the depth plane of depth surface ... is
not implemented"). A one-slice 2D array now views the depth image as an
array. Several slices, a cube, or an integer view of a D16 plane are
copied into a DepthPlaneCopy image at every use: each slice from the
depth surface bound at its address (base plus one 64 KiB-block depth
slice per layer), otherwise from guest memory, detiled. A depth-bits
R16_UINT view (a depth swizzle) keeps the depth copy, since the shader
converts the sampled depth to its bits itself (GTA V's compute
0x242d4a200 samples a D16 plane array that way, reported by
@tibzejoker). Such a texture is not kept in resource templates.

Storage-image access to depth surfaces is no longer part of this
change; #4500 implements it.

TechnicalDebt (Depth plane copies) and ARCHITECTURE describe the copy and
what still throws.
2026-10-13 12:11:02 +13:00
Dean 30e169127d Merge pull request #4419 from Ondra97/codex/deferred-module-lifetime
fix(libkernel): bind deferred initialization to the loaded image
2026-10-13 07:24:39 +13:00
ThatsLeo ca99658cef perf(agc): memoize untracked texture comparisons by the stores that reach the surface
The memo stamp held TrackerGeneration, which every collect and driver store
advances, so it almost never matched. Driver stores into uncovered ranges
record their generation per 64 KiB block, and NewestStamp of the surface
replaces TrackerGeneration in the stamp.
2026-10-12 14:34:14 +02:00
Ondra97 ec5688a666 fix(libkernel): bind deferred initialization to the loaded image 2026-10-12 12:34:35 +02:00
Adrià FranchandClaude acc0a0487d docs: describe shader modules and pipeline libraries built at registration
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-12 09:50:41 +02:00
Adrià 90933951d9 Merge pull request #419 from MichonGoddijn231849/feat/msaa-targets
feat(agc): multisampled color and depth targets with CB resolves
2026-10-12 00:01:37 +02:00
Adrià Franch 5c5912c49e fix(agc): guard a multisampled target's whole sample planes; docs for host-only multisampled targets
The guest-access guard covers CB bytes times samples (the single-sample layout size alone missed the later sample planes' tile padding), with a test row in that padding. Remove two TechnicalDebt lines duplicated by the rebase, document the guard bound and the unevicted targets, and add the multisampled targets to ARCHITECTURE.md.
2026-10-11 23:41:30 +02:00
Adrià Franch fdf8246f5e docs(architecture): describe APR and AMM command buffer queues 2026-10-11 19:56:25 +02:00
Alex ColliniandClaude Opus 5.5 687f73c951 fix(agc): select the registered back half for a fused hull header
sceAgcFuseShaderHalves returns a fused hull as a copy of its back half
(type 7) that is not registered: type 3, no user data, and shader
registers copied to scratch memory with the checksum, RSRC1/RSRC2 and
the LS program address rewritten. sceAgcCreatePrimState accepts only that
type-3 header, and graphics ABI resolution refused it as a replaced
shader header.

SameHeader now also takes a type-3 header as the registered type-7
header for the same code when every other field is equal and its shader
registers differ only in the fused ones. Graphics ABI resolution then
applies the bound header's shader registers over the registered state.

PPSA26344 fuses its hull halves during its loading screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:36:01 +02:00
Adrià FranchandClaude 8c619f16da docs(architecture): user-data callees; drop code comment
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-11 09:59:27 +02:00
Adrià Franch 250b617f14 docs: record the two-pass blend into 8_8 SWAP_ALT targets 2026-10-11 04:00:52 +02:00
Adrià 2cae6cc3cd Merge pull request #2917 from XairRrR/feat/agc-prepare-unpredicted-variants
feat(agc-driver): prepare shader variants registration did not predict
2026-10-11 01:31:32 +02:00
Adrià FranchandClaude 946158a503 docs(architecture): describe the per-code header registry
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-11 01:04:50 +02:00