Commit Graph
6138 Commits
Author SHA1 Message Date
Dean 698c23d81e Merge pull request #528 from adriadam10/test/vop1-vop2-integer
fix(shader): v_cvt_rpi/flr_i32_f32 NaN and exact round-half-up, VOP1/VOP2 integer test
2026-10-05 21:35:53 +13:00
Dean e3e1dfae16 Merge pull request #526 from adriadam10/test/scalar-sop1
fix(shader): s_wqm SCC and s_andn{1,2}_wrexec destination, SOP1/SOPC/SOPK execution test
2026-10-05 21:35:11 +13:00
Dean 5ad0c99f98 Merge pull request #525 from adriadam10/test/scalar-alu
fix(shader): s_lshlN_add SCC and s_bfe_[iu]32 fields past bit 31
2026-10-05 21:34:30 +13:00
Dean 1e37ed456a Merge pull request #514 from adriadam10/fix/dpp16-movrels-pk-fmac
fix(shader): DPP16 operands of v_pk_fmac_f16 and the relative moves
2026-10-05 21:33:48 +13:00
Dean 6fd9739d9d Merge pull request #510 from adriadam10/feat/sdwa-vop2-integer-clamp
feat(shader): saturate VOP2 integer SDWA results on clamp
2026-10-05 21:33:38 +13:00
Dean 070ed19249 Merge pull request #509 from adriadam10/feat/sdwa-vop2-integer
feat(shader): accept every SDWA selector on VOP2 integer ops
2026-10-05 21:32:55 +13:00
Dean 17ef50b681 Merge pull request #504 from adriadam10/feat/vop3-opsel-16
fix(shader): VOP3 op_sel on v_alignbit/alignbyte_b32 and 16-bit max3/min3/med3
2026-10-05 21:32:13 +13:00
Dean Galvin 16f19c51ad feat(agc): thick mip tails of mipmapped 3D textures
3D textures in SW_4KB_S or SW_64KB_S whose smaller levels reach the mip
tail were refused ("3D texture mip tails are not implemented"). The
tail now follows addrlib's GFX10 thick layout
(Gfx10Lib::ComputeSurfaceInfoMacroTiled):

- a level enters the tail once its size, rounded up, fits the thick
  tail extent (the block with its height halved for 4 KiB blocks and
  its width halved for 64 KiB blocks) and at most GetMaxNumMipsInTail
  levels remain (5 for 4 KiB, 10 for 64 KiB);
- each slab starts with one tail block, then the other levels from the
  smallest to level 0;
- tail level i sits in slot maxMipsInTail - 1 - i, at the element offset
  addrlib derives from that slot's byte offset and the 256-byte thick
  micro block, at every depth slice.

The tests check element addresses of five volumes (4 KiB and 64 KiB
blocks, 1 to 16 bytes per element, odd sizes, a chain that starts in
the tail) against Mesa addrlib's Addr2ComputeSurfaceAddrFromCoord for
a Navi10 configuration.
2026-10-05 21:32:06 +13:00
Dean bbedad3b00 Merge pull request #500 from adriadam10/fix/f32-clamp-bits
fix(shader): clamp f32 results to +0 for NaN and -0
2026-10-05 21:31:31 +13:00
DotDebianandClaude Opus 5.5 84974a9b2a fix(shader): locate image LOD, mip and sample index by the instruction's dimension
The address operands of a MIMG instruction are laid out by its own DIM
field: the decoder counts them that way. The SPIR-V emitter located the
gradients, the LOD, the LOD clamp, the mip level and the sample index
after the coordinate count of the bound T# instead, so an array
instruction on a non-array texture took the slice as its LOD.

An instruction that addresses fewer coordinates than its image has is
refused instead of reading the next address component as a coordinate.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 10:29:48 +02:00
Dean Galvin 23d67b59a7 perf(agc): keep label stores larger than 64 bytes out of the label table
PPSA21564 clears each occlusion query slot with a 256-byte WRITE_DATA
before its begin dump, about 8,400 times per frame in one level. The
clear is larger than a deferred label, so it is recorded at once, and
Recorder::NoteLabel entered all 64 of its dwords in the pending-label
table: 64 map inserts, 64 batch dword entries and 64 group entries, then
64 lookups in CloseLabelGroup and 64 erases when the batch is reaped. A
replay of that pattern (21 clears per batch, 20 batches in flight) costs
1.6 us for the inserts, 0.7 us for the close and 1.3 us for the erases
per clear, about 31 ms of the graphics worker per frame there. The new
path costs 0.07 us per clear in the same replay.

The table serves WAIT_REG_MEM values and CPU pollers, which read 4 or 8
bytes. A store larger than 64 bytes (the bound the driver's own label
history already uses) now enters one range instead of its dwords:
- table entries inside the range are removed, as the store replaced
  them before: a lookup there finds no value and falls back to the
  pending-write path, where it used to find the store's value;
- the range leaves with its batch, like the dword entries;
- PendingLabelIn sees the range, and the write-evidence check, which
  syncs a capture read over any pending label dword, now also syncs
  over a pending range (Recorder::WideLabelIn).
The store is still a pending write of the open batch (the flush hook
syncs CPU readers), still starts the label flush deadline and is
recorded exactly as before. Stores of 64 bytes or less are unchanged.

Test: agc_driver_recorder_tests largeLabelTests (a 256-byte label enters
no dwords and removes the older entry under it but not one beside it,
its range, pending write and deadline, a later 4-byte label over it,
a 64-byte label entered in full, everything gone after the batch).
2026-10-05 21:28:34 +13:00
Dean Galvin 2b388e0b0e perf(agc): resolve the recorder's query and fold entry points once
Every occlusion counter dump the recorder handles on the GPU ends the
batch's occlusion query, copies the pending results, folds them with a
compute dispatch, then resets and begins a new query. Each of those
commands was resolved through vkGetDeviceProcAddr, a loader name lookup,
on every call: seven lookups per dump on the graphics worker, and a title
dumps the counter twice per occlusion query. The mesh argument and timing
paths resolved their bind, push, dispatch and reset per call too.

They join the entry points the recorder already resolves in its
constructor: vkCmdBeginQuery, vkCmdEndQuery, vkCmdResetQueryPool,
vkCmdCopyQueryPoolResults, vkCmdBindPipeline, vkCmdPushConstants and
vkCmdDispatch. The same functions are called with the same arguments;
APS5_NO_PROC_TABLE=1 still resolves every call.
2026-10-05 21:27:15 +13:00
Dean ceeab79cff Merge pull request #618 from oneandonlydean/fix/relinker-linux-host-libc-needed
fix(relinker): keep the host libc.prx needed on Linux when a title bundles its own
2026-10-05 21:26:07 +13:00
DotDebianandClaude Opus 5.5 63711450f4 feat(libSceAudioOut): implement sceAudioOutGetLastOutputTime
Report the process time at which sceAudioOutOutput or sceAudioOutOutputs
accepted the port's last block. A port that never output reports 0, and
waiting with a null pointer does not move the time.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 10:25:30 +02:00
Dean fbd6ccbc36 Merge pull request #611 from oneandonlydean/perf/recompiler-masked-select-elim
perf(recompiler): drop exec selects whose inactive lanes nobody reads
2026-10-05 21:25:25 +13:00
Dean d18fa1367c Merge pull request #612 from oneandonlydean/perf/recompiler-lds-constant-slots
perf(recompiler): index lane-strided per-invocation LDS by constant slots
2026-10-05 21:24:42 +13:00
DotDebianandClaude Opus 5.5 096bdb38c2 fix(libkernel): give CLOCK_VIRTUAL, CLOCK_PROF and CLOCK_SECOND their FreeBSD meaning
clock_gettime and clock_getres served three FreeBSD clock ids with the wrong
clock or not at all:

- CLOCK_VIRTUAL (1) was the monotonic clock on Windows and threw on Linux; it
  is the user CPU time of the process.
- CLOCK_PROF (2) threw on both hosts; it is the user plus system CPU time of
  the process.
- CLOCK_SECOND (13) returned the coarse realtime clock with nanoseconds and a
  sub-second resolution; it is the current second with tv_nsec 0 and a
  resolution of one second.

Behaviour follows FreeBSD 11 clock_gettime(2) and kern_clock_gettime /
kern_clock_getres in sys/kern/kern_time.c.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 10:24:22 +02:00
Dean cf716ff268 Merge pull request #586 from oneandonlydean/fix/recompiler-lds-array-bound
fix(recompiler): size per-invocation LDS arrays to the addresses they reach
2026-10-05 21:24:00 +13:00
Dean Galvin 604826c138 perf(agc): pin the queue workers to the performance cores on Linux
CpuTopology::Get and Pin did nothing off Windows, so on a hybrid CPU the
queue workers, the draw front end and the presenter ran wherever the
scheduler put them. On a Linux kernel that reports the same capacity for
every CPU, those threads spent part of each frame on the efficiency cores.

Get now reads the process affinity with sched_getaffinity and the core
classes from /sys/devices/cpu_core/cpus and /sys/devices/cpu_atom/cpus, and
Pin restricts the calling thread with pthread_setaffinity_np. A CPU is
hybrid only when both lists exist and are disjoint, so non-hybrid CPUs are
never pinned. On Linux the workers are pinned to the performance cores of
a hybrid CPU by default; APS5_NO_WORKER_AFFINITY=1 leaves them free.
Windows keeps the APS5_WORKER_AFFINITY=1 opt-in, and
APS5_WORKER_AFFINITY_MASK=<hex> still picks the cores on any platform.

PPSA21564 on a hybrid CPU, pinned against unpinned (2 runs each): title
46.7 to 49.0 fps, menu 45.4 to 47.7, intro level 37.2 to 41.3 (space) and
31.7 to 37.6 (ships). Hub descent 6.56 to 7.92 fps (3 runs each).
2026-10-05 21:23:34 +13:00
Dean c0e8c89e2e Merge pull request #505 from oneandonlydean/fix/agc-compacted-color-exports
fix(agc): route compacted color exports to the slots CB_SHADER_MASK enables
2026-10-05 21:23:18 +13:00
Dean ef88b5be00 Merge pull request #490 from oneandonlydean/fix/agc-views-past-max-mip
fix(agc): address texture views that start past the surface's last mip
2026-10-05 21:22:36 +13:00
Dean b0c2e05ba0 Merge pull request #619 from oneandonlydean/fix/agc-writeback-block-padding
fix(agc): keep the guest's bytes in the padding a storage write-back stores
2026-10-05 21:21:54 +13:00
DotDebianandClaude Opus 5.5 c0d293ff7b feat(font): implement sceFontGetPixelResolution
Report the sub-pixel resolution of the library's driver.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 10:05:31 +02:00
DotDebianandClaude Opus 5.5 9837c69f0d feat(agc): implement sceAgcDcbPrimeUtcl2 and its size query
The DCB variant threw "not implemented" while the ACB one wrote a
5-dword packet. Both now go through Agc::Command::WritePrimeUtcl2 and
the size query returns 20 bytes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 10:05:28 +02:00
DotDebianandClaude Opus 5.5 992933b95c feat(libc): implement snwprintf_s
Format through the shared wide formatter, truncate to the buffer and
return the full length, as C11 K.3.9.1.3 specifies. A null buffer or
format, a zero or oversized n, %n and a null %s argument are
runtime-constraint violations that return a negative value.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 09:59:30 +02:00
Dean Galvin a3ec50aefa fix(agc): keep the guest's bytes in the padding a storage write-back stores
A GPU-direct storage write-back retiles the image's elements into an
uninitialised device scratch buffer and then copies whole ranges of it
into the import or the import's unit shadow. The retile writes only the
bytes elements occupy, so every other byte of the copied ranges was
stored from uninitialised scratch memory: the padding of the tile blocks
a surface covers only partly (its last block column and row), mip tail
blocks, the bytes between mips and the pitch padding of linear surfaces.
Another surface over the same memory, or the CPU, then read garbage
there instead of what the memory held. Both the block-window path and
the whole-layer path did this.

Before the retile, the bytes of the copied ranges that no element holds
are now copied into the scratch from where they are current (a fresh unit
shadow, else the import), so the write-back stores them unchanged. Whole
tile blocks every byte of which holds an element are left out
(CoveredMipBytes), so only partly covered blocks pay for the extra copy.

Tests: CoveredMipBytes against the XOR address equations of the four
64 KiB XOR modes at every element size; a device test that stores
cleared images over patterned memory (tiled with and without mip chains,
off a 64 KiB boundary, 4 KiB standard, linear; plain and watched memory)
and checks every byte that is not a texel kept its pattern.
2026-10-05 20:51:34 +13:00
Dean Galvin 7103f390cf fix(relinker): keep the host libc.prx needed on Linux when a title bundles its own
A title that ships sce_module/libc.prx makes addHost drop the host
libc.prx from the executable's dependencies. Every host prx needs it, but
their DT_RUNPATH is empty, so loading fails with "libc.prx: cannot open
shared object file".

Mirror the Windows rule from WindowsImportBuilder: when libSceLibcInternal.prx
is a host dependency and no host libc.prx remains, append libc.prx as the
last host dependency. The title's own imports still bind to the guest
modules first.

Refs #476
2026-10-05 20:36:29 +13:00
Dean e7bf72d503 Merge pull request #616 from adriadam10/feat/mimg-tfe-lwe
feat(shader): accept tfe and lwe on MIMG loads, stores and samples
2026-10-05 20:29:00 +13:00
Dean 05ed3e29dc Merge pull request #614 from nicolasabosch/fix/relinker-function-aliases
fix(relinker): allow function aliases with different sizes
2026-10-05 20:28:18 +13:00
Dean 56acdef2f5 Merge pull request #569 from adriadam10/feat/global-store-addtid
feat(shader): implement global_store_dword_addtid
2026-10-05 20:27:36 +13:00
Dean 033ac78439 Merge pull request #563 from adriadam10/feat/vop3-mad64-clamp
feat(shader): implement clamp on v_mad_u64_u32 and v_mad_i64_i32
2026-10-05 20:26:55 +13:00
Erige005 fb16cb3b19 test(agc): discard the results of recompiles expected to be refused 2026-10-05 07:12:32 +00:00
Adrià FranchandClaude d044384a89 feat(shader): implement global_store_dword_addtid
Decode FLAT opcode 0x17 in the global segment and store the data VGPR of
each active lane at the SGPR pair base plus the signed offset plus four
times the lane id, sharing the address path of global_load_dword_addtid.

Co-Authored-By: Claude <[email protected]>
2026-10-05 08:41:19 +02:00
Adrià FranchandClaude 1cc11cee57 feat(shader): accept tfe and lwe on MIMG loads, stores and samples
Co-Authored-By: Claude <[email protected]>
2026-10-05 08:25:57 +02:00
Dean Galvin 31af5a9d22 perf(recompiler): index lane-strided per-invocation LDS by constant slots
Stages without workgroup memory keep LDS in a per-invocation Function
array. With the array sized to the addresses it reaches (#586),
PPSA21564's intro pixel shader still takes 864 bytes of local memory
per thread on NVIDIA: its accesses are lane * 4 + {0, 256, 512}, so
the index stays dynamic and the 192-dword array lives in local memory.

When every LDS access's address is lane * stride + constant (lane id,
constants, adds, multiplies and left shifts), with one stride, a whole
number of dwords, for all of them and no wrap (the bound analysis
already proves that), the emitter now addresses each access by its
constant plus its instruction offset. The lane id is the same for
every access of an invocation, so taking lane * stride out maps the
addresses one to one, keeps which accesses alias and keeps every
access in bounds; the array sees only constant indices and the driver
keeps it in registers. Shaders with any other address form are
emitted as before.
2026-10-05 18:18:29 +13:00
Dean Galvin bdf39840cf fix(recompiler): size per-invocation LDS arrays to the addresses they reach
Stages without workgroup memory (vertex, pixel, ...) keep LDS in a
per-invocation Function array of 8192 dwords. PPSA21564 creates a pixel
shader that keeps three lane-strided slots there (lane * 4 at byte
offsets 0, 256 and 512). On NVIDIA (driver 610.57) the array becomes
32864 bytes of local memory per thread, and about 15 s after the
pipeline is created the GPU hangs: Xid 109 (CTX SWITCH TIMEOUT), or
Xid 31 (FAULT_PDE VIRT_WRITE) with VK_EXT_device_fault enabled, at an
address no Vulkan object ever held.

The analysis now bounds each LDS access's address over lane ids,
constants, adds, multiplies, shifts, ands, ors, xors, min/max, selects,
bitfield extracts and phis of those. A loop-carried or otherwise
unknown address, an add that could wrap with the instruction offset,
or a shared access whose width is not listed has no bound. The array
becomes the dwords the accesses can reach, rounded up to 64, and stays
at 8192 when any access is unbounded. No access can reach past the
smaller array, so the bounds checks and results are unchanged. The
pixel shader above now takes 192 dwords.
2026-10-05 18:18:18 +13:00
Dean 3655c2ae34 Merge pull request #506 from adriadam10/feat/global-atomics-float
feat(shader): implement the float and 64-bit inc/dec flat and global atomics
2026-10-05 18:15:22 +13:00
Dean 2afc403a04 Merge pull request #495 from adriadam10/feat/flat-cache-bits
feat(shader): accept the glc, slc and dlc cache bits on flat, global and scratch
2026-10-05 18:14:41 +13:00
Dean 12cf58256f Merge pull request #491 from adriadam10/feat/global-atomics
feat(shader): implement the integer flat and global atomics
2026-10-05 18:13:59 +13:00
Dean 7db3e55c6a Merge pull request #591 from hamb3r/fix/avplayer-decode-ahead
fix(avplayer): add decode-ahead buffers for application video output
2026-10-05 18:13:18 +13:00
Dean 50e7712002 Merge pull request #608 from adriadam10/test/scalar-program-flow
fix(shader): wave-wide exec and vcc branch conditions
2026-10-05 18:13:07 +13:00
Dean f809199b0a Merge pull request #606 from adriadam10/fix/permlane16-inactive-source
fix(shader): keep v_permlane16 lanes whose source lane is inactive
2026-10-05 18:12:26 +13:00
Dean Galvin 60c655211d perf(recompiler): let exec-guarded memory and exports end a masked select
A register written under EXEC is usually consumed by an LDS or buffer
access or an export under the same EXEC. MaskedSelectEliminator kept
the select in that case, which leaves the NGG mesh shaders where they
were.

The emitter performs buffer and LDS loads and stores (8 to 128 bits,
GPU-selected V# included) and SetAttribute only in lanes whose active
operand is true: stores and exports sit in an if on it, loads return 0
outside it. Every other operand is read only in those lanes. So such an
access whose active operand implies the select's condition reads the
derived value only where it equals the original, and it now ends the
search like a masked select does. The active operand itself, atomics,
append/consume, scratch and address-space accesses and image accesses
still keep the select.
2026-10-05 18:12:23 +13:00
Dean Galvin dd2a70bc13 perf(recompiler): drop exec selects whose inactive lanes nobody reads
Every VGPR write under EXEC is translated to Select(exec, new, old), so
in SSA each register's previous value stays live until the next write,
even when the guest only used the register as a temporary. In NGG mesh
shaders at 255 registers that keeps dead values alive and the driver
spills them to local memory.

MaskedSelectEliminator (after SSA, folding and the readlane pass)
replaces r = Select(m, new, old) by new when no lane where m is false
can observe r:
- every value derived from r is either a lane-local pure op or a phi,
  and their uses are checked the same way;
- or the derived value reaches the true operand of a select, or an
  operand of a LogicalAnd, whose condition implies m: m itself, a
  LogicalAnd containing m, or a thread bit rebuilt from a ballot of
  such a condition (also through ANDs of the mask words);
- anything else (cross-lane ops, ballots, readfirstlane, DPP, swizzle,
  permutes, stores, exports, atomics, branch references, implicit-LOD
  pixel samples, the false operand of any select) keeps the select.
The select and every value it reaches must be in blocks outside loops,
so each of them runs at most once per invocation and every use sees the
same instance of m. Selects whose condition is the full wave
((0xffffffff >> (lane & 31)) & 1) are replaced in any block.

Exact: in lanes where m is true r equals new, so every derived value is
unchanged there; in lanes where m is false only derived values change,
and each path from them ends in a select or AND that discards those
lanes. The selects are removed latest first and dead code is removed
between rounds, so chains of writes to one register collapse.
2026-10-05 18:12:23 +13:00
Dean e074483ab7 Merge pull request #604 from adriadam10/fix/smem-buffer-offset-overflow
fix(shader): return zero when an s_buffer_load offset passes 2^32
2026-10-05 18:11:43 +13:00
Dean e90d2edf54 Merge pull request #596 from adriadam10/fix/vop3-pack-ldexp-bits
fix(shader): compute v_cvt_pknorm_*_f32, v_ldexp_f32 and v_pack_b32_f16 on the bits
2026-10-05 18:11:01 +13:00
Dean bdd10f1929 Merge pull request #475 from adriadam10/feat/mimg-coarse-g16
feat(shader): implement image_sample_cd/_g16 variants and fix a16 gradient layout
2026-10-05 18:10:20 +13:00
Dean 71f248ad54 Merge pull request #568 from adriadam10/fix/f32-minmax-nan
fix(shader): return src0 when both f32 min/max sources are NaN
2026-10-05 18:09:38 +13:00
Dean 4ca60c434c Merge pull request #524 from DotDebian/feat/mimg-bvh64-intersect-ray
feat(shader): implement image_bvh64_intersect_ray
2026-10-05 18:08:57 +13:00
Dean 1e0f62e118 Merge pull request #515 from adriadam10/fix/dpp16-inactive-source
fix(shader): DPP16 lanes whose source lane is inactive
2026-10-05 18:08:46 +13:00