Imports were held to a configured budget only. With APS5_HOST_IMPORT_MIB
at 6864 on a 15.4 GB PC, Village's imports reached the NVIDIA driver's
pinning ceiling of about 6.56 GiB, and from there every import was tried
and failed at vkAllocateMemory, 992 times.
Where the device enables VK_EXT_memory_budget, the budget is now also at
most the host heap's reported budget less its other usage and an eighth,
in 64 MiB steps, read at most once a second. A vkAllocateMemory failure
within an eighth of the budget, or the third in a row, lowers the budget
to the live imports, so later imports are refused or evict instead of
failing; an isolated failure further below only marks its range failed,
as before. A remembered refusal stays remembered while the budget does
not grow.
FC 27 stops on Windows with Failed to load module / error 126 because this PRX is absent. Export Initialize and Terminate as no-ops and keep RequestDevices throwing until the buffer layout is known.
Co-authored-by: Cursor <[email protected]>
sceAudioOut2ContextGetQueueLevel left out the grains that
sceAudioOut2ContextAdvance completed and no push had consumed yet. While
pushes filled the 40 ms cushion the level stayed at 0, so a title that
renders as many grains as the level leaves room for and pushes them on
another thread rendered a full queue again after each push. PPSA23226's
Wwise sink does that and stopped once more than 16 completed grains
awaited a push.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An address-based build asks for every registered GPU range whenever it
rebuilds its address space, and at a full budget it was refused each
one again: a title held at its default budget counted 304,394 refusals
over about 12,000 ranges at its title screen.
A request that may not evict (the build's walk, an import when a range
is mapped) now remembers a refused run with the live bytes and the
budget at the refusal. Until live imports drop below those bytes, the
budget changes or one of the run's ranges changes, a request for that
range is answered from memory, still counted as refused and also as
remembered, and a remembered block is skipped, so a block stepped down
for the budget keeps its import across registry changes. A request
that may evict always tries again, and a new import forgets the
refusals of the runs it covers. The shutdown report and the budget
line print how many refusals were remembered.
An import covered one registered range unless adjacent ranges filled a
whole aligned 8 MiB window. A title that maps its GPU heap in 64 KiB
pieces got one import per piece from every requester: Village held
about 27,000 of them at its first scene, enough to fill the budget.
The run of an address is now the largest aligned power-of-two block,
from twice the import alignment up to the window, that holds the
address and that continuing readable ranges fill exactly.
A block over the budget steps down to the largest block that fits what
is left of it, counting what the requester may evict, and nothing is
tried when not even the address's own range fits, so a full budget
costs one refusal per request as before. A block that would replace an
import a build holds, or that fails (a refused Windows alias,
vkAllocateMemory), steps down to the next smaller block. A failed block
is remembered until one of its ranges changes, instead of pinning the
span to single ranges for the session. An address-based build no
longer asks for the ranges inside the block it just imported.
A storage image over 64 adjacent pieces now finds its block import at
write-back instead of importing every piece as a segment. The split
write-back test keeps its two ranges apart with a 64 KiB window.
The ancillary input's sample number (bits 11:8) was the SampleId
builtin in every pixel shader. Vulkan enables sample shading for a
shader that uses SampleId, so a shader that reads the sample field or
the whole ancillary word ran once per sample on a multisampled draw
even when the guest runs it once per pixel (PS_ITER_SAMPLE clear), and
FragCoord, and with it POS_X_FLOAT and POS_Y_FLOAT, moved to the
sample. The sample number is now SampleId only when the request says
the guest iterates the shader, and otherwise 0, the iterated sample
number of a pixel the hardware does not iterate.
The other inputs that depend on the sample follow where the console
evaluates them:
- PERSP_SAMPLE and LINEAR_SAMPLE are interpolated with
InterpolateAtOffset at sample 0's offset in a shader the guest does
not iterate, and in an iterated one at the offset of the host's
SampleId, read from a table of the guest's locations, because Vulkan
leaves plain barycentrics under sample shading at an
implementation-defined position (lavapipe uses the pixel centre).
- PERSP_CENTER and LINEAR_CENTER are interpolated at offset 0 in an
iterated shader.
- POS_X_FLOAT and POS_Y_FLOAT follow SPI_BARYC_CNTL: the centre is
floor(FragCoord) + 0.5 under sample shading, a sample position in a
shader the guest does not iterate is the centre plus sample 0's
offset, and POS_FLOAT_ULC gives the upper-left corner.
The offsets need InterpolationFunction (sampleRateShading) and the
fragment-shader barycentric path; devices without them keep the host's
default positions. The shader disk cache format goes to 39.
Brings the series onto main 4e9a2a871 (#1193, #1201, #1202, #6060,
#6333 and the descriptor word tracking of 682b07eb0).
Resolutions:
- ShaderMemory: the column memo's supplied and spilled blocks now sit
on #1193's page vector, page pool and word masks. Pages are found by
binary search, since a spilled block can add many pages, and a block
placed into a lazily read page fills the rest of the 16-word blocks it
touches, so a later lazy read never overwrites placed words.
- ResourceMaterializer and SharedValues: the selected-record path and
the words index carry availableDwordMask, as the snapshot hash does.
A selected record's dwords past the end of its buffer count as read
zeros, which is what a scalar buffer load returns there, so a key past
the heap still selects a null image without reading the heap.
- DescriptorCache: kept table sets live beside #1201's recycled sets.
- Tests and CMake: both sides' tests are kept.
The draw cache sets each fresh stage capture against the variants it keeps (insertDrawEntry), and
the dispatch cache sets a fresh capture against its front variant, by comparing every captured word.
A 32,768-record table column is 262,144 of them, so each compare went through 1 MiB on each side
even when both captures had copied the column from the same shared block.
A variant now records the words it copied from a block the shader memory shares (SharedWords: where
they sit in the capture and in the block, and a weak reference to the block; consecutive pages of
one block join into one entry). SameCapturedWords takes the words both captures copied from the
same place of the same block as equal, since a block never changes, and compares the words around
them as before. Two blocks with equal words, words read into pages and a released block fall back
to the word compare. The reference is weak, so a kept variant holds no block alive, and a released
block's identity cannot pass to a new one: its control block stays until the last weak reference
goes.
A result memo miss resolves every image table of the snapshot again: ResolveImageTables decodes each
distinct T#, builds the element and poison indices and fills one map record per key, which is most
of a miss for a large table. A miss whose table did not change is common: another buffer, flat SRT
word or user data word changes the result key while the column is the one the column memo shares.
ResolveImageTablesShared keeps the last resolutions and hands one out by reference when the variant
(its id), the reducing state of the direct samplers paired with table images and every column match:
base, size, records, keys, fault, outside, and the column's codes and distinct T#s by identity. The
memo holds those shared values, so a block it compares cannot be freed and reused for other words;
a column read again is a new block and resolves again even when its words are equal.
It keeps at most 4 resolutions per variant and 256 in all within 8 Mi words, counting the columns
their keys keep alive, least recently used first out, under one mutex. A resolution is immutable
once made and is shared between threads read-only. APS5_NO_TABLE_RESOLUTION_REUSE=1 turns it off.
ImageTableReuseCounts gains the resolutions made and the ones shared.
When the write tracker vouched for an unwritten image table column, the capture still copied each of
its words into ShaderMemory's pages (supplyWords) so they would be in the captured regions, and
Regions() then found them again bit by bit. On a 32,768-record column that is a 1 MiB copy per
capture (about 120 us here) and a scan of 262,144 read bits per Regions() call (about 230 us), and
the draw path calls TakeRecentRegions() and Regions() once each per stage.
The memo now keeps a column's words in one immutable block (SrtGuestWords), and supplyWords hands
that block over. ShaderMemory keeps a reference and reports its runs page by page, the regions a
read of the same words reports, merged in address order with the pages and the initial regions; a
second stage that supplies the same block only marks it recent again. The words reach pages only
when they must: a later read in the same ShaderMemory that touches them first moves the whole block
into its pages (spilled), so a word is never reported twice, and a block that overlaps pages already
fetched or another block's runs is copied as before. A spilled block stays referenced until the
ShaderMemory goes, so the regions already taken from it stay valid. ShaderMemory::Supplies() counts
the words shared, copied and spilled.
A capture that reused a memoized image table column still copied it: each record's state and code
and the distinct T#s went from the memo into the snapshot, the snapshot rebuilt its codes from the
states, and the column's per-record vectors were allocated before the memo was even looked up. Every
Recompile then hashed the codes and the distinct T#s word by word for the result memo. On a
32,768-record column of distinct T#s that was about 155 us of copying per capture and 410 us of
hashing per Recompile here, for a column nothing wrote.
A column is now built once into immutable shared values (SharedValues: its codes, its distinct T#s
and its read ranges) and the memo keeps that column. A capture that reuses it takes a reference, so
two captures of an unchanged table share one column and copy nothing, and the per-record vectors
are allocated only when the column is walked. The codes index the column's own distinct T#s
(ImageTableColumnSnapshot::words) instead of a list shared by the shader's columns, so every column
of a shader is shared whole, not only the first. A column read again after a write is a new one:
the snapshots holding the old one keep it unchanged. The result memo hashes each shared value once
and keeps the hash in it; equal contents hash equal whether they are shared or not.
ImageTableReuseCounts gains the reuses that shared a column and the columns built.
The entry now sits with the other sceKernelMprotect entries for
DirectMemory.cpp, before the Windows guest module image protection one,
instead of after the setcontext entry, where #5918 adds its own.
A draw with PA_SC_CONSERVATIVE_RASTERIZATION_CNTL 0x6001 and
SPI_PS_INPUT_ENA.PERSP_CENTROID or LINEAR_CENTROID threw in DecodeState,
whatever its rasterized sample count. With PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES
0 the single rasterized sample is the pixel centre, which is where the
recompiler already puts the centroid barycentrics, so such a draw now
decodes to overestimation. More rasterized samples, and a single-sample
draw raised to a multisampled color target, still throw.
Extends agc_driver_graphics and records the inference in the driver's
technical debt.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01XJ3RTcWpqBBdVE8QRtKxJD
The entry described the NID as sourced from the community firmware dump
alongside the signature, but nid_patcher derives the NID from the export
name itself; only the signature is unverified. Rewrite the entry to match
the house style used by libSceHttp2.md and libSceContentDelete.md: NID
inline, the importing title named, and a pinned link to the one external
source instead of an unlinked claim.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vb2m1o8ZDHARkX6FTP9xuJ
Until Dawn (CUSA02636) bundles a libSceFios2.prx that imports two
libSceDiscMap functions, sceDiscMapIsRequestOnHDD (lbQKqsERhtE) and
ioKMruft1ek, and no host library exported them.
Both now return SCE_DISC_MAP_ERROR_NO_BITMAP_INFO (0x81100004) and
write nothing, as shadPS4 and fpPS4 do. That is the error the console
library returns while it has no disc bitmap loaded.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HotSpot makes its stack guard zones with an RW protect of the lowest
pages of the thread's stack, then PROT_NONE, as it commits them on
native Windows (os::pd_create_stack_guard_pages, then os::guard_memory).
Below a host stack's guard page such a protect was only recorded, so the
pages stayed reserved: they could not be written after the RW protect,
and the zones never held.
A protect of reserved pages that starts at the bottom of the reserve and
stops short of the guard page now commits and protects them. The guard
page and the reserve above the range stay as they are, so the stack still
grows through the guard down to it, and the stack limit in the TEB still
follows the committed stack.
Windows moves the guard down by committing the pages below it as
read/write guard pages, committed or not. The zones therefore hold until
the stack is within one guard region of them (three pages on GitHub's
runners), and the pages the guard reaches then come in read/write. The
test checks both, and the debt entry says so.
Committing the reserved pages of a host thread's stack before protecting
it, as the stack branch did for a range the arena doesn't own, also
cleared the guard page's PAGE_GUARD. The thread was left with no guard
and no reserve: an overflow ran into whatever lay below the stack instead
of raising a stack overflow, and the stack limit in the thread's TEB,
which Windows moves only when the guard page is hit, stopped following
the stack. A range that began inside the reserve also left the reserved
pages below it with no guard above them, so the stack could no longer
grow into them.
The stack branch now leaves the guard page and the reserved pages below
it as they are, and commits and protects only what lies above them;
reserved pages above the guard are still committed first. The protection
is recorded for the whole range, and a range of only reserved and guard
pages changes nothing on the host. The test protects the main thread's
whole stack and a reserved thread's whole stack and upper part, then
checks that the guard page is still there and that the stack grows
through it, also after a PROT_NONE protect of its lowest page. The debt
file records that those pages come in read/write as the stack grows.
A draw whose sample locations vary between the pixels of the guest's
2x2 pattern threw "sample locations varying between pixels require a
2x2 sample location grid for the draw's sample count" on a device
whose maxSampleLocationGridSize for that sample count is not a
multiple of 2x2. An RTX 3070 Laptop GPU reports 1x1 for every count,
so Resident Evil Village's 2x checkerboard pattern stopped there
before its title.
The pipeline now decides the grid when it is created: such a pattern
is drawn through a 1x1 grid with pixel (0,0)'s locations, the first
registers of the pattern and the pixel the guest's grid is anchored
at, and the first such pipeline of each sample count logs it. The
render pass begin takes the pipeline's grid, so a sample-location-
compatible depth image is begun with the locations it is drawn with.
Devices with the grid keep it, and uniform patterns are unchanged.
On macOS the RLIMIT_NOFILE soft limit is not a bound on the descriptor table: setrlimit accepts soft limits above kern.maxfilesperproc, RLIM_INFINITY included, and getrlimit reports what was set, so returning the guest soft limit alone could report up to INT_MAX for a table of about 122880 descriptors, and a closefrom-style loop up to that value would run billions of iterations instead of about 122k. Return the smaller of the guest soft limit and the host's ::getdtablesize(), which is the FreeBSD sys_getdtablesize result. The guest test derives its expectation from the host call instead of the implementation's formula, and the debt entry no longer claims the limit is raised when libkernel loads outside Linux. Reported by jotitadev in review of the getdtablesize implementation.
A static, read-only image slot whose T# has TYPE 1 is now bound as a null
image, like TYPE 0 since #5473 and #4779. On gfx1010, image_sample_lz,
image_sample_b, image_load and image_get_resinfo through a format-22
TYPE 1 T# return zeros without a fault, and gfx1035 reads it as zeros
too. Written, atomic and table slots still throw, and the reserved
types 2-7 stay refused.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Morrowind imports fchown and stops at it once rtprio_thread and the pthread concurrency controls are in. Accept the guest's own owner and group (0) or unchanged (-1), fail an invalid descriptor with EBADF, and throw for another owner or group, since ownership is not modelled.