68 KiB
68 KiB
libSceAgcDriver technical debt
Unknown function info
- sceAgcDriverSetSubmitValidationMode, sceAgcDriverGetSubmitValidationMode, sceAgcDriverSetSubmitValidationConfig, sceAgcDriverGetSubmitValidationConfig, sceAgcDriverSetValidationErrorOutputFrequency, sceAgcDriverGetShaderDebuggingStatus (libSceAgcDriver) - the five validation calls return
0x8A6C1000without reading their arguments or writing caller memory, andsceAgcDriverGetShaderDebuggingStatusreturns 1, as OpenAGC reads them from Sony's libSceAgcDriver.sprx: each validation export ismov eax, 0x8a6c1000; retin the copy Dragon Quest VII Reimagined bundles (0x7bb0-0x7bf0), and the debugging status is 1 in the firmware 5.50 library (0x6740). The parameters (a 32-bit mode or frequency, a mode output, a configuration pointer) follow OpenAGC and are never read. Whether later retail firmware enables submit validation or shader debugging is unverified; no title in this repository is known to call them - Indices far past every vertex fetch (libSceAgcDriver) - when a draw's highest index is at least 64Ki vertices past the largest per-vertex record count, every index past that count is moved to it in a copy of the indices, so the zero-padded vertex copies are not sized to the original index (PPSA04159 draws a list of
0xffffffffwith primitive restart off, which asked for 160 GiB). Each attribute fetched by vertex index reads zeros there as at the original index (out-of-bounds select 0 or 1); a draw with another out-of-bounds select keeps its indices. The vertex index the shader sees changes for the moved indices, which a shader that reads it directly would notice - Depth clear draws (AGC driver) - a draw with
DB_RENDER_CONTROL.DEPTH_CLEAR_ENABLEis taken to writeDB_DEPTH_CLEARto every pixel it covers whatever the depth test, depth bounds and depth bias say: it is drawn with an always-passing depth write and the viewport depth range pinned to the clear value. The draw is rasterized as submitted, so its coverage is the draw's own. Whether the DB still applies the depth test or HiZ to such a draw is not measured - Alpha-to-coverage (AGC driver) - only
DB_ALPHA_TO_MASK = 0xaa01is accepted: the enable bit with the four quad offsets at 2 and noOFFSET_ROUND, the undithered setting radeonsi programs. Draws are single-sampled, so the pixel shader writesSampleMask0 when its MRT0 alpha is below one half or NaN and all ones otherwise, and runs with late fragment tests. That rule was measured on a Radeon 660M (gfx1035, RADV withRADV_DEBUG=noatocdithering, which programs the same register) for 0, -0, 0.25, the two floats below one half, one half, the two above, 0.75, 1, 2, -1, both infinities and both NaNs, and is pinned in AlphaToCoverage.cpp; it is not measured on gfx1013. Vulkan'salphaToCoverageEnableis not used: the NVIDIA driver 615.78.08 dithers single-sample coverage (an alpha of 0.25 writes half the pixels of an RTX 3050 Laptop target). Other offsets,OFFSET_ROUND, a sample-mask export, and an MRT0 that is missing, swizzled, packed or exported without a floating-point alpha throw - MSAA_ENABLE on a single-sample target (AGC driver) -
PA_SC_MODE_CNTL_0.MSAA_ENABLEwithPA_SC_AA_CONFIG.MSAA_NUM_SAMPLES = 0is drawn like the same state without the bit when sample 0 of the fourPA_SC_AA_SAMPLE_LOCS_PIXEL_*_0registers is at the pixel center and the draw rasterizes filled triangles with alpha-to-coverage and conservative rasterization off. RADV programs this state for one-sample custom sample locations on GFX10 and later, where the bit makes the rasterizer use the programmed location of sample 0 (061bc615); that the bit changes nothing else for such a draw is inferred from that and is not measured on hardware. Other locations of sample 0, absent location registers, points, lines, rectangles, alpha-to-coverage and conservative rasterization throw - sceAgcDriverGetEqContextId (libSceAgcDriver) - returns
ident: an end-of-pipe interrupt is delivered only to the registration whose id equals the raising queue, soidentis that context's id (titles register 0 or 1 for graphics and0x20 + 8 * pipe + queuefor compute). The console's value is not known; KytyPS5 returns the event'sdata, which it fills with the interrupt's context id, whereasdatahere is the EV_CLEAR trigger count. Other event filters throw - User data registers (AGC driver) - a
SPI_SHADER_USER_DATA_*orCOMPUTE_USER_DATA_*register the title never wrote reads as 0 when a stage's RSRC2 count covers it, as Kyty (zeroedUserSgprInfo) and SharpEmu (missing registers read 0) do, and as AMD PAL and Mesa initialize them; the console's value is not confirmed - SET_PREDICATION (AGC driver) - boolean predication follows the AMD PM4 encoding Mesa uses: operation 3 reads 64 bits and 4 reads 32 bits, and bit 8 runs predicated packets when the value is non-zero (clear: when it is zero); the bit 12 hint is ignored. The address must have its low 4 bits clear, as
sceAgcDcbSetPredicationwrites it. Query predication (Z-pass, primitive count), a predicated COND_EXEC, predicated command buffer chains and flips in predicated command buffers throw - NGG vertex programs on the mesh path (AGC driver) - an unregistered vertex program with
PRIMGEN_ENthat uses LDS ors_barrierruns on the geometry (mesh) path. Each input primitive is assumed to yield at most one output primitive, the subgroup is bounded byGE_MAX_OUTPUT_PER_SUBGROUPas a vertex budget, and the GS/ES VGPR count checks are skipped, because the merged inputs keep their positions. Taken from the code Mesa's ACO emits for NGG culling and from PPSA59322 and PPSA99010 draws, not measured on hardware. Registered programs keep the vertex path - Strided buffer overhang (libSceAgcDriver) - a strided buffer descriptor that ends less than one stride past the end of the readable registered allocation holding its base is bound only up to that allocation's end, even when another readable allocation starts right there. This assumes the bytes past the end are the rounded-up
num_recordsof the last record (RADV rounds it up), which no fetch inside the buffer reads; a guest that really keeps a vertex split across two adjacent allocations would read zeros for the cut tail. Taken from the RetroArch Dolphin core case in #2912, not measured on hardware. - Zero-stride vertex fetch range (libSceAgcDriver) - for a stride-0 V# with OOB_SELECT 2 the whole fetch reads memory unless NUM_RECORDS is 0; with OOB_SELECT 3 it reads zero when NUM_RECORDS is 0 and throws when the fetch only partly fits. Rules taken from the GFX10 OOB_SELECT table in Mesa ac_descriptors.c, not measured on hardware. Whether OOB_SELECT 3 checks each dword of a multi-dword fetch separately is unknown. OOB_SELECT 0 and 1 keep the structured record checks.
- Resource registration (libSceAgcDriver) - registration always fails with
RESOURCE_REGISTRATION_UNAVAILABLE, so the owner and resource queries,sceAgcDriverSetResourceUserData,sceAgcDriverFindResourcesPublicandsceAgcDriverUnregisterAllResourcesForOwnerreturn the same error without reading their arguments. Their parameter lists follow the libSceGnmDriver counterparts and are unverified.sceAgcDriverGetResourceRegistrationMaxNameLengthwrites 0xfc and returns 0, as prosper does from a title's registration configuration; theuint32_t*parameter matches the SDK wrappersce::Agc::ResourceRegistration::getMaxNameLength(unsigned int*). sharpemu writes 256 and KytyPS5 returnsRESOURCE_REGISTRATION_UNAVAILABLE, so the value is unverified; a null output throws - sceAgcDriverRegisterMultipleResources uses the existing unavailable-registration state and leaves caller memory untouched. Its four arguments (uint32 handle output, uint32 owner, descriptor pointer, uint32 count) follow the original PPSA34502 / 01.010.000 codzm call. The caller builds 40-byte descriptors and explicitly accepts 0x8A6C9018 with an invalid output handle. Enabled registration and console argument-validation precedence are unverified.
- Shader headers (libSceAgcDriver) - registration and ABI resolution read fixed header fields through aligned copies, so the header pointer itself may have any alignment. PPSA26344 passes headers 4 and 1 bytes past an 8-byte boundary, and PPSA01317 passes 4-byte-aligned headers to ABI resolution; console alignment requirements are unknown
- NGG vertex workgroups (libSceAgcDriver) - type-2 PRIMGEN programs using DS instructions or
s_barrierare prepared with the draw's routing and primitive registers on the existing mesh path. The workgroup requirement follows Mesa's NGG lowering; the mesh path supplies LDS and two guest lanes per host invocation for wave64 on a 32-lane host subgroup. Console routing has not been measured. Passthrough and tessellation keep their existing paths; unsupported mesh assembly and subgroup budgets throw. - Shader header copies (libSceAgcDriver) - a header bound for a draw or dispatch at another address than the registered one is accepted when its fixed fields match the registered header except
user_data; the registered header's metadata is used. PPSA21567 binds such a copy of a vertex shader header withuser_dataset to null. Whether the console reads the copy'suser_datais unknown - HtileFillCovers (libSceAgcDriver) - a fill is taken to cover a depth surface's whole HTILE when it spans 32 KiB per 1024x512 pixel block from DB_HTILE_DATA_BASE, the pipe-aligned SW_64KB_Z_X metadata extent AMD PAL's gfx10 addrlib gives for GB_ADDR_CONFIG 0x00100044 (1-8 samples, D16/D32); other metadata layouts are not decoded. A partial fill is ignored (the surface keeps its contents); not measured on the console.
- Depth fast clear through HTILE (libSceAgcDriver) - a surface cleared by an HTILE fill is cleared to DB_DEPTH_CLEAR, also when it is next sampled. For a depth-only HTILE the clear word also carries a value (radv writes
minz = maxz = round(depth * 0x3fff),HTILE_Z_CLEAR_REGin Mesa'ssrc/amd/common/ac_descriptors.h, and allows TC-compatible fast clears only to 0.0 or 1.0); which of the two the texture unit returns for a cleared tile was not measured. - Depth-only and stencil HTILE fill (libSceAgcDriver) - in a whole-HTILE fill, ZMask (bits 0-3) 0 clears the depth and SMem (bits 8-9) 0 clears the stencil, while SMem 3 leaves the stencil alone (
HtileFillClears); other SMem values are taken to leave it alone too. The bit meanings follow the gfx10 HTILE layout as the code reads it, not a measurement;agc_driver_depth_fast_clear_submitpins them for the paths the Driver takes. - HTILE address of a depth array slice (libSceAgcDriver) -
decodeDepthadds theDB_DEPTH_VIEWslice offset to the Z and stencil addresses but not tohtileAddress, so every slice of a layered depth target shares slice 0's HTILE address and a fill of slice 0's HTILE marks all slices cleared. Astro's Playroom fills the HTILE of each of its three shadow cascades 0x40000 apart (0x24000 bytes of tiles each) back to back before drawing, so the result is still right there; a title that clears one slice between draws to another would lose depth. The per-slice HTILE stride is not derived yet. - sceAgcDriverGetHsOffchipParam (libSceAgcDriver, NID
r28hEh6cNH0) - declared as a throwing stub. The twouint16_toutput pointers and integer return type follow ps5-vulkan, which reads the pair before it binds a tessellation factor ring. The meaning of the two values, their initial state and how they relate tosceAgcDriverSetHsOffchipParam, which keeps nothing here, are unknown; the stub does not write caller memory. PPSA99011 imports it
Functional
- Reused depth storage: a storage view with another extent or a format that cannot hold the cached depth plane uses guest memory. Depth rendering is not written back to those guest bytes; this admission rule does not prove allocation reuse or preserve a depth writer through an incompatible view. A 2D storage view, or a one-layer 2D array view of layer 0, of level 0 with the plane's extent and format (32-bit float or the 32-bit depth-bits layout for the depth plane, 16-bit unorm for D16, 8-bit uint for the stencil plane) is a storage image that is filled from the depth image before every use and, when the shader writes it, copied back after the work: a whole-plane copy through a staging buffer each way. A written view is also left pending like any other storage image, so the plane's content, drawn depth included, reaches guest memory on the next flush of that range. A matching view still throws when it has another dimension (2D array of more than one layer or from another layer, cube) or level, when the surface is multisampled, when it names the depth plane of a 16-bit depth with stencil surface the device stores in a wider format, and when it was written after another view of the same plane (another format, tile mode, address or dimension) replaced it, as two such views bound in one dispatch are. Stored depth values outside 0..1 are not clamped, and Vulkan leaves that copy undefined without
VK_EXT_depth_range_unrestricted.agc_driver_depth_storage_dispatchpins these paths; none is measured on the console. - Depth plane copies (libSceAgcDriver): a depth plane sampled as a 2D array or a cube, or a D16 plane read as
R16_UINT, is copied into an image of its own (DepthPlaneCopy), kept per base address, extent, format and slice count; a slice comes from the live depth surface when one is bound at the slice's address and from guest memory (detiled) otherwise. A draw or dispatch that binds the plane copies again only the slices that changed and records nothing when none did: a surface slice when its image or write serial differs from the one copied (the serial is bumped when the surface is created, when a draw binds it as its depth target, when a pending fast clear is applied and when a written storage view of the plane is copied back), a guest-memory slice when its bytes were written since they were last read (GuestMemory::UnchangedSince), a snapshot write overlaps them, or its address, size or tile mode changed. The surface of the latest depth draw is copied at every use until another depth target is bound, since that draw may still be recorded after the copy. A slice's address is the base plus a whole 64 KiB-block depth slice per layer (DepthSliceBytes), and the cube face order is the array order; both are inferred from the titles' use, not measured on hardware. Planes with mips, a base slice other than 0, a cube with other than 1 or 6 slices, and other views of the planes still throw, and so do these copies of a 16-bit plane with stencil that the device stores as D24 or D32 (16-bit depth with stencil, below). - SW_64KB_D_X color targets (libSceAgcDriver) use the existing texture block/mip geometry and equations from AMD addrlib GFX10 non-RB+ for 16 pipes and 256-byte interleave. The address-reference tests use legal pipe/bank XOR bits for that configuration. Raw column bits outside the source configuration's pipe/bank mask 0xcf00 throw; their console meaning is unresolved. Console address equations and render results have not been measured.
- Per-face depth bias (libSceAgcDriver): Vulkan has one depth bias per draw, so a draw that rasterizes both faces with different front and back bias (PA_SU_SC_MODE_CNTL, PA_SU_POLY_OFFSET_FRONT/BACK_*) is recorded twice, front faces with the back faces culled and then back faces with the front faces culled. The vertex stages run twice, so a vertex shader that writes memory writes twice, and all front faces land before all back faces instead of in primitive order, which differs only for blending or stencil updates that depend on the order.
- Partial rect-list primitives (libSceAgcDriver): a rect-list draw whose vertex or index count is not a multiple of 3 draws its complete rectangles and leaves out the 1 or 2 vertices after them, because rect lists are drawn as Vulkan patch lists of 3 control points, which generate ⌊vertexCount/3⌋ patches. What the console draws for the incomplete rectangle is not measured; Palworld (PPSA23126) draws 4 vertices × 9 instances as a rect list when its character creation opens.
- Separate alpha blend into 8_8 SWAP_ALT targets (libSceAgcDriver): the second channel of a two-channel target with
COMP_SWAP1 holds the alpha export, but Vulkan blends it with the color equation of the R8G8 attachment, so a draw whose alpha equation differs from its color equation is recorded twice, the first channel with the color equation and the second with the alpha factors and operation moved into the color equation. The vertex stages run twice, so a vertex shader that writes memory writes twice, and occlusion counts include both passes. Draws that write depth or stencil, pixel shaders that write memory and factors of the destination alpha throw. That the hardware blends the second channel with the alpha equation is taken from Mesa'ssi_translate_colorswapand radeonsi's use of it for luminance-alpha formats, not measured on hardware. - Unexported pixel inputs (libSceAgcDriver): a pixel input whose
SPI_PS_INPUT_CNTLparameter the previous stage does not export is programmed as a default-value input (OFFSETbit 5,DEFAULT_VAL0, FP16 high half too) and reads (0, 0, 0, 0), as radeonsi'sSI_PS_INPUT_CNTL_UNUSEDdoes and as rect-list draws already do. The console's value for an unwritten parameter cache entry is not measured on hardware. - Compute LDS beyond the device's shared-memory limit (libSceAgcDriver) lives in a coherent storage buffer with one slice of
LDS bytes / 4 + 1dwords per workgroup (the last dword is the atomic lock), shared by all such dispatches and grown in 1 MiB steps up tomaxStorageBufferRange; a dispatch whose slices do not fit throws. It is slower than shared memory and its cost was not measured. An indirect dispatch of such a program always reads its group counts on the CPU. Its results are pinned against the in-limit semantics inagc_driver_lds_beyond_device_limit, not measured on hardware. - AGC buffer allocations retry host/device out-of-memory once after reclaiming idle pool buffers and fully unused slab blocks. Live buffers and blocks with live slots are retained. Live images and other retained resources have no global memory bound; allocation failure still throws when reclaim cannot satisfy the request.
- Graphics pipeline libraries (libSceAgcDriver): a pipeline is fast-linked at the draw and relinked with
VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXTon a background thread, and binds the optimized one from the next draw on. Draws recorded before that use the fast link, whose vertex stage keeps the outputs the fragment stage does not read: on RADV (Radeon 660M, Mesa 26.2.4) 346 of 483 Astro's Playroom vertex shaders paired with a fragment stage that reads none of them used more VGPRs (median 8 more, up to 2.5 times), and on an RTX 4090 13 of 15 PPSA36082 vertex shaders up to 68 instead of 38 (#2242). The optimized link compiles to the same code as a whole pipeline - Prebuilt pipeline libraries (libSceAgcDriver): each registered vertex or pixel shader in registration form gets its pre-rasterization or fragment-shader library built on one background thread, with a fixed state the draw may not use: triangle list, counter-clockwise front face, no depth clamp, depth bias or conservative rasterization, first provoking vertex, one sample. Libraries are kept until the device is torn down; on Astro's Playroom 1948 of them added 0.44 GB of peak memory on RADV.
APS5_THREAD_PRIORITY=rt(libSceAgcDriver) lowers the process's hardRLIMIT_RTTIMEwhen it is aboveAPS5_THREAD_RTTIME_USor rtkit'sRTTimeUSecMax, because rtkit (verify_process_rttime) reads the hard limit from/proc/<pid>/limitsand refuses real-time scheduling when it is higher. An unprivileged process cannot raise the hard limit again, so it stays lowered for the process and its threads even when rtkit then refuses the request (burst limit, user or start-time checks) and the thread throws. rtkit'sMaxRealtimePriorityandRTTimeUSecMaxare read first, so an unreachable rtkit or a priority above its maximum leaves the limit untouched. The lowered limit only matters for threads that later run under a real-time policy- On a device without VK_EXT_robustness2's
nullDescriptor, such as MoltenVK, a null image descriptor in a typed heap is a placeholder image of that heap's type, 1×1 and cleared to zero, which every null descriptor of the heap shares. Reads give zeros, asnullDescriptorallows, but stores and atomics to it are kept, so a later read of a null storage image can return them, and a depth comparison compares against depth 0. A heap whose placeholder the device cannot create, for example 1D or 3D depth, 64-bit atomics without 64-bit image atomics, or multisampled storage withoutshaderStorageImageMultisample, throws when a null descriptor reaches it. - Draw input snapshots (libSceAgcDriver):
PrepareDrawBindingscaps read-only input buffer snapshots at 64 MiB (overridable viaAPS5_DRAW_SNAPSHOT_MAX_MIB). Larger inputs (streaming heaps, bindless pools) or draws exceeding the cumulative budget skip the per-draw snapshot and bind in place, avoiding multi-gigabyte CPU copy stalls (such as Ratchet & Clank: Rift Apart PPSA01474 stalling at frame ~380 copying 36 GB every 5 s). If the title rewrites those large ranges on the CPU before GPU completion, the in-place binding observes the modification, as verified bydrawSnapshotCapTests. - Pending storage-image results (libSceAgcDriver) are stored when something reads their memory, not when the GPU work that wrote them completes. A title that waits for that work, frees the image and reuses its memory can have the new data overwritten by the late store (#1970).
APS5_STORE_AT_FLIP=1stores every pending result at each flip; storing them at the title's GPU waits (labels, fences) is unimplemented. - Windows host-pointer imports of the guest mapping itself are compared instead of write-watched: a driver can mark imported pages dirty after the synchronous import window. Shared memory imported through a separate writable alias retains protection-based tracking on the guest views; driver access to the import alias does not disarm those views. GuestMemory excludes every overlapping 64 KiB tracker block while its backing is retained. A full block replaced by fresh private backing regains tracking and gets a new generation; protection changes, partial replacements and shared remappings do not restore it. This preserves CPU edits through byte comparisons but disables snapshot reuse and unit shadows for excluded blocks; restoring tracking on import retirement without replacing the backing and measuring the comparison cost remain unimplemented.
- Pages under an import window (libSceAgcDriver) whose write protection was resolved while a host import that leaves its range watched was made are compared with the bytes they held before the import and count as stored over only when those differ. A CPU store of the bytes already there within the window cannot be told apart from an import that resolves the write protection without storing, as the NVIDIA driver (610.57.04) does for every imported page, so such a store does not win over pending results. Where host-pointer and dma-buf imports differ in whether they stay watched, a failed host-pointer import and the dma-buf import after it are two windows, and the pages the failed one resolved count as stored over.
- Unnormalized S# coordinates (libSceAgcDriver) -
FORCE_UNNORMALIZEDuses a VulkanunnormalizedCoordinatessampler, so the S# LOD range, LOD bias and mip filter are not applied, which is exact only on a one-level view. - An unnormalized S# (libSceAgcDriver) throws with unequal minification and magnification filters, anisotropy,
TRUNC_COORD,MC_COORD_TRUNCor X/Y clamping other than last-texel or border. - Background link-time-optimized relink of library pipelines (libSceAgcDriver) is skipped on every GPU under NVIDIA's proprietary Vulkan driver (
VK_DRIVER_ID_NVIDIA_PROPRIETARY), so those pipelines keep their fast-linked form. The optimized link loses the device when bound on an RTX 3080 (Ampere, #2866) and an RTX 4060 (Ada, #3434); an RTX 5060 and an RTX 5080 (Blackwell) on the same driver do not, so the scope is wider than the evidence. No standard Vulkan query tells the architecture apart, and the driver version that fixes it is unknown.APS5_NO_PIPELINE_LTOdisables the relink on every driver. - 8_SRGB and 8_8_SRGB textures (libSceAgcDriver) that the device samples as R8_SRGB or R8G8_SRGB use its own decode, which need not match gfx1035: NVIDIA's R8_SRGB is one bf16 step off in 29 of 256 codes.
- WAIT_REG_MEM reports a diagnostic after
APS5_GPU_WAIT_TIMEOUT_MSwithout treating the timeout as completion. An unsatisfied wait keeps polling and checking driver failures; a missing producer can therefore leave its queue waiting indefinitely. Existing recorded-label shortcuts remain in use. - COND_EXEC reads its condition after the earlier packets of its queue complete. Without PFP_SYNC_ME the console's graphics PFP reads it ahead of the ME, so a title that races the read sees a later value here.
- A flip inside a COND_EXEC range throws: the flip is reserved with the video output when the buffer is submitted, and how the console withdraws a skipped one is unknown.
- A COND_EXEC range must hold whole packets and end inside its command buffer. A chained INDIRECT_BUFFER or a REWIND inside a range throws, as what the CP does with the skipped dwords then is unknown.
- A COND_INDIRECT_BUFFER (
sceAgcCbBranch) runs only with the always-pass compare function; a comparison or a predicated packet throws. Modes 1 and 2 run the first buffer, assumed to be the if-then and if-then-else modes of AMD's PM4 packet. sceAgcCbBranch does not check the compare address of an always-pass branch or the address of an empty buffer, as PPSA14632 passes an uninitialised compare address and a null else buffer of 0 dwords. - A label or store (
RELEASE_MEM,WRITE_DATA,DMA_DATA) into guest memory the device has no host import of waits for all recorded work and is then written by the CPU, so the queue worker stalls once per such label. The console's CP stores it in stream order without that stall. - WRITE_DATA with DST_SEL 3 (libSceAgcDriver) writes guest memory like DST_SEL 5. AMD's GFX10 header names 3 the GDS, but Killzone: Liberation (PPSA11142) writes its frame fence with
sceAgcDcbWriteData(dst 6, cache policy 3, write confirm), which the SPRX-confirmed encoding (OpenAGC) turns into DST_SEL 3 with a 40-bit label address in the title's own memory thatRELEASE_MEMandWAIT_REG_MEMthen use; the console must write that label or the title would wait on it forever. Whether the console routes it through the GDS or a cache is unknown - SET_*_REG_INDIRECT (libSceAgcDriver) reads its register list when the command buffer is submitted, with the packets. The console's graphics PFP reads it when it reaches the packet, so a list written after the submission (by the CPU, or by GPU work ahead of the packet behind PFP_SYNC_ME) is read here before that write.
- The shader float mode (
FLOAT_MODE,DX10_CLAMP,IEEE_MODEandFP16_OVFLofCOMPUTE_PGM_RSRC1andSPI_SHADER_PGM_RSRC1_PS/GS/HS, bit positions from Mesa'sgfx10.jsonandgfx103.json) is decoded intoGuestContext::floatModeand is part of the shader cache keys and the serialized request (#981).IEEE_MODEis read byv_min_f32,v_max_f32,v_min3_f32,v_max3_f32andv_med3_f32, their f16 forms,v_min_f64andv_max_f64,IEEE_MODEand theFLOAT_MODEoutput denormal bits by the output modifier,DX10_CLAMPby the clamp modifier,FP16_OVFLby every instruction with an f16 result (see its entry), and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous f32 lowering, which matches IEEE=0. The f16 forms choose their NaN results on the bits: a NaN operand returns the other operand unchanged (src0 when both are NaN), and with IEEE=1 a signaling NaN operand returns that NaN quieted, in the same order as f32.v_min_f64andv_max_f64quiet a signaling NaN operand the same way with IEEE=1; with IEEE=0 they return src0 unchanged when src1 is NaN, otherwise src1 when src0 is NaN. Both were measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f32 denormals kept and flushed and f16/f64 denormals kept; RDNA2 has no f64 min3, max3 or med3. Unknown modes follow IEEE=0.v_min_f16andv_max_f16flush each denormal input to its signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3 preserve inputs, even when output flushing is enabled. Measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1, including signed zeros, NaNs and neg/abs modifiers. Unknown modes keep f16 denormals. With f16/f64 denormals flushed the hardware also flushes denormal inputs of f64 min/max, which those lowerings still do not. NaN quieting in f16 arithmetic (the unary instructions and the f32 arithmetic follow it, see below) andFLOAT_MODE(f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header'ssh_registers, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none. - A WAIT_REG_MEM (libSceAgcDriver) that a compute queue passes on another queue's recorded label store, before the GPU has executed it, is landed before the queue's next dispatch, draw, COND_EXEC or predicated packet reads guest memory: the worker polls until memory satisfies the wait or no unfinished batch writes the label. The graphics queue (queue 0) is exempt, as landing its waits costs PPSA01341 about a quarter of its frame rate; a title that fills memory a graphics command buffer reads after it submits the buffer behind such a wait reads it early there.
APS5_LAND_WAITS_ALL_QUEUES=1lands the graphics queue's waits too. - The dispatches that follow a compute queue's wait (libSceAgcDriver), up to the next wait that memory does not satisfy, a packet that writes guest memory other than a label, or a conditional packet, are resolved (memory captured, shader recompiled) when the wait passes, as the CP reads them right after it; the graphics queue is exempt (
APS5_GROUP_CAPTURE_ALL_QUEUES=1includes it). The device still reads guest memory when the dispatch is issued; when that memory no longer matches the capture, the dispatch is resolved again from current memory. - PA_CL_CLIP_CNTL (libSceAgcDriver) -
DX_LINEAR_ATTR_CLIP_ENA(bit 24) clear is drawn like bit 24 set, with Vulkan's clipping ofNoPerspectiveoutputs; what a clear bit changes on the console is undocumented. - 10_11_11 unorm color targets (libSceAgcDriver) render into R32_UINT attachments: the pixel shader packs each export, rounded to nearest even like format-30 image stores, which is assumed to match the CB's conversion.
- PA_SU_SC_MODE_CNTL.PROVOKING_VTX_LAST (libSceAgcDriver) uses Vulkan's last-vertex mode for native primitives. Bit 19 follows opengnm's
flatshade_firstmapping; vertex selection follows Vulkan, without console measurements. Last mode throws withoutVK_EXT_provoking_vertex, for generated rectangle, mesh or tessellation primitives, explicit per-vertex flat inputs, and triangle strips with per-vertex inputs, whose ordering depends on the device. A FLAT_SHADE input without pass-through that is read throughv_interp_mov_f32P10 and not P20 returns the provoking vertex's value, like P0. ps5-opengl reads every flat input that way, and its driver passes the GL 4.6 CTS on a PS5 (#1413). The lane itself was not measured on the console, so with the first provoking vertex such a shader throws. - Dual-source blending (libSceAgcDriver) takes the MRT1 export as the second source of the MRT0 blend, as RADV and radeonsi program it; what the CB does with other export or target layouts is unknown, so they throw.
- DB_SHADER_CONTROL.CONSERVATIVE_Z_EXPORT (libSceAgcDriver) becomes
DepthLess/DepthGreater. For a pixel shader that breaks the promise, the host driver decides whether its interpolated Z is depth-tested before shading; what the console does then was not measured. - DB_SHADER_CONTROL.STENCIL_TEST_VAL_EXPORT_ENABLE (libSceAgcDriver) becomes
FragStencilRefEXT, taken from the second MRTZ channel of the 32_GR and 32_ABGR formats as Mesa's AMD drivers write it (emit_ps_mrtz_export); the console was not measured. The host value is the test value of both faces and whatREPLACE_TESTwrites, so a stencil operation that writes all ones or the operation value throws with it, as do a stencil clear draw, the UINT16_ABGR layout,STENCIL_OP_VAL_EXPORT_ENABLEand a device withoutVK_EXT_shader_stencil_export(NVIDIA's driver 610.57 offers none). - PA_SC_CONSERVATIVE_RASTERIZATION_CNTL (libSceAgcDriver) - 0x6001 uses Vulkan overestimation on devices that add at most 1/256 pixel and keep degenerate triangles; the console's uncertainty region and degenerate handling are unmeasured. A value with
OVER_RAST_ENABLEandUNDER_RAST_ENABLEclear is decoded as disabled even whenUNCERTAINTY_REGION_MODEor theOUTER_/INNER_UNCERTAINTY_EDGERULE_OVERRIDEbits (19:16) are set, as PPSA01474 writes 0xe6000; that these fields have no effect without over- or underestimation is assumed from their names, not measured. - Descriptor pool chain (libSceAgcDriver) - each pool holds two storage buffer descriptors more than a set can take, as a workaround: RADV (Mesa 26.0 and later) lets a
FREE_DESCRIPTOR_SETpool place a set up to 32 bytes past the pool's buffer (util_vma_heap_initis givenbo_size + RADV_POOL_HEAP_OFFSETas a size), and with a page-multiple pool size those bytes are an unmapped page - CMASK fast clears (libSceAgcDriver) - each fast-cleared CMASK tile (code 0) is stored as
CB_COLOR_CLEAR_WORDinto its 8x8 texels at the first draw into the target or at its fast-clear eliminate or DCC decompress pass, not as the color block writes the tile, and the CMASK is then stored as expanded (0xF): a texture or copy read of the target after that draw and before the title's eliminate sees the clear value in tiles no draw wrote, where the console keeps their previous bytes. Tile addresses follow the pipe-aligned SW_64KB_Z_X CMASK equation for 16 pipes; other CMASK swizzle modes, codes other than 0 and 0xF, a cleared tile of a DCC target and a fast clear of a mipmapped, 3D or array color target throw. When the clear runs as a compute pass (a resident target whose CMASK is host-imported), an invalid code throws at the next fast-clear materialization, not at the one that read it - HTILE depth fast clears (libSceAgcDriver) - HTILE is not modeled. Only RADV's
clear_htile_maskcompute kernel (17 GFX10 words, 64x1x1 groups) is recognized, and only when it leaves ZMASK at 0 (user[4]anduser[5]low nibbles both 0); the dispatch still runs, and the host depth image is cleared toDB_DEPTH_CLEARthe next time the surface with thatDB_HTILE_DATA_BASEaddress is bound. The address match is exact, so a clear of a different HTILE range does not hit. HTILE initialization, the stencil-only update, and fast clears made by the SDK's own paths, CP DMA fills or other kernels are not covered and leave the depth image as it was. The kernel words are the ones the PR author saw RADV submit; they are not compared with Mesa's source. A second kernel, the 9-word dword fill PPSA01802 (Kena: Bridge of Spirits) clears its depth HTILE with (64x1x1 groups, onebuffer_store_format_xper thread through the stride-4 V# inuser[0..3], the value read bys_buffer_load_dwordthrough the V# inuser[4..7]), is taken as a fill of that range when the store format is 32_UINT, 32_SINT or 32_FLOAT and the value V# covers the dword (NUM_RECORDS times its stride, or NUM_RECORDS bytes when the stride is 0) (MatchMemoryValueFill, then the whole-HTILE and ZMask rules ofNoteDepthMetadataFill). The value is read from guest memory when the dispatch is decoded, so a value that earlier GPU work in the same submission has not written back yet is not seen. - Host imports (libSceAgcDriver) on Linux skip ranges that are not mapped read-write, which are copied instead. The NVIDIA driver (595.91.07) refuses
VK_EXT_external_memory_hostimports of read-only or executable mappings (and shared mappings of regular files) withVK_ERROR_OUT_OF_DEVICE_MEMORY, after which everyvkAllocateMemoryon the device fails the same way. Read-only ranges are never imported, even on drivers that accept them. Shared mappings of regular files are not checked for. On Windows, an alias of adjacent registered ranges is refused if any page is not a shared view; a range made entirely of shared views is aliased run by run, including across sections or nonconsecutive section offsets. Adjacent registered ranges are joined into one import only where they fill a whole 8 MiB aligned window; ranges of a partly filled window are imported one at a time, and a buffer that crosses two imports is copied. Adjacent registered ranges are joined into one import only where they fill a whole 8 MiB aligned window; ranges of a partly filled window are imported one at a time, and a buffer that crosses two imports is copied. The 8 MiB window is a choice: unbounded runs were re-imported whole on every growth of a heap mapped in small pieces, and the NVIDIA driver (610.57.04, 615.78.08) then refused imports or lost the device. Not measured on RADV. - Cube texture descriptors with equal
BASE_ARRAYandDEPTHexpose six faces starting atBASE_ARRAY. Measured withimage_load,image_sampleandimage_get_resinfoon RX 6600 (gfx1032), using 64x64 R32_UINT faces and base slices 0, 5 and 6. Other ranges containing fewer than six faces or incomplete cube arrays still throw: hardware load bounds and filtered-sample addressing differ. Out-of-range cube-array sampling remains unimplemented. Samplers withDISABLE_CUBE_WRAPare created withVK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT, the flag RADV maps to that bit (Mesasrc/amd/vulkan/radv_sampler.c,src/amd/common/ac_descriptors.c); withoutVK_EXT_non_seamless_cube_mapa cube texture sampled through such a sampler throws. PS5 equivalence has not been measured on console hardware. - Multisampled CMASK (libSceAgcDriver) - a multisampled color target's CMASK is decoded under CB_COLOR_INFO FAST_CLEAR (bit 13, as for a single-sample target) or COMPRESSION (bit 14). Which bit a multisampled target sets is inferred: its fast clears were seen on GTA V's multisampled targets through their CMASK fills, not in a register dump. A COMPRESSION-only target's zero CMASK is not read as cleared (inferred: zero means cleared only under FAST_CLEAR), so only a fill after its first use clears it. A slice of a multisampled array is its own image at its own address with its own CB_COLOR0_CMASK (inferred from the register layout). Its CMASK is main's whole-metablock size.
- Multisampled CMASK state (libSceAgcDriver) - a multisampled target has no guest texels, so its CMASK state comes from fills rather than from a read at each draw as a single-sample target's does. A fill of the whole CMASK with 0 clears the host image at the target's next use, one with 0xffffffff marks it expanded, and any other pattern, or a fill that covers only part of the CMASK, throws at the next use (inferred: main's rule that a mixed CMASK is unmodeled, applied to the one host image). A FAST_CLEAR target starts from its CMASK keys at its first use, and a COMPRESSION-only target starts expanded. Copies by DMA into the CMASK are not seen, and a later use with another CMASK address throws. The clear does not rewrite the CMASK bytes as expanded, so a new target over the same CMASK (a different extent, so a different image) seeds from them again and clears once more.
- Multisampled color fills (libSceAgcDriver) - a multisampled target's samples are on the host, so a fill of its color memory is applied at its next use: a uniform fill over the whole of it clears the host image to that value, and a pending CMASK fast clear takes precedence over the fill and over a fill that would throw. Its color memory is a single mip and slice of addrlib's block dimensions for its sample count (ComputeThinBlockDimension). A fill over part of it, a fill reaching the start of a target whose footprint is not computed (linear), and a fill value with no exact clear value in the format (sRGB channels other than 0 and 1) throw at the next use. A fill before the target's first use is not seen (the host image starts at 0), nor are copies or shader writes into its color memory.
- Multisampled targets in CB passes (libSceAgcDriver) - a CB eliminate or DCC pass over a multisampled target throws, since its texels are on the host and only a CB resolve is modeled. A CB resolve into a fast-clear destination throws: the resolve writes the destination's texels but not its CMASK, which would still read as cleared.
- Multisampled target guest access (libSceAgcDriver) - the memory a multisampled color target throws on (CPU read, DMA copy, texture or storage image read) uses fragment-aware 64 KiB render-target blocks. AMD Addrlib's GFX10 thin-block rule gives the block width and height from element bytes and fragment count; its surface-size calculation rounds both extents to those blocks and multiplies by element bytes and fragments. Thus a 1920x1080 4x RGBA8 surface occupies 0x1fe0000 bytes, leaving the next allocation at +0x2000000 outside the guard. The 2x and 8x block orientations differ from treating the fragments as a larger element. Console address-layout equivalence has not been measured. Other tile modes retain the conservative
max(CB bytes * samples, width * height * element bytes * samples)bound, which may still reject a distinct allocation. Targets are kept per device for the device's lifetime, one per surface address, format, extent and sample count, and are not evicted. - CB resolve sample count (libSceAgcDriver) - a draw with CB_COLOR_CONTROL in resolve mode is a CB resolve when either PA_SC_AA_CONFIG or CB_COLOR0_ATTRIB.NUM_SAMPLES is multisampled, and takes its sample count from CB0. Inferred, not from documentation: PPSA36082 resolves its 4x targets with no sample count in PA_SC_AA_CONFIG, and radeonsi sets PA_SC_AA_CONFIG to 0 for a multisampled framebuffer when rasterizer multisampling and line smoothing are off.
- Custom MSAA sample locations (libSceAgcDriver) support a uniform 1x1 pattern for 2, 4 or 8 samples through VK_EXT_sample_locations. Patterns varying across the guest's 2x2 pixel grid throw. Devices without the required sample count, four subpixel bits or the full guest coordinate range refuse custom patterns. Depth images remain in GENERAL between draws, and render passes cannot continue across pattern changes. Guest offsets retain the existing decoder's signed four-bit, sixteenth-pixel interpretation; PS5 coverage and interpolation equivalence have not been measured on hardware.
- DCC display buffers are presented only when all keys of the console's DCC extent agree on uncompressed or a fast-clear code, padding keys included; other keys, and register-clear keys over a pending image, throw.
- Storage image write-back (libSceAgcDriver) stores the GPU results over a CPU-stamped range whose guest bytes equal the copy taken when the image's layers got their generation (images up to 1 MiB whose guest bytes were settled at that point, with no unit shadow, pending image results or recorded GPU store over them; other images keep the previous rule: the CPU's bytes win). A CPU store that rewrites the baseline value after the GPU changed it is not told apart from no store, and the GPU result wins there. The 1 MiB limit is a choice, not a console value.
- 16-bit float display buffers (libSceAgcDriver, libSceVideoOut) - pixel formats
0xC001000622000000and0xC001000600000000are taken as R16G16B16A16 and B16G16R16A16 float, eight bytes per texel, as KytyPS5 maps them and SharpProspero names them (Rgba16Float,Bgra16Float); PPSA11386 registers 3840x2160 tiled buffers of the first. The tiled layout is assumed to be the 8-byte SW_64KB_R_X equation ofTextureSwizzleEquations.hppapplied to full coordinates, as addrlib applies it, by analogy with the 32-bit scanout;ColorTargetLayoutapplies the equations to in-block coordinates, which for 8-byte texels differs in every odd 64-row block row (address bit 11 takes y bit 6) and for 16-byte texels also differs, so a float display buffer written throughWriteColorTargetand presented from guest memory would show those rows misplaced. The values are assumed to be linear light and are encoded with the sRGB curve for the 8-bit output. Bits 32-39 of these formats are0x06, as in the 10-bit formats SharpProspero lists without sRGB transfer, while its sRGB formats have0x00, its 10-bit PQ formats0x04(with0x07in bits 40-47) and its float BT.2100 PQ formats0x07: they show only that these formats are not the sRGB ones. NaN and negative values give 0, values of 1 and above give 255, alpha is linear. Neither the layout nor the transfer is verified against console or title frames, and HDR output is not modelled. DCC metadata on a float buffer throws. - DCC key stores cover the console's DCC extent only for pipe-aligned DCC of single-level, single-layer 4-byte SW_64KB_R_X surfaces; other surfaces, and every texture and color target key read, cover one byte per 256 bytes.
- DCC key fills over other images (libSceAgcDriver) - a uniform fill at a live image's
dccAddress, at least one key per 256 bytes long, is a key fill of that image even when other live images overlap it, but only while it is no longer than the image's key extent (DccKeyCount, the pipe-aligned count); a longer one is classified by its overlaps as before. The bound is inferred, not measured on a title: a longer fill also writes the bytes past the keys, which the overlapping image may own. Whether the title's bloom-target key fills stay within the extent was not checked (APS5_TRACE_DCC_KEYSprints the length of the fills classified as keys, not of the refused ones). - Comp-to-single DCC keys (libSceAgcDriver) - key 0x10 (
GFX9_DCC_CLEAR_SINGLEin Mesaac_descriptors.h) uniform over a surface makes every texel the element at the start of its 256-byte block, the top-left texel of the block where radv'sbuild_clear_dcc_comp_to_single_shaderstores the clear color. Sampled textures and storage images read that expansion (a storage image's first write stores the keys uncompressed, as under a clear code), and a CB DCC decompress pass stores it over the texels. A fast-clear eliminate over such keys throws, as a metadata pass over mixed keys does; color targets that render over such keys use their stored texels, and textures read mixed keys, and comp-to-single keys of a format whose element does not divide 256 bytes, as stored. "Uniform" is judged over the key bytes the driver reads, one per 256 surface bytes (see DCC key stores above), so a pipe-aligned surface whose keys differ past that range is expanded as if all were comp-to-single, as for the other clear codes. Not verified on hardware - 8_SRGB color targets (libSceAgcDriver) on a device that cannot attach and blend R8_SRGB render into an RGBA8_SRGB copy of the target, so the sRGB encode, decode and blend rounding are the device's; the console's are not measured.
- 8_SRGB color targets (libSceAgcDriver) on such a device throw unless they are single-mip 2D surfaces with a resident image; linear targets have none.
- 1_5_5_5 color targets (libSceAgcDriver) render into A1R5G5B5. AMD formats keep the first component in the least significant bits (X in bits 0-4, Y in 5-9, Z in 10-14, W in bit 15, as in the fpPS4
kSurfaceFormat1_5_5_5doc comment and as measured for textures in the packed 16-bit textures entry), while A1R5G5B5 keeps R in bits 10-14 and B in bits 0-4, so the pixel exports are written with X and Z exchanged, which makes the stored bits follow the guest layout. The decoded format, component mapping and write mask are tested, and an execution test draws 32_ABGR exports into tiled and linear targets and compares every stored texel with the guest layout on the host device; the export direction is not measured on a PS5. Only UNORM with the standard component swap is accepted. Constant blend colors go through the same component-swap handling as other targets with that export mapping (0xc6), which is not tested for this format. PPSA06699 renders into this format. - COPY_DATA from the GPU clock (libSceAgcDriver,
SRC_SEL9) writes the host steady clock in 10 ns ticks when the packet runs, the same counter as theRELEASE_MEMtimestamp, not the console's GPU clock. TheCOPY_DATAcache policy fields are accepted and have no effect. - 16-bit RGBA color targets (libSceAgcDriver) support the standard and the alternate (
COMP_SWAP1) component order. Alternate swaps red and blue in shader exports, write masks and constant-color blend factors, followingac_translate_colorswapin Mesa 26.2.2 ac_formats.c. The reverse orders (COMP_SWAP2 and 3) keep throwing when blending, and simultaneous targets requiring conflicting blend constants throw. Console equivalence has not been measured on hardware. - UINT16_ABGR color exports (libSceAgcDriver) - export format 7 is accepted only into an unsigned integer target, and each 16-bit value reaches the target unchanged; a value beyond a narrower target's range (
R8_UINT) is undefined in Vulkan, and whether the console clamps or truncates it is not confirmed. SINT16_ABGR (8) throws - VGT_TF_PARAM (libSceAgcDriver) - DISTRIBUTION_MODE is taken as scheduling only; the host tessellator decides the order of a patch's triangles.
- Local programs (libSceAgcDriver) that the hull header leaves at address 0 are prepared at their first draw, one artifact per tessellation configuration, instead of at registration.
- Packed 16-bit textures (libSceAgcDriver) -
5_6_5,1_5_5_5and4_4_4_4are viewed as R5G6B5, A1R5G5B5 and B4G4R4A4 (the 4-bit format every Vulkan device can sample; R4G4B4A4 is optional and lavapipe lacks it) with a swizzle that reads X from the low bits, and a DST_SEL of the missing W of5_6_5reads X. Measured on RDNA2 (gfx1035), not on the console: throughimage_loadandimage_sample_lz, X, Y, Z (and W of1_5_5_5and4_4_4_4) come from the low, middle and high fields, and W of5_6_5returns X (the low 5 bits), not 1. - Min/max S# reduction (libSceAgcDriver) -
FILTER_MODE1 and 2 use Vulkan's min and max reduction (the sameSQ_IMG_FILTER_MODEvalues RADV programs). Measured on RDNA2 (gfx1035), not on the console: only texels of non-zero weight are reduced, and the weight is taken after rounding to the 8-bit subtexel grid (a sample within 1/512 texel of a centre reduces that texel alone); a driver with othersubTexelPrecisionBitsreduces a different set there. Gathers ignore the mode. - Min/max S# (libSceAgcDriver, shader recompiler) - throws with anisotropy, a linear mip filter, depth or color-texture compare, or bilinear filtering of an image the recompiler point-samples (sint, converted, depth-bits).
- FORCE_DEGAMMA (libSceAgcDriver) - an S# that forces sRGB decoding adds nothing to an sRGB texture, as in shadPS4 (PS4). Measured on RDNA2 (gfx1035), not on the console:
8,8_8and8_8_8_8sRGB textures sample bit for bit the same with and without the bit (point and bilinear; BC formats not measured). Other textures throw; on the same hardware their UNORM8,8_8and8_8_8_8forms sample bit for bit as the sRGB format (decoded before filtering). - Tessellation factor ring (libSceAgcDriver) -
sceAgcDriverSetTFRingrecords the ring andsceAgcDriverGetTFRingreports it. Before a title sets one, it reports the console's default, 0xFF0000000 and 128 KiB, as measured in PS5_Vulkan's hardware findings. Nothing is written to the recorded ring: tessellated draws run as Vulkan tessellation stages, which keep their factors on the host, so a title that reads tessellation factors from the ring finds none. A null base or a zero size throws; ps5-opengl's AGC runtime callssceAgcDriverSetTFRing(0, 0)in its cleanup, and what that does on the console is unknown. - VertexBufferReadSize and the vertex staging copy (libSceAgcDriver) - a vertex fetch whose index is at or past the descriptor's
num_recordsno longer throws forOOB_SELECT0 and 1. The copy still spans the whole fetch, but the bytes fromnum_records * strideonward read as zeros, so the out-of-range vertex is(0, 0, 0, 0). Measured withbuffer_load_format_xyzw(idxen) through the hardware oracle on RDNA2 gfx1036 for PPSA01325's word 30x0004dfac(OOB_SELECT0) in #2241: indices 3, 4 and 5 over 3 records each return00000000 00000000 00000000 00000000, W included, unlike VulkanrobustBufferAccess, which returns(0, 0, 0, 1). The zeros come from the record check, not from memory: stored values placed past the records are ignored, and anum_records6 control returns them.RESOURCE_LEVEL1,OOB_SELECT1 and FORMAT 7432_32_32_FLOATgive the same zeros, in wave32 and wave64; gfx1013 (the console) is not measured.OOB_SELECT2 returns the stored values past the records andOOB_SELECT3 comparesnum_recordsagainst the byte offset, both measured on the same setup in #2241; the two keep throwing pastnum_recordshere, since each needs its own rule. Zero-stride descriptors (stride0) keep their separate byte-count check and are not covered here. Each out-of-range fetch is copied alone and outside the draw-snapshot cache, which is neither read nor written for it: a reused snapshot would carry another draw's bytes into the zero region, and a stored zero-padded copy would hand zeros to a later in-range draw at the same address. The valid prefix is still flushed for GPU writes before it is read. - Draw snapshot buffer descriptors rewrite each affected array from element zero, preserving unchanged descriptors. MoltenVK 1.4.2 writes auxiliary buffer lengths at index
iinstead ofstart + ifor partial array updates (MVKDescriptorSet.mm, BufferAuxSize). Per-element writes therefore let one buffer length replace another even when Vulkan buffer handles and ranges are correct. - User-data callees (libSceAgcDriver) - a compute shader's
s_swappc_b64whose source pair is a user-data pair, or a copy of one made withs_mov_b32ors_mov_b64in the same block, calls the code at the address that user data holds. At dispatch the callee is read from guest memory up to itss_setpc_b64on the link pair, one copy of it is appended per call after the caller's last instruction, and the call is rewritten tos_call_b64to that copy. The words after the caller's last instruction are dropped; in the shaders seen they are metadata, and a caller that reads its program counter, which could address them, throws. A copy runs at another program counter than the callee, so a callee that reads its program counter, makes a call, ends the program, jumps through another pair, returns before code it branches to, spans more than 64 KiB or is not readable throws. Callees are read again whenever a dispatch misses the dispatch cache; a dispatch that hits it reuses the linked program, so a title that rewrote a callee in place at the same address would keep running the old code. - DB_RENDER_CONTROL resummarize draws (libSceAgcDriver) - HTILE is not modeled, so a RESUMMARIZE_ENABLE draw without depth or stencil tests and without color writes changes no modeled state and draws nothing. A pure resummarize with pass-through D16/D32 depth, no depth writes or bounds test, no stencil plane, no color writes and no running pixel stage also draws nothing; other resummarize, copy or decompress draws throw.
- Comparison-sampled R32 float and R16 unorm textures (libSceAgcDriver) whose surface a storage image holds are sampled from a D32_SFLOAT or D16_UNORM image that the GPU refills from the storage image whenever its version moves, because Vulkan compares only depth formats. Only surfaces of at least 1 MiB (
APS5_DEPTH_COPY_MIN_KIB) that are not 3D take the copy; smaller ones stay CPU snapshots compared against guest memory. With copies of small surfaces, one title hit an intermittent boot-time crash that was not explained. - Null vertex buffer V# (libSceAgcDriver) - a V# whose four dwords are all zero is bound to a zeroed vertex buffer of the attribute's size with stride 0, so every component of its attribute reads 0. This follows the V# fields in the AMD RDNA2 ISA (
NUM_RECORDS0,DST_SEL0 selects 0), not measured on hardware. - Descriptor pool recycling avoids individually freed sets: RADV 26.2.2 initializes its free-set heap with 32 bytes beyond the backing allocation. An allocation-only RX 6600 test reproduces acceptance of a 4,128-byte set in a 4,096-byte pool; the path without individual frees rejects it. Pools reset after their last set is released. A cached set can retain unused slots in an exhausted pool until eviction; peak gameplay pool memory remains unmeasured.
- 16-bit depth with stencil (libSceAgcDriver) is stored as
D24_UNORM_S8_UINT, orD32_SFLOAT_S8_UINT, on a device withoutD16_UNORM_S8_UINTas an attachment. Depth comparisons, clears and the depth image see the wider format's precision, so results at the 16-bit quantization boundary can differ from the console; the constant depth bias is scaled by 256 for D24, and a non-zero constant on the D32 float fallback throws. Not measured on console hardware.