AMD hardware compares a sample_c reference against a color texture's red
channel; Vulkan defines comparison sampling only for depth formats, so the
driver refused any compare-sampled texture that is not R32 float or R16
unorm. PPSA21567 compares the same LESSEQUAL, clamp-to-border (opaque
white) sampler against a shadow map and against a 1x1 RGBA8 texture.
For a compare-sampled image whose format is not natively comparable, the
specialization records the paired sampler's compare function, filter,
addressing and border, and the view's reference clamp kind; the image then
binds as an ordinary color texture. The backend fetches the red channel,
applies wrap, clamp-to-edge or clamp-to-border addressing with the
sampler's texel-centre convention (border taps take the border's red),
compares, and blends four comparisons for bilinear filtering. Real depth
formats keep the native path.
Only sample_c / sample_c_lz on 2D and 2D array views, float/unorm/snorm
formats, wrap, clamp-to-edge or clamp-to-border, point or bilinear at one
level are implemented; half-border, the border color table and everything
else on this path throw. The reference clamp for unorm/snorm color formats
is not documented by AMD and is recorded in the technical debt.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The output modifier was a separate multiply by 2, 4 or 0.5. On NVIDIA
that multiply behaves as if folded into the instruction's own v_mul_f32
despite NoContraction, so a product that overflows comes back finite
after div:2 (FLT_MAX * 2.0 div:2 gave FLT_MAX instead of +inf).
Scale a normal result by adding to or subtracting from its exponent
field instead: mul:2/mul:4 saturate to signed infinity when the exponent
overflows, div:2 keeps the existing signed-zero flush below 2^-125, and
infinities and NaNs pass through unchanged. Over all 2^32 inputs the
results are bit-identical to the previous IEEE multiply and selects.
Titles that list libSceUsbd.prx as a needed module stop in the
startup stub with "Failed to load module: libs\libSceUsbd.prx"
(GetLastError 126), even when they import no function from it.
Add libSceUsbd with an Export.cpp that contains only APS5_DUMMY_FUN,
like libkernel_sys, libkernel_web, libSceAgcVsh, libSceAjm and
libSceNgs2. The module now loads, exports no sceUsbd function and
guesses no signature, so a title that does import one still stops
with "FAIL: unresolved ELF import" for that NID.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
IntrusivePtr<T> is three pointers: object, deleter, LibContext. The default
constructors zero them, the copy constructors copy them and take a reference
on the object, operator-> and operator* return the object, and the destructors
drop a reference and hand the object to the deleter when it was the last one.
The last-reference release of a pointer without a deleter needs the object's
destructor and the LibContext memory pool and still throws.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The constructor was a silent stub that left the guest object
uninitialised and c_str threw. The constructor now writes the 0x20-byte
object, c_str returns the buffer or an empty string, and the destructor
throws for a non-null buffer, whose release needs the LibContext
allocator.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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).