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.
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]>
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).
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.
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]>
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]>
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).
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]>
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]>
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.
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
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]>
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.
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.
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.
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.