materializeRegisterClear read the target's DCC keys with CurrentDccKeys on
every draw into a resident target with metadata: a full AllKeysEqual scan
of the key range (52 KiB for a 2432x1368 RGBA8 target), about 440k scans
per 10 s and 7.2% of the queue 0 worker's CPU time in one level.
ProvedCurrentDccKeys answers CurrentDccKeys from a proof kept with the
resident image (DccRangeProof: the key range, the keys and the write
generation collected before the scan), the rule ProvedClearKeys already
applies to a storage image's own keys: the proof holds while the key range
is unstamped since that generation (CollectWrites first, then
UnchangedSince), and a scan becomes the proof only when it read the bytes
(not the pending store memo) and no recorded work still writes them. Every
key writer stamps the range, so a store, a pending result or another key
range always scans again. The proof is used under GuestMemory::GpuMutex
only, and not with APS5_NO_KEY_FAST_PATH.
agc_driver_recorder_tests: an unchanged range is proved, and a key store
stamp, a CPU write, another range or address, and a pending key fill each
force a scan; the fill's result is proved once its batch finished.
Every small host buffer (size classes below 1 MiB: draw and dispatch data,
copied regions, snapshots) had a VkDeviceMemory of its own. When the pool
missed, the worker paid a vkAllocateMemory and vkMapMemory, and the release
thread a vkUnmapMemory and vkFreeMemory, for each one. The frees also stall
the worker's vkQueueSubmit in the driver.
A host-visible buffer of a size class now gets its own VkBuffer bound into a
slot of a shared block (2 MiB, or 8 slots for the large classes) per memory
type, size class and device-address use, mapped once. Releasing it destroys
only the VkBuffer and returns the slot; a block is freed when it empties and
its slab already keeps an empty one. A slot is used only when the buffer's
alignment and the non-coherent atom divide its size class, so the slot
offset is valid for the bind and Invalidate covers exactly the slot. The
pool still retains released buffers by class as before; only what backs
them changed.
A sample whose T# is chosen in one if/else and whose S# is chosen in a later one has descriptor Phis in two blocks. The web walk required every Phi of the tuple to sit in one block, so it gave up and the plan threw "GetImageResource dword 0 is not a valid runtime value". The walk now splits at the Phis of the latest block in program order, and Phis of earlier blocks pass through each edge unchanged until the walk reaches their block.
The hold no longer polls every millisecond and has no switch to turn it
off. A held submission waits on the driver's condition variable until no
flip is ahead of it, or until queue 0 waits for memory that only the
title can store. The queue 0 worker reports the second case from its
memory and rewind waits, and the worker that reaches a flip wakes the
holders.
agc_driver_flip_tests: a flip held behind a rendering wait is released
when the worker goes on to wait for a label only the title stores, with
no further submission.
A draw whose stages bind a BDA table or a fault buffer builds resources
that need completion work, so noteReusable never keeps them and the shared
resource cache never holds an entry such a draw could hit. Building the
content key and probing the cache for each of them was pure cost. Those
draws now skip both (ShaderResources::NeverReusable) and count as
uncacheable.
A title that paces itself on pending flips assumes what hardware gives it:
the command processor consumes a submitted buffer at once, so a pending flip
is a frame whose commands have been read and that only waits for the display.
The queue worker is slower than that. With three flips pending it can still be
recording the oldest frame while the title rewrites that frame's slot of a
three-deep ring of per-frame memory for the frame after next, and the draws
are then recorded with the later frame's (or half-written) constants.
A submission on queue 0 that carries a flip now waits until the worker has
reached the flip of every earlier one. The wait ends early when queue 0 is
blocked on memory that no running or pending work writes, since only the
title can end that wait. APS5_NO_FLIP_HOLD=1 submits as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017MPYE7P9ob12aAE1H4JtHF
Throw when /proc/self/maps cannot be read or parsed, and when the kernel
places a reservation chunk at another address (MAP_FIXED_NOREPLACE
treated as a hint). Remove the unused PageSize and keep the POSIX-only
headers in the non-Windows block.
An unmapped range inside the guest arena stays a PROT_NONE host
reservation, so host munlock succeeds on it. Check the guest mapping
state first, as msync does, and return ENOMEM for ranges the guest
has not mapped or reserved.
CheckUnmappedArenaRangeStaysReserved uses mmap, PROT_READ, PROT_WRITE,
MAP_FIXED_NOREPLACE and EEXIST directly, so the Windows build fails to compile it.
The probe exists to show that an unmapped arena range still rejects a hostile
MAP_FIXED_NOREPLACE mapping, which is Linux-specific behaviour.
Guard the function and its call site with __linux__. The other tests in the file
compile on both platforms.
The Linux implementation of GuestArenaReset released the range in addition to
resetting the memory, while the Windows implementation only resets the memory and
leaves accounting to the caller. Two call sites already release explicitly after a
reset, so every one of them released the same range twice: sceKernelMunmap through
ArenaForget, and GuestHeap::release through GuestArenaRelease.
The two releases acquire the arena lock separately. Contains is span-based, so the
second release still runs, and Release erases every overlapping entry. A thread that
allocates the range between the two calls therefore has its entry erased, and the arena
hands the same guest address range out twice.
Delete the release from the Linux reset so both platforms restore the documented
contract. Every existing caller already pairs the reset with an explicit release.
The arena constructor was Windows-only, so on Linux Available() was false: the guest heap fell back to the host allocator and direct memory landed wherever the kernel pleased. Titles that require a low virtual address rejected the mapping and re-issued the identical request, which surfaced as a start-up assert inside their own allocator.
Seed the window from the mappings that already exist instead of reserving it up front, because a PROT_NONE reservation of the whole range starves the kernel's own top-down mmap, which picks addresses just below the stack inside that range. Commit, reset and set-protection become portable so the arena owns its used ranges on both platforms, and fixed mappings are checked against the arena's used ranges rather than against EEXIST from a reservation.
The window-placement and no-overwrite checks in guest_memory were compiled only for Windows; they now run on Linux too, and the window check fails without this change.
Fixes#692
Since guest threads get exactly the stack size they request, host code
that a guest thread calls runs on that small stack too. sceAgcCreateShader
reaches Driver::RegisterShader, whose frame alone is about 50 KiB, so a
title whose job threads ask for 48 KiB overflows the stack (#2603).
Each guest thread now gets a fixed 1 MiB of host stack below the range it
asked for. The guest still sees the requested size and address range
through scePthreadAttrGet and sceKernelIsStack; only host calls made from
the thread use the extra room. Running the prepared-shader driver tests on
a limited main-thread stack needs between 128 and 192 KiB for shader
registration, Vulkan setup included, so 1 MiB leaves several times that.
Switching to a separate host stack at each HLE entry point would need a
trampoline on every export that may go deep, handling of stack-passed
arguments and unwinding across the switch, and a list of which exports
qualify. A margin below the guest range covers every host call with no
per-export list.
On Windows the committed size was the requested size, and the margin
below it depended on the executable's default stack reserve; requests at
or above that reserve had none. The commit now includes the same margin.
macOS does not run guest threads.
guest_pthread_stack now also calls host code that uses 256 KiB of stack
below each thread's guest range; without the margin it crashes on the
16 KiB thread.
Fixes#2603
scePthreadCancel and pthread_cancel threw, scePthreadTestcancel did
nothing and the cleanup handler exports did not exist.
A cancel request is now recorded on the thread and acted on at
scePthreadTestcancel, the condition variable waits, scePthreadSem and
sem waits, scePthreadJoin and the sleeps, and under their POSIX names.
A thread blocked in one of the waits is woken; a condition wait takes
its mutex back before the thread exits. The thread exits with
PTHREAD_CANCELED through scePthreadExit, which now runs the handlers
of __pthread_cleanup_push_imp and __pthread_cleanup_pop_imp.
scePthreadSetcancelstate and scePthreadSetcanceltype reject unknown
values, and pthread_setcanceltype is added.
Constants, the handler node layout and the order of events follow
FreeBSD libthr. A sleep is not interrupted, file and socket calls are
not cancellation points and the asynchronous type acts like the
deferred one except at the two set calls (TechnicalDebt). New test:
guest_pthread_cancel.
Astro's Playroom (PPSA01325) calls sceRudpActivate during start-up, where the throwing stub stopped it. The host has no reliable-UDP stack to activate, so the export returns success and does nothing, matching the OK-returning stubs shadPS4 keeps for the whole library. The guest_offline_imports test now checks the success path with and without sceRudpInit, and still checks that the sceRudpGetStatus sibling throws and writes nothing.
Leave the Windows identity fields at their stat values when
GetFileInformationByHandle fails on a handle whose basic information
could be read, instead of failing the whole call.
st_ino was always 0 and st_dev differed between stat (drive number) and
fstat (0), so the usual a.st_dev == b.st_dev && a.st_ino == b.st_ino
identity check gave wrong answers. Read the volume serial, the file index
and the link count from the file handle, which works the same for path and
descriptor lookups.
Fixes#2296
ProsperoStore (PPSA99000) imports kill from libkernel and stops at it on
current main. Its crash reporter resets a crash signal to SIG_DFL and
sends it again with kill(getpid(), signal).
kill follows FreeBSD's kern_kill and killpg1 for the only guest process:
EINVAL for signals outside 0 to 128, signal 0 only checks the target,
and the caller's own pid, 0 and minus its pid deliver through raise.
Any other pid, and -1, which skips the caller, report ESRCH.
The registry records whether a range was mapped or protected with GPU
access (0x10/0x20), and an address-based build leaves out ranges without
it, as the console's GPU page tables do. The main image is registered
without GPU access; the title's own mprotect with GPU bits adds it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The hardware turns anisotropy off under ANISO_OVERRIDE when the T#'s BASE_LEVEL equals its LAST_LEVEL. The view's level count is taken after LAST_LEVEL is clamped to MAX_MIP, so a T# with BASE 0, LAST 3, MAX_MIP 0 counted as single-level here while the hardware keeps anisotropic filtering for it (measured by @adriadam10 on the hardware oracle). The pairing now reads BASE_LEVEL and LAST_LEVEL from word 3 (DescriptorSingleLevel).
The per-draw path gets a test, agc_driver_image_sample_aniso_override: one ANISO_OVERRIDE sampler shared by two images in a dispatch, with single-level, mipmapped, mixed, and LAST_LEVEL > MAX_MIP descriptors, checking which pairings run (with their samples) and which are refused as mixed. With the old view-based check the LAST_LEVEL > MAX_MIP pairing is refused.
applyAnisoOverride reads the APS5_NO_SAMPLER_CACHE switch through the same cached flag as addImageBinding instead of calling getenv per sampler per draw.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
With ANISO_OVERRIDE the texture unit disables anisotropic filtering when the
sampled view has a single level. Each draw now records, per sampler, whether
its paired images are single-level or mipmapped. A sampler with the bit and an
anisotropic filter whose images are all single-level is bound as the same S#
with point or linear filtering in place of the anisotropic filters, taken from
the sampler cache. One sampler descriptor serves every image paired with it,
so a sampler paired with both kinds in one draw still throws. Samplers of
mipmapped images keep their anisotropy, as on the hardware.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Mesa sets ANISO_OVERRIDE (S# word 2, bit 29) on every GFX10+ sampler
unless aniso_single_level is on; it turns anisotropic filtering off for
single-level views. Samplers carrying it were rejected. Accept and
ignore it: host samplers are built from the S# alone and shared between
images, so single-level views keep the sampler's anisotropy. The gap is
recorded in TechnicalDebt.
Co-Authored-By: Claude Opus 5.5 <[email protected]>