Files
AnyPS5/docs/dev/debt/libSceAgcDriver.md
T
AhirandClaude Opus 5.5 f970620d95 fix(agc): keep half the host heap out of the host import budget
Imports, type-3 mirrors and the buffers and textures placed in host memory share the driver's system memory heap. On Windows that heap is half the physical memory, so on a 16 GiB machine the old budget (the heap less 2 GiB) let imports hold about 6 of its 8 GiB, and host buffer allocations then overflowed into the 256 MiB device-local host-visible heap and failed. The budget now reserves the larger of 2 GiB and half the largest host heap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 04:11:15 +02:00

153 KiB
Raw Blame History

libSceAgcDriver technical debt

Silent stubs

  • sceAgcDriverRegisterDefaultOwner (libSceAgcDriver) - takes one uint32 owner by value (OpenAGC include/agcdriver.h, SharpEmu AgcExports.DriverResources.cs) and returns 0 without recording it. PPSA21480 treats the code 0x8A6C9018 it returned before as fatal. Console behaviour is unverified: OpenAGC describes the function as a firmware 5.50 stub that returns 0x8A6C9018, SharpEmu returns 0

Unknown function info

  • Optional BMP frame dumps use checked host stdio. An open, header, pixel or close failure prints one diagnostic and disables subsequent BMP dumps for the process; queued frames drain without more file I/O. Pixel validation and allocation failures retain the existing fatal path. The CPU fixture checks identical BMP bytes and real pipe failures. VideoOut flips on an RTX 3060 continue after a first dump that cannot be opened; header, pixel and close failures during presentation are unmeasured.

  • Inactive vertex point size - PA_CL_VS_OUT_CNTL.USE_VTX_POINT_SIZE is accepted for lines, triangles and rectangles with GS_EN and HS_EN clear, when polygon mode is disabled or both faces are filled. Points, patches and absent routing or rasterization registers still throw. Bit 16 selects the misc export's X component as PointSize (AMD CIK register reference, PA_CL_VS_OUT_CNTL); ignoring it for non-point rasterization follows Vulkan PointSize. This is inferred, not measured on console hardware. XashPS5 (PPSA19111 v1.3) sets 0x01210000 on a filled triangle draw.

  • Native multisampled depth clears accept the recognized HTILE mask kernel's direct full-range dispatch after its GPU writes complete. Every metadata byte needs tracked driver-store coverage since notification, no intervening CPU or depth write, and HTILE words proving the current clear value of zero or one. Partial, overlapping, indirect and unproven producers remain unsupported. Existing sampled views retain the native image; guest depth publication and metadata-fill transitions remain unimplemented. Windows shared imports retain write tracking through separate aliases; private host imports that lose tracking and copied or mirrored HTILE uploads remain unsupported. Clear-word encoding and console equivalence remain unmeasured on PS5 hardware.

  • Native depth allocation retirement covers sampling-eligible multisampled owners only. Successful release notifications must cumulatively cover both complete depth and HTILE ranges; protection changes, failed releases and partial coverage keep the original guards. Retired owners leave lookup and metadata routing, while held views and recorded work retain their images. Retirement of single-sample or sampling-ineligible depth attachments and console equivalence remain unimplemented or unmeasured. The regression uses real allocation-registry operations with a no-op host-release callback, so it does not validate the host unmap backend.

  • Native MSAA depth attachment feedback is not implemented. A draw sampling a native multisampled depth view while attaching its source image throws before pipeline creation or render-pass recording, including read-only attachment feedback. Sampling that image in a separate draw or dispatch remains supported. The refusal compares the held native image views and their device and attachment identity; single-sample depth behavior is retained.

  • Native multisampled depth sampling accepts numeric D16 or D32, two/four/eight samples, one Z_X slice and mip, and pipe-aligned ITERATE_256 HTILE without stencil or VRS. Sampling eligibility is recorded before the attachment's first host clear: every HTILE word must prove the same initial clear of zero or one, and the complete sample-aware depth and HTILE ranges must be accessible and write-watched. Unsupported initial state or device sampling support retains attachment rendering but sampling throws; eligibility is never retried. Sampling rejects later guest writes, queued external GPU writes, and unproven HTILE clear/fill transitions. Guest publication, arbitrary guest depth import, stencil/raw-bit sampling, arrays and storage access remain unimplemented. The block footprint follows main's AMD Addrlib thin-block rule; the accepted HTILE clear words and PS5 equivalence are not measured on console hardware. PPSA99902's historical resident eight-sample D32 reader is documented in #419.

  • sceAgcDriverSetSubmitValidationMode, sceAgcDriverGetSubmitValidationMode, sceAgcDriverSetSubmitValidationConfig, sceAgcDriverGetSubmitValidationConfig, sceAgcDriverSetValidationErrorOutputFrequency, sceAgcDriverGetShaderDebuggingStatus (libSceAgcDriver) - the five validation calls return 0x8A6C1000 without reading their arguments or writing caller memory, and sceAgcDriverGetShaderDebuggingStatus returns 1, as OpenAGC reads them from Sony's libSceAgcDriver.sprx: each validation export is mov eax, 0x8a6c1000; ret in 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 0xffffffff with 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_ENABLE is taken to write DB_DEPTH_CLEAR to 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

  • GS_FAST_LAUNCH geometry (AGC driver) - VGT_SHADER_STAGES_EN.GS_FAST_LAUNCH = 1 with a point list and GE_CNTL groups of one vertex and one primitive decodes as one point per subgroup, with the primitive limit taken from GE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTOR and the GS/ES VGPR counts not required. Follows how RADV drives mesh shaders on gfx10.3 (ac_nir_lower_ngg.c, the workgroup index comes from the vertex id of the first thread), not measured on the console. Fast launch 2, other group sizes or input primitives and indexed fast-launch draws still throw. PPSA03671's fast-launch draws decode, but the title stops earlier in a wave64 vertex program, so they have not reached the GPU yet.

  • Line-strip geometry output (AGC driver) - a geometry program with VGT_GS_OUT_PRIM_TYPE = 1 (line strip) runs as a mesh shader with OutputLinesEXT: each primitive export is one line whose two vertex indices are packed like the three of a triangle, as Mesa's NGG lowering exports line strips (ac_nir_lower_ngg_gs.c), and each input primitive gets VGT_GS_MAX_VERT_OUT - 1 lines where a triangle strip gets - 2 triangles. A subgroup gets at least GE_MAX_OUTPUT_PER_SUBGROUP line slots, because Mesa's lowering exports one primitive, possibly null, per emitted vertex; whether the console's compiler does the same is not known, and triangle strips keep - 2 per input. A fast-launch subgroup takes its line count from GE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTOR as for triangles. Line output counts as lines for the polygon offset decode, as on the vertex path. The lines are drawn 1 pixel wide with Vulkan's default line rasterization, like lines on the vertex path: PA_SU_LINE_CNTL is not read, and neither its width nor the console's line rules were measured. Points, rectangle lists, per-stream types and line strips from a vertex program on the mesh path still throw.

  • Alpha-to-coverage (AGC driver) - only DB_ALPHA_TO_MASK = 0xaa01 is accepted: the enable bit with the four quad offsets at 2 and no OFFSET_ROUND, the undithered setting radeonsi programs. Draws are single-sampled, so the pixel shader writes SampleMask 0 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 with RADV_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's alphaToCoverageEnable is 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. A draw without a color target on MRT0 keeps alpha-to-coverage from its MRT0 export, as radeonsi exports MRT0 as 32_AR for alpha-to-coverage without a color buffer. A draw that enables alpha-to-coverage with no MRT0 export still throws: what the console covers then is not measured. Default dithered DB_ALPHA_TO_MASK = 0x18701 additionally uses a per-quad sample-mask table measured on gfx1036 for eight standard samples with FP16_ABGR or 32_ABGR exports; other sample counts, custom locations, EQAA combinations and dithered UNORM16/SNORM16 exports throw. The dithered rule is not measured on gfx1013.

  • A one-sample target under a multisampled rasterizer without MSAA_ENABLE (AGC driver) - PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES > 0 with PA_SC_MODE_CNTL_0.MSAA_ENABLE clear, one-sample color targets and no depth target, or a one-sample depth target with no color target or one-sample color targets, is drawn as a one-sample draw at the pixel center. Without the bit every sample is taken to be at the pixel center (see the entry for a multisampled target rasterized without MSAA_ENABLE below), and a one-sample target is taken to keep the coverage of sample 0, so a one-sample depth target is tested and written once per pixel with the depth at the pixel center. That is the rule VK_AMD_mixed_attachment_samples gives a color target with fewer samples than the rasterizer, but Vulkan reaches it only beside a depth or stencil target with the rasterizer's sample count (VUID-VkGraphicsPipelineCreateInfo-subpass-01505), so here it is an analogy, and for a one-sample depth target an extension of it. shadPS4 also rasterizes with the largest sample count of the bound depth and color targets (L416-L417, L477-L480), and reads PA_SC_AA_CONFIG only for a SAMPLE_COVERAGE input (L174-L177). This is not measured on hardware. PS_ITER_SAMPLE, an ANCILLARY or SAMPLE_COVERAGE pixel input and a sample-mask export throw in a pixel program that runs, because what the console gives them under a multisampled configuration is not known; a depth-only draw that skips its pixel program is drawn with the null program, and its PS_ITER_SAMPLE and pixel input bits are ignored. Points, lines and alpha-to-coverage throw as for a multisampled target. With a one-sample depth target, DB_EQAA Z interpolation (INTERPOLATE_COMP_Z, INTERPOLATE_SRC_Z) or overrasterization (OVERRASTERIZATION_AMOUNT, ENABLE_POSTZ_OVERRASTERIZATION) also throws. METAL GEAR SOLID: Peace Walker (PPSA18216) draws a one-sample color target under a 4x configuration this way; the first draw of its opening movie binds a one-sample depth target and no color target under the same configuration.

  • MSAA_ENABLE on a single-sample target (AGC driver) - PA_SC_MODE_CNTL_0.MSAA_ENABLE with PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES = 0 is drawn like the same state without the bit when sample 0 of the four PA_SC_AA_SAMPLE_LOCS_PIXEL_*_0 registers 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

  • A multisampled target rasterized without MSAA_ENABLE (AGC driver) - PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES > 0 with PA_SC_MODE_CNTL_0.MSAA_ENABLE clear is drawn with the target's sample count and every sample at the pixel center, as a single-sample scan into a multisampled target already is. On GFX10 and later the bit is what makes the rasterizer use the programmed sample locations (RADV 061bc615), and with multisampling disabled GL rasterizes at the pixel center and gives the fragment full coverage, which radeonsi programs by clearing the bit; that the hardware does the same with MSAA_NUM_SAMPLES > 0 is inferred from these and is not measured on hardware. A depth target with the same sample count also has every sample at the pixel center, so each depth and stencil sample is tested and written with the depth at the pixel center, as GL allows when multisampling is disabled (L185-L190, L422-L425). GL also lets an implementation test only a centermost sample and set every sample from it (L425-L434), which differs when a pixel's stored samples differ; which one the console does, and how its occlusion queries count such a draw, is not measured on hardware. DB_EQAA Z interpolation or overrasterization and a sample-mask export with a depth target throw. Points, lines and alpha-to-coverage throw. SONIC SUPERSTARS (PPSA06888) draws such a 2x target, and METAL GEAR SOLID: Peace Walker (PPSA18216) a 4x depth and color pair.

  • sceAgcDriverGetEqContextId (libSceAgcDriver) - returns ident: an end-of-pipe interrupt is delivered only to the registration whose id equals the raising queue, so ident is that context's id (titles register 0 or 1 for graphics and 0x20 + 8 * pipe + queue for compute). The console's value is not known; KytyPS5 returns the event's data, which it fills with the interrupt's context id, whereas data here is the EV_CLEAR trigger count. Other event filters throw

  • User data registers (AGC driver) - a SPI_SHADER_USER_DATA_* or COMPUTE_USER_DATA_* register the title never wrote reads as 0 when a stage's RSRC2 count covers it, as Kyty (zeroed UserSgprInfo) 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 sceAgcDcbSetPredication writes 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_EN that uses LDS or s_barrier runs on the geometry (mesh) path. Each input primitive is assumed to yield at most one output primitive, the subgroup is bounded by GE_MAX_OUTPUT_PER_SUBGROUP as 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

  • VGT_SHADER_STAGES_EN 0x02002008 (AGC driver) - this one value, ES_EN = 1 without HS_EN on a non-patch draw, is drawn as the vertex path. AMD's CIK register reference calls ES_EN = 1 ES_STAGE_DS (the ES stage runs the tessellation evaluation shader), which does not explain it on a non-patch draw. Seen only on PPSA29413 and not verified on the console; other vertex-path values with ES_EN set still throw.

  • 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_records of 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.

  • Buffer descriptor CACHE_SWIZZLE hint (libSceAgcDriver) - bit 30 of word 1 in a buffer resource descriptor (words[1] & 0x40000000u) is normalized to zero when binding guest buffers in ShaderResources::addGuestBuffer. On GFX10 (gfx1013), bit 30 of V# word 1 is CACHE_SWIZZLE (Mesa's gfx10-rsrc.json: STRIDE [29:16], CACHE_SWIZZLE [30], SWIZZLE_ENABLE [31]), a cache-bank hint with no effect on the address or data, which is dropped here.

  • Buffer descriptors past the range limit (libSceAgcDriver) - a buffer V# whose size is over maxStorageBufferRange is bound only up to the end of the readable registered allocation holding its base when that allocation ends before the limit, and as the empty placeholder buffer, like a null V#, when no registered allocation holds its base; a V# over an allocation that reaches the limit still throws. Titles leave such words in descriptor slots their shaders do not reach on the path taken (PPSA10528 binds c8010002 2c6bec20 c0053c00 04000013, about 33 TB from an address above the user address space). What the console returns for an access past the bound range is not known; here the device's robust buffer access decides. Follows the review of #3202, 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, sceAgcDriverFindResourcesPublic and sceAgcDriverUnregisterAllResourcesForOwner return the same error without reading their arguments. Their parameter lists follow the libSceGnmDriver counterparts and are unverified. sceAgcDriverGetResourceRegistrationMaxNameLength writes 0xfc and returns 0, as prosper does from a title's registration configuration; the uint32_t* parameter matches the SDK wrapper sce::Agc::ResourceRegistration::getMaxNameLength(unsigned int*). sharpemu writes 256 and KytyPS5 returns RESOURCE_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

  • Registered shader types (libSceAgcDriver) - types 0, 1, 2, 4, 6 and 7 are prepared and type 5 is skipped. Any other type, such as type 8, is logged (unsupported registered shader type 8; it is not prepared at registration) and left unprepared at registration, and throws when it is used later. Hello Neighbor 2 (PPSA07426), Ad Infinitum (PPSA10600) and Ghostrunner (PPSA03685) register type 8 headers and run on past them; what a type 8 header describes is unknown, #1892 found no context registers in it.

  • NGG vertex workgroups (libSceAgcDriver) - type-2 PRIMGEN programs using DS instructions or s_barrier are 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 with user_data set to null. Whether the console reads the copy's user_data is 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_REG in Mesa's src/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_submit pins them for the paths the Driver takes.

  • Depth and stencil plane fills (libSceAgcDriver) - a fill that covers a known depth surface's whole stencil plane (DepthSliceBytes of its extent at one byte per texel) with four equal bytes clears the host stencil to that byte the next time the surface is bound, sampled or stored to, and a fill that covers the whole depth plane (two or four bytes per texel) clears the depth when the dword is a float in 0.0-1.0, or two equal 16-bit halves read as UNORM for D16 (NoteDepthMetadataFill). A partial fill, a dword whose bytes differ, a depth value outside 0.0-1.0 (0xff000000 is seen), a multisampled surface, or a fill made before the surface exists leave the host image as it was. An HTILE fill or a recognized HTILE clear kernel noted after a plane fill replaces its value with DB_DEPTH_CLEAR/DB_STENCIL_CLEAR. On the console the fill writes the plane the depth block reads; the surfaces this was seen on keep the stencil out of HTILE (PPSA26525 Heretic + Hexen and PPSA21444 DOOM + DOOM II reset the Cohtml UI stencil plane to 1 with a 0x01010101 fill every frame, then draw with stencil Equal 1 and 2), and what the console returns for a plane fill under a compressed HTILE was not measured. agc_driver_depth_plane_fill pins the rules.

  • HTILE address of a depth array slice (libSceAgcDriver) - decodeDepth adds the DB_DEPTH_VIEW slice offset to the Z and stencil addresses but not to htileAddress, 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 two uint16_t output 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 to sceAgcDriverSetHsOffchipParam, which keeps nothing here, are unknown; the stub does not write caller memory. PPSA99011 imports it

  • Vertex input and color target overlap (AGC driver) - direct draws exclude the descriptor prefix skipped by firstVertex or firstInstance from the alias check; indexed draws conservatively use index zero as the lower bound before adding firstVertex. Indirect draws still check the full descriptor extent. Reads of the active target's bytes remain unsupported; same-draw console ordering is not established.

Functional

  • Texture cache budgets (libSceAgcDriver) - the storage image cache's budget is what the reported heap budget leaves after the heap's use other than the cached storage images, the unused part of the sampled textures' 2 GiB floor and an eighth of the budget, capped at a quarter of the budget and at TextureCacheBudget (a quarter of the heap's size, at least 2 GiB), never below 512 MiB; the sampled cache's budget reserves TextureCacheBudget for the storage cache. So the sampled budget falls first while the sampled textures alive stay within their floor, and both fall together above it; each cache trims only at its next insert, a storage image is allocated before its insert reads the budget, and both budgets read the heap at most once a second, so a burst between two reads can still exceed the reported budget. The 2 GiB floor and the eighth are the sampled formula's values reused and the 512 MiB floor and the caps the earlier ones; none was measured beyond one title (PPSA11386) on one 10 GB GPU, and a title whose storage working set exceeds the shrunk budget re-creates storage images at each use.

  • Polygon primitive type (AGC driver) - VGT_PRIMITIVE_TYPE 21 (PrimitiveType::Polygon) decodes to VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN and is treated as inactive for vertex point size, like triangle fans and strips. PPSA01317 (Gran Turismo 7) submits polygon draws; whether the console performs polygon-mode decomposition or custom vertex routing is unverified.

  • Native multisampled DCC fast clears import uniform 0000/0001/1110/1111 keys into a single-layer, level-zero SW_64KB_R_X color image with 2, 4 or 8 fragments. Color and DCC/CMASK ranges must stay write-watched and unchanged; expanded or mixed initial DCC, register CMASK clears, compressed payloads, color transforms, lossy modes, metadata changes, guest publication and writes over a live owner throw. Driver-mediated reads of the color and metadata stay guarded. The metadata extent follows AMD Addrlib with the PS5 Mesa model: GFX10.1, 16 pipes, 256-byte interleave, two maximum compressed fragments. Console geometry, concurrent guest mutation and transitions outside this subset are unverified.

  • Initial stencil state uses zero for DB_STENCIL_CONTROL, DB_STENCILREFMASK and DB_STENCILREFMASK_BF (context 0x10b-0x10d), including after CLEAR_STATE. The public V0, V4, V5 and V7-V11 register-default tables supply these values. Linux's GFX10 clear-state table agrees on the control register but uses 0x01000000 for the reference masks; the console reset values have not been measured. Planet Coaster (PPSA01736 v01.017) enables stencil tests without writing the control register.

  • Type-0 and type-1 texture descriptors (TYPE = 0 or 1 in dword 3) with a nonzero base are bound as null images, like a zero base. TYPE 0 comes from a gfx1036 (RDNA2) measurement in issue #4139 (image sample, load and resinfo return zero, no fault). TYPE 1 comes from a gfx1010 (RDNA1) measurement in #4139 and #5828 (image_sample_lz, image_sample_b, image_load and image_get_resinfo return zero, no fault) with format 22 only; TYPE 1 is not measured on RDNA2, with other formats or with depth-compare reads (image_sample_c*). Neither type was measured on the console's gfx1013.

  • Multisampled color targets lose their guest-read guards and cannot be reused after a successful allocation removal or unmap covers their full guarded range. The next target, sampled or storage lookup removes retired cache owners; recorded work and sampled views keep their images alive until their last reference is released. Partial unmaps keep the guard; partial allocation reuse and guest access to live multisampled data remain unsupported.

  • Stencil reference export replay on devices without VK_EXT_shader_stencil_export supports at most sixteen values proved from constant, select and phi exports, with an export proved on every kept path. It records instances, then their triangles, then the reference classes of each triangle. One pipeline filters fragments at shader termination against a dynamic push value before late stencil and depth tests. The Vulkan stencil-reference replacement rule discards bits above the attachment's stencil width; supported stencil attachments have eight bits. Numeric subgroup observations throw except bounded own-lane mask plumbing and group control with kept-lane and derivative-helper coverage proved. WQM inputs must exclude helper contributions, since non-quad subgroup operations may treat helpers as inactive. Readonly guarded SRT reads may report internal host faults; these draws wait and check faults before publishing results, and loops throw. Indirect draws, restart, strips, fans, tessellation, mesh, multisampling, early tests, ordered pixel execution, guest shader memory writes, other fault-producing memory access, clocks, primitive ID inputs, attachment feedback and pipeline face/channel splits throw. An exhausted stage push range throws. Equivalence to console ordering has not been measured on console hardware.

  • 32-bit depth-bits reads of guest memory (libSceAgcDriver): a sampled 32_UINT or 32_SINT T# with a depth swizzle mode that no live depth surface matches reads the guest bytes through an R32_SFLOAT image, as the recompiler's depth-bits read expects, and the shader casts the fetched value back to the stored dword. On RX 6700 XT (gfx1031, AMD Windows driver 32.0.21045.5002) the read keeps float denormals, the sign bit alone and quiet and signaling NaN payloads bit for bit. A host that canonicalizes NaNs or flushes denormals on a 32-bit float fetch would change those dwords, and other drivers are not measured. A live depth surface whose extent or base level differs from the T# is not used; the read takes the guest bytes, as the 32-bit float and 16-bit views already do.

  • 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 dispatch of the 15-word 8x8x1 image clear kernel (MatchStorageImageClear: one colour read from the V# in user[8..11] stored to every texel of the T# in user[0..7]) that covers the whole plane and stores 0 or 1 through a 16-bit unorm view, 0 or 0xffff through a 16-bit uint view, or 0 or a normal float in (0, 1] through a 32-bit float or integer view leaves out the copy-in and replaces the copy-out by a clear of the depth image to that value; the dispatch still writes the storage image. The colour is read again when the copy-out is replaced, and a changed value keeps the copy-out. Indirect dispatches, partial last groups, dispatches that queue on the draw pipeline, other values and formats, a plane the kernel covers only partly and stencil planes keep both copies. 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 2D array view of more than one slice of the depth plane, or of a later slice, is a storage image over the slices from 0 to its last (LayeredDepthStorage); only the slices from its base slice to its last are copied: a slice that has a depth surface at the plane's address plus a whole depth slice per layer (DepthSliceBytes, as for the depth plane copies below) is copied from that surface before every use and back to it after the work, every bound slice each time, through a staging buffer; the other slices hold guest memory like any storage image. These images are kept per address, extent, slice count, base slice, format and tile mode until the device goes away, outside the storage cache's budget. Such a view needs a layer pitch equal to that slice size, and another view of the same memory (the 2D view of its first slice, a view from another base slice, or a sampled copy) is a separate image, copied in before and out after each use, so views that are written before a flush and share a slice do not merge: the later copy-out wins, so their results are not merged. A matching view still throws when it is a cube, a 2D array of more than one layer of the stencil plane or from a later slice, or another 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_dispatch pins these paths; none is measured on the console.

  • Collapsed color array views preserve the selected slice in the XOR address equation when the view is represented as a shifted 2D surface. Native Vulkan coverage checks R_X draws, array reads, publication and re-upload for slices 1-3; Lost Soul Aside PPSA26358 validates the RGBA16F cube mip chain on Windows/NVIDIA. Other devices and console comparisons remain unverified. Full-array views retain their layer addressing.

  • A depth surface (libSceAgcDriver) is retired when a color target or a written storage image it does not serve overlaps it, or when it is sampled through a view it cannot serve and guest memory over it was written since it was last bound; lookups then skip it, so sampling reads the memory like any texture. Bound again, it keeps its own depth instead of the bytes written over it. Without write watching, a view it cannot serve still throws. A retired surface also gives up its depth and stencil planes, so another surface sharing them no longer copies from it.

  • 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. A 2D-array T# whose BASE_ARRAY is past slice 0 reads slices BASE_ARRAY to LAST_ARRAY of the copy, which holds slices 0 to LAST_ARRAY, and a 2D binding of it reads slice BASE_ARRAY; that follows the colour 2D-array semantics and the titles that hit it (PPSA01556, PPSA03912), and was not measured on hardware for depth surfaces. Planes with mips, a cube or a 2D T# with a base slice other than 0, a BASE_ARRAY past LAST_ARRAY, 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).

  • Repacked format-43 copies (libSceAgcDriver): a sampled texture of format 43 (10-bit R, 11-bit G and B unsigned floats from bit 0) is uploaded into a B10G11R11_UFLOAT image with its R and B fields swapped (RepackTextureWord) and viewed with R and B swapped back. A texture whose results a storage image still holds is copied from it again, whole, whenever the image changed (RefreshDepthCopy); a 3D one is flushed to guest memory and uploaded instead. Storage reads and writes of format 43 keep the packed R32_UINT path.

  • SW_64KB_Z_X color targets (libSceAgcDriver) use the texture equations and 64 KiB block geometry of SW_64KB_Z_X, whose addresses are checked against AMD addrlib GFX10 in the texture tiling tests. Any 256-byte pipe/bank XOR below the block size is accepted, as for SW_64KB_R_X targets and Z_X textures; which of those bits the console honours, and the render results, are not measured.

  • 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.

  • Draw recipes with depth targets (libSceAgcDriver): direct draw recipes record and replay with active depth targets, matching the bound depth surface view by its target view identity; altered depth surface addresses or incompatible view handles trigger a recipe miss and pipeline rebuild.

  • 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.

  • 8_8 UINT color targets (libSceAgcDriver) decode as R8G8_UINT with the same component-swap mapping as 8_8 UNORM/SNORM (0xe4 / 0xec). Follows the existing 8 and 8_8_8_8 UINT paths; not measured on the console. RetroArch (PPSA99169) uses it during menu startup.

  • Separate alpha blend into 8_8 SWAP_ALT targets (libSceAgcDriver): the second channel of a two-channel target with COMP_SWAP 1 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's si_translate_colorswap and radeonsi's use of it for luminance-alpha formats, not measured on hardware.

  • Unexported pixel inputs (libSceAgcDriver): a pixel input whose SPI_PS_INPUT_CNTL parameter the previous stage does not export is programmed as a default-value input (OFFSET bit 5, DEFAULT_VAL 0, FP16 high half too) and reads (0, 0, 0, 0), as radeonsi's SI_PS_INPUT_CNTL_UNUSED does and as rect-list draws already do. The console's value for an unwritten parameter cache entry is not measured on hardware.

  • Per-sample pixel shading (libSceAgcDriver): PA_SC_MODE_CNTL_1.PS_ITER_SAMPLE (bit 16, PS5_Mesa GFX10.3 registers) on a multisampled draw enables Vulkan sample shading with a minimum rate of 1.0, so the pixel program runs once per sample. The draw throws when the device lacks sampleRateShading. On a single-sample draw the bit has no effect and is dropped. That the console runs the pixel program once per covered sample, and what it does with the other multisample registers in that mode, is taken from the register name and Vulkan's definition, not measured on hardware. Without the bit the pixel program runs once per pixel: the recompile request carries the mode (iteratesSamples), the ancillary's sample number reads 0 instead of SampleId, whose use would make Vulkan shade per sample, and the sample barycentrics and positions are evaluated at sample 0, whose locations the request carries (sampleLocations, every sample's in an iterated draw). DB_EQAA.PS_ITER_SAMPLES, the number of samples an iterated shader covers, is not read: an iterated draw runs once per sample.

  • Unused pixel programs (libSceAgcDriver): a bound pixel program is skipped when color masks have no intersection and DB export and execution enables are clear, regardless of retained SPI_SHADER_Z_FORMAT. This follows AMD PAL DbShaderControl and its GFX10 register definitions, not a hardware measurement. Enabled DB_ALPHA_TO_MASK conservatively retains the program even with ALPHA_TO_MASK_DISABLE, because DecodeState currently enables Vulkan alpha-to-coverage without applying that override. Console behavior for these combinations has not been measured.

  • Sample masks (libSceAgcDriver) - PA_SC_AA_MASK_X0Y0_X1Y0 and PA_SC_AA_MASK_X0Y1_X1Y1 hold one 16-bit sample mask for each pixel of the aligned 2x2 quad (Mesa gfx10.json); only the bits of the MSAA_NUM_SAMPLES rasterized samples count, and at one sample bit 0 of a pixel's field covers every sample of the target the draw is raised to. A mask equal for the four pixels is the Vulkan pSampleMask, the case radv and radeonsi program; with early fragment tests it needs earlyFragmentSampleMaskTestBeforeSampleCounting (radv reports it). A mask that differs between the pixels is applied in the pixel shader without early fragment tests: a pixel whose mask covers all or none of its samples is demoted to a helper invocation by gl_FragCoord parity and killed when the program returns (the Radeon 860M's Windows driver 32.0.13058.8001 writes undefined colour for a demoted pixel whose export leaves its output unwritten), and otherwise the pixel's mask is ANDed into gl_SampleMask (with alpha-to-coverage and the mask export) after demoting invocations whose SampleMask input it leaves empty. A pixel program with early Z that writes memory under depth, stencil or depth-bounds tests is therefore refused. That XnYm is the pixel at (x mod 2, y mod 2) = (n, m) is read from the field names; that the mask removes samples before the depth and stencil tests and occlusion counting is inferred from radv implementing the Vulkan sample mask test with it; and that it takes no part in the centroid (AA_MASK_CENTROID_DTMN 0) is inferred from radv setting that bit only for conservative rasterization. None of it is measured on gfx1013. A mask equal for the four pixels that removes some but not all samples of a pixel is refused with active centroid inputs, since where Vulkan puts the centroid relative to pSampleMask is implementation-defined, and any partial mask is refused with conservative rasterization (radv sets PREZ_AA_MASK_ENABLE and POSTZ_AA_MASK_ENABLE there, which 0x6001 leaves clear).

  • Covered-centroid raster state (libSceAgcDriver) accepts PA_SC_AA_CONFIG.COVERED_CENTROID_IS_CENTER only when the pixel program is skipped or SPI_PS_INPUT_ENA and SPI_PS_INPUT_ADDR have no active perspective or linear centroid input. Bit 29 follows PS5_Mesa GFX10.3 registers; RADV sets it on GFX10.3 and later. Active centroid interpolation and unspecified input registers still throw. Console covered-pixel centroid results were not measured.

  • Compute LDS beyond the device's shared-memory limit (libSceAgcDriver) lives in a coherent storage buffer with one slice of LDS bytes / 4 + 1 dwords, rounded up to an even count so that 64-bit atomics stay 8-byte aligned, per workgroup (the dword after the LDS is the atomic lock on a device without shaderBufferInt64Atomics), shared by all such dispatches and grown in 1 MiB steps up to maxStorageBufferRange; 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 in agc_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.

  • Host placement after a device-memory refusal (libSceAgcDriver): when the driver refuses a device-local allocation with VK_ERROR_OUT_OF_DEVICE_MEMORY after the pool's trim and the texture-cache release, device buffers and images (textures, storage textures, block, repacked, depth, depth plane and stencil copies, render, depth and multisampled targets and attachment proxies) are allocated in a host memory type their requirements allow. A host-placed device buffer leaves the pool on release. A host-placed image keeps that placement until it is destroyed and is never moved back to device memory, so every access to it crosses PCIe for its lifetime. The placements are counted in a rate-limited [bufferpool] line with the cumulative MiB, not the resident bytes. The 1x1 denormal clear probe still allocates device memory directly and throws on a refusal.

  • Pipeline journal (libSceAgcDriver): the journal is append-only and never compacted, so a module a later run no longer builds pipelines from stays in the file until it is deleted, and the whole file is read into memory while the replay runs (its modules reference that buffer until the last replayed pipeline was built). Only the first APS5_PIPELINE_JOURNAL_ENTRIES pipelines recorded are replayed (a record is never rewritten, so a pipeline past the bound stays past it), and the pipeline store evicts the least recently used entry past APS5_PIPELINE_STORE_ENTRIES (1024 each): a title with more configurations rebuilds the rest at the draw as before. A compute pipeline is replayed over the set layout DescribeSetLayouts derives from the result's bindings, which a dispatch that finds it resident checks against its ShaderResources layout only under APS5_VERIFY_SET_LAYOUTS=1. Recording and replay are best effort: a record whose write fails is lost, an entry whose build fails is skipped, and both are counted in the [pipeline-journal] line rather than stopping the run. A replayed pipeline that the title never asks for again costs its build time and its driver memory (its optimized relink is deferred to its first draw, so a run whose first frames bind many replayed pipelines queues their relinks then); nothing ages recipes out of the file. Two runs sharing the file take turns under its lock file, but each only knows the modules it loaded or wrote itself, so a module the other run appended meanwhile may be appended again (the loader keeps the first copy). Records a run appends after another run's load are replayed by that run only after its next start. The file header's format number is the only guard against a change of the recipe codec, CanonicalPipelineState or the store key that keeps the structures' sizes (the recompiler's source version does not cover the driver's sources).

  • Graphics pipeline libraries (libSceAgcDriver): a pipeline is fast-linked at the draw and relinked with VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT on 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 hard RLIMIT_RTTIME when it is above APS5_THREAD_RTTIME_US or rtkit's RTTimeUSecMax, because rtkit (verify_process_rttime) reads the hard limit from /proc/<pid>/limits and 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's MaxRealtimePriority and RTTimeUSecMax are 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

  • AMD image descriptors with TYPE=0 are treated as null for read-only image slots even when the base and other words are nonzero: image_load on a gfx1035 with such a T# returns 0,0,0,0, and the same descriptors were seen in direct reads in PPSA01341 on an RTX 4070. Writable and atomic image slots and entries of an image table keep faulting because there is no hardware measurement for them, and the reserved types 2-7 also fault. TYPE=1 is treated as null in the same read-only slots: gfx1035 reads it as zeros too, and gfx1010 returns zeros for image_sample_lz, image_sample_b, image_load and image_get_resinfo with format 22 (#5828); it is not measured on RDNA2, with other formats or with depth-compare reads (image_sample_c*). gfx1013 behavior is unverified

  • 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, as nullDescriptor allows, 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 without shaderStorageImageMultisample, throws when a null descriptor reaches it.

  • Draw input snapshots (libSceAgcDriver): PrepareDrawBindings caps read-only input buffer snapshots at 64 MiB (overridable via APS5_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 by drawSnapshotCapTests.

  • 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). A title's own CPU reads are not seen either, so a title that reads the results after waiting for a label reads stale memory. Explicit-size file writes (write, pwrite, writev, pwritev, AIO and fwrite) synchronize their source ranges through the host-read hook; other host readers are not covered. APS5_STORE_AT_FLIP=1 stores every pending result at each flip and APS5_STORE_AT_LABEL=1 before each label the GPU writes; both store every pending image, not only the ones the waited-for work wrote, and neither is on by default.

  • Pending storage-image results over memory outside the write watch (libSceAgcDriver) leave to the CPU the 64 KiB blocks whose bytes differ from the ones saved at the upload, the fill, the image copy, the previous store or the moment their range left the watch (Unwatch, or a host import that does not stay watched), and the blocks the watch saw the CPU write before that moment. A store made on the GPU saves its bytes through a host-visible copy that becomes the saved bytes when its batch completes; a store or a refresh that needs them earlier waits for that batch. A unit without a write generation saves the guest bytes when results land in it outside the watch. One whose range leaves the watch while its results are pending, and ranges that leave the watch through a failed collect, have no saved bytes; storing such results throws.

  • Windows host-pointer imports of the guest mapping itself are compared instead of write-watched, except writable imports of private MEM_WRITE_WATCH memory when the import probe keeps the write watch and a settle queue exists: those of 1 MiB or more stay watched, and smaller ones return to the watch once their settle has completed, unless a storage image overlaps them (see Pages under an import window): 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. Excluded blocks keep their write-watch marks as a hint only (UntrackedHint): a storage image or a sampled texture snapshot over them compares its bytes again after a mark in its range, a driver store, a mapping change or a recorded GPU write instead of at every collect epoch or lookup, and a mark the driver makes without a CPU store costs one comparison. This preserves CPU edits through byte comparisons but limits snapshot reuse on excluded blocks to draws of one collect epoch and disables unit shadows for them; restoring tracking on import retirement without replacing the backing remains 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. On Windows, the NVIDIA driver (617.42) marks every page of a host import of private MEM_WRITE_WATCH memory written, asynchronously, until a GPU use of the import completes; a 4-byte read of it on the settle queue ends that even while main-queue work that used the import first is still running, and an earlier import of the same pages whose first use runs after that read marks none of them (agc_import_settle_first_use, agc_import_settle_earlier_import). Such an import stays watched only after a 4-byte read of it on the settle queue has completed inside the window. Each one waits for that read, which keeps its window open for one round trip on the settle queue, so a same-value CPU store from another thread in that time counts as unchanged too. An import made where no settle queue exists is compared instead. An import of under 1 MiB is not settled inside its window, because the settle blocks the importing thread and a title that imports a 64 KiB buffer around every submission lost about 19 % of its frame rate to that wait (Dripl1x, #6060). It is compared until its read, submitted in a batch by a later import lookup under GpuMutex while the imports match the registry, has completed; a later lookup then returns its whole blocks to the write watch, each stamped as a CPU store, so whatever was derived from them while they were compared reads as changed once. Its blocks stay compared while storage results are pending over them, and those of an import retired before that stay compared.

  • Settled dma-buf imports (libSceAgcDriver): on Linux a dma-buf import of 1 MiB or more is read by a 4-byte copy on the settle queue right after it is made (settleImport), so the kernel maps its pages for the GPU there and not in the first submission that uses it (about 2 s for a 3 GiB import on RADV). The import's buffer is shared with the settle queue's family. An import made at map time settles on the guest thread that maps the range, inside its map call, so that thread waits while the kernel maps the pages: 0.47 to 2.2 s for the 3 GiB range of Hades (PPSA03355) and 0.49 to 4.3 s for the 4.5 GiB range of ASTRO BOT (PPSA21567) on RADV. Both titles map these ranges at start-up; a title that maps large ranges during gameplay stalls its mapping thread for as long. An import made from a submission settles under the GPU mutex, so its frame still pays for it; a smaller import, and every import on a device with no settle queue (one without a copy-capable queue besides the main one), pays in its first submission. A range imported through a host pointer, as lavapipe imports shared memory, is not settled

  • Sparse dma-buf imports (libSceAgcDriver): on Linux a shared-backed range larger than one 64 MiB udmabuf buffer is imported as one sparse buffer bound to the 64 MiB buffers, on a queue of its own when the device has a second family that binds sparse memory. The path is decided before anything is imported, and a failure after that throws. A range whose offset in the backing file is not a multiple of 64 KiB starts its sparse buffer at the 64 KiB block below it (HostImport::lead). A range takes the single udmabuf import instead, which udmabuf's size_limit_mb refuses above 64 MiB by default so the range is copied, when the buffer's sparse block size is larger than 64 KiB (it is 64 KiB on RADV), when the sparse buffer does not accept every memory type a chunk import can have, when the backing file ends inside its last chunk, or when it is imported at map time on a device with no second family (the bind then needs the GPU mutex); the NVIDIA proprietary driver imports no dma-buf memory

  • Unnormalized S# coordinates (libSceAgcDriver) - FORCE_UNNORMALIZED uses a Vulkan unnormalizedCoordinates sampler, 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) maps clamp mode 0 (repeat) on X and Y to clamp-to-edge as required by Vulkan unnormalized coordinates, and assigns the magnification filter when minification and magnification filters differ (since unnormalized coordinates sample at LOD 0). Anisotropy, TRUNC_COORD, MC_COORD_TRUNC and other clamp modes still throw. Secondary LOD bias (LOD_BIAS_SEC, bits [19:14]) is accepted and ignored, matching RDNA2 hardware measurements across explicit and implicit samples. High on Life (PPSA15388, #4736) uses secondary LOD bias and unnormalized coordinates with clamp mode 0 and differing min/mag filters during home base and city rendering.

  • 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_LTO disables 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. A device that cannot sample them gets an RGBA8 sRGB copy instead (a blit of the R8 or RG8 texels, refreshed with the storage image's version), whose decode is likewise the device's; 3D textures and storage images that are not single-depth and format-matched are copied from their snapshot instead.

  • PM4 submission rings (libSceAgcDriver) keep only the first 512 dwords of each submission, so the packets past them are not printed. The failure report dump does not allocate from the C runtime heap: packet names come from static strings, each line is formatted with integer and string conversions into a 512-byte stack buffer and written with one WriteFile. It still runs on whatever state the crash left. It waits about 100 yields for each ring's lock and otherwise prints that queue as busy, so a crash while recording a submission loses that queue's ring. It reads the rings without checking them, so a crash that corrupted them can fault inside the dump; such a fault during the report of an unhandled exception stops in that report's Sleep(INFINITE) and the process hangs instead of ending. The reports run at most once per process. Linux has no crash report: there the rings are printed only by APS5_STALL_WATCHDOG_SECS.

  • WAIT_REG_MEM reports a diagnostic after APS5_GPU_WAIT_TIMEOUT_MS without 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 to thread-trace user-data registers (libSceAgcDriver) updates the queue's UCONFIG values for register destinations 0xc340..0xc347, including repeated writes to one address; these are not guest-memory labels. The range follows AMD's GFX10 SQ_THREAD_TRACE_USERDATA_0..7 definitions in the Linux register header and the packets emitted by PPSA21564's bundled AGC driver before its EOP flip. Other register destinations throw. GPU thread-trace capture and its user-data stream are not emitted; only the final register state is retained. Console trace behavior is unverified.

  • 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 that RELEASE_MEM and WAIT_REG_MEM then 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_MODE and FP16_OVFL of COMPUTE_PGM_RSRC1 and SPI_SHADER_PGM_RSRC1_PS/GS/HS, bit positions from Mesa's gfx10.json and gfx103.json) is decoded into GuestContext::floatMode and is part of the shader cache keys and the serialized request (#981). IEEE_MODE is read by v_min_f32, v_max_f32, v_min3_f32, v_max3_f32 and v_med3_f32, their f16 forms, v_min_f64 and v_max_f64, IEEE_MODE and the FLOAT_MODE output denormal bits by the output modifier, DX10_CLAMP by the clamp modifier, FP16_OVFL by 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_f64 and v_max_f64 quiet 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_f16 and v_max_f16 flush 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) and FLOAT_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's sh_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=1 lands 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=1 includes 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 of NoPerspective outputs; 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.

  • 11_11_10 float color targets (libSceAgcDriver) render into R32_UINT attachments: the pixel shader packs each export toward zero like format-43 image stores (overflow to the largest finite value, negatives to zero, NaN kept), which is assumed to match the CB's conversion; only the reversed component order (COMP_SWAP 2) is accepted.

  • PA_SU_SC_MODE_CNTL.PROVOKING_VTX_LAST (libSceAgcDriver) uses Vulkan's last-vertex mode for native primitives. Bit 19 follows opengnm's flatshade_first mapping; vertex selection follows Vulkan, without console measurements. A rect-list draw in last mode gives all four corners of each rectangle the flat inputs of its third vertex through the generated control stage, as first mode gives them those of its first, so Vulkan's choice of provoking vertex for the tessellated triangles does not matter; which vertex the console takes for a rectangle is not measured either. A geometry program's primitives keep the vertex indices of its own primitive export on the mesh path, so last mode takes their flat inputs (from Vulkan) and their layer from the last index of each primitive, while their per-vertex inputs keep the order of first mode (Vulkan); that the console does the same for an exported primitive is not measured. Last mode throws without VK_EXT_provoking_vertex, for tessellation primitives, for triangle strips or fans assembled for a geometry program (whose odd-triangle order on the console is not measured), explicit per-vertex flat inputs, and triangle strips whose pixel shader reads a vertex slot itself (v_interp_mov_f32, or 16-bit interpolation, which is not checked yet), since the vertex order of odd strip triangles depends on the device. v_interp_p1_f32 and v_interp_p2_f32 read P10 and P20 against the guest's I and J, which come from gl_BaryCoordKHR in the same vertex order as the per-vertex values, so the order cancels out and strips with only those reads draw. A FLAT_SHADE input without pass-through that is read through v_interp_mov_f32 P10 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.

  • Blending into reversed-order color targets (libSceAgcDriver) - 8_8_8_8 and 16_16_16_16 targets with COMP_SWAP 2 or 3 keep the exported alpha in their first channel and red or blue in their fourth, which Vulkan would blend with the color and the alpha equation respectively. The color block is taken to blend in shader export order and apply COMP_SWAP afterwards: the alpha equation and every source-alpha factor use the exported alpha, and the destination alpha is the first channel. This follows the CB_TARGET_MASK text of the AMD SI (p. 229) and CIK (p. 238) register references ("the order of components after blending and before COMP_SWAP is applied"). RADV builds CB_BLENDn_CONTROL without regard to the swap (radv_pipeline_graphics.c) and renders R4G4B4A4, B4G4R4A4, R5G5B5A1 and B5G5R5A1 with SWAP_STD_REV and SWAP_ALT_REV (vk_format.c, ac_formats.c). Mesa CI's Navi10 (gfx1010) CTS run (job 112381553, 112381554) passes all 400 blend.format.<format>.states cases of each of those formats, including separate alpha and destination-alpha factors. It is not measured on gfx1013 or on 8_8_8_8 and 16_16_16_16 targets. Factor pairs Vulkan applies per channel are used directly. Otherwise the pixel shader only copies values: it writes the exported alpha, each channel's own export or the constants 0 and 1 into a second color at location 0 index 1 (and 1 into a channel of the first color whose source factor is ONE, moving its export into the factor), and the blend forms every factor, 1 - and product from those copies through SRC1_COLOR/SRC1_ALPHA factors. The blend unit therefore rounds them as it rounds a plain Vulkan blend of the same export; Vulkan does not require a second-source color to be converted like the first, but on an RTX 5080 and lavapipe every reversed-order case of the execution test is bit-identical to the same draw into a SWAP_STD target, which the test checks. Factor combinations that copies cannot express (for example ONE_MINUS_SRC_ALPHA in the alpha equation with SRC_COLOR in the color equation), sRGB, SNORM and integer targets, constant and second-source factors, destination-alpha and SRC_ALPHA_SATURATE factors in the color equation, different color and alpha operations, MIN or MAX with factors other than ONE, other color exports when the blend needs the second color, and devices without dualSrcBlend throw. Gran Turismo 7 (PPSA01317) blends CB_BLEND0_CONTROL 0x65010504 into such a target (#4782).

  • 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 when available, 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 what REPLACE_TEST writes, 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 and STENCIL_OP_VAL_EXPORT_ENABLE. A device without the extension uses the bounded replay described above. On such a device a draw first drops the export when the reference cannot change its result (StencilReferenceUnused: no stencil test or attachment, or on both faces a compare that ignores it and no reachable REPLACE through a non-zero write mask), and its pixel shader is then compiled at its first draw, not when its ABI is resolved. A device with the extension keeps the export, so each such shader keeps one pixel variant.

  • DB_SHADER_CONTROL.DEPTH_BEFORE_SHADER (libSceAgcDriver) becomes EarlyFragmentTests, and so does Z_ORDER EARLY_Z_THEN_LATE_Z or EARLY_Z_THEN_RE_Z without kill, Z export, mask export or stencil export; with alpha-to-coverage the tests stay late. The meaning of the bit and of Z_ORDER 3 follows Mesa (src/amd/registers/gfx10.json; RADV sets the bit for early_fragment_tests in src/amd/vulkan/radv_shader.c). What the console does with DEPTH_BEFORE_SHADER and a Z export, a mask export, a stencil export or alpha-to-coverage together was not measured.

  • 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_ENABLE and UNDER_RAST_ENABLE clear is decoded as disabled even when UNCERTAINTY_REGION_MODE or the OUTER_/INNER_UNCERTAINTY_EDGERULE_OVERRIDE bits (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. With PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES = 0 the PERSP_CENTROID and LINEAR_CENTROID inputs get the pixel-centre barycentrics, as without conservative rasterization (shader debt): the one rasterized sample sits at the pixel centre, so every centroid the rasterizer could pick for an overestimated pixel is that point; that it does not move the centroid towards the primitive when the sample is uncovered is inferred, not measured. With more rasterized samples, and when a single-sample draw is raised to a multisampled color target, centroid inputs throw.

  • 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_SET pool place a set up to 32 bytes past the pool's buffer (util_vma_heap_init is given bo_size + RADV_POOL_HEAP_OFFSET as 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_WORD into 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, a cleared tile of a DCC target and a fast clear of a 3D or array color target throw; base mip 0 of a 2D mipmapped color target is supported (offset follows AMD Addrlib Gfx10Lib::HwlComputeCmaskInfo reverse mip-chain ordering preceding base mip 0, unverified on PS5 hardware; mipmapped targets with 8 or 16 bpp throw until dsMipmapHtileFix mip-tail shrinkage is modelled). A tile with a code other than 0 and 0xF keeps its texels like an expanded tile: PPSA18391 (engine: Clickteam Fusion) fast clears a target whose CMASK it never initialized, so some of its nibbles hold leftover values such as 1 and 10; what the console does with such codes on a single-sample target is unverified on hardware. Single-sample views with viewMip > 0 skip CMASK fast-clear decoding since CMASK metadata only covers base mip 0 on AMD hardware (Mesa si_state.c similarly clears FAST_CLEAR for level > 0).

  • HTILE depth fast clears (libSceAgcDriver) - HTILE is not modeled. Only RADV's clear_htile_mask compute kernel (17 GFX10 words, 64x1x1 groups) is recognized, and only when it leaves ZMASK at 0 (user[4] and user[5] low nibbles both 0); the dispatch still runs, and the host depth image is cleared to DB_DEPTH_CLEAR the next time the surface with that DB_HTILE_DATA_BASE address 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, one buffer_store_format_x per thread through the stride-4 V# in user[0..3], the value read by s_buffer_load_dword through the V# in user[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 of NoteDepthMetadataFill). 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. When the fill HLE takes the dispatch instead (an output descriptor of one 32-bit value per element, a value descriptor that covers the loaded dword, a 16-byte aligned range), the value is read again when the fill is recorded, after the labels queued before it, and the HTILE fill is noted with that value.

  • HTILE written by other kernels (libSceAgcDriver) - when a depth target is bound and its HTILE was written since it was last read, the words over its whole pipe-aligned metadata (HtileMetadataBytes, the extent HtileFillCovers takes) are read, outside the surfaces mutex since the read waits for recorded work: when every one marks the depth (ZMask 0) or the stencil (SMem 0) cleared, the surface takes that fast clear and the words are stored back as expanded (depth only: ZMask 15 over the full Z range; with stencil: ZMask 15, Z range and SMem, SR1 and SR0 3), so only the title's next clear clears it again. PPSA29141 clears its depth every frame with a compute kernel the fill hooks do not recognize (InvokeHTileClearComputeShader in its ClearMRT pass); without this its depth kept every earlier frame and moving objects left black trails. PPSA25344 (Unreal Engine 5) reads its HTILE after the depth prepass in its Nanite depth export, which takes a tile with ZMask 0 as holding the clear value; without the expanded words the export put Nanite depth over the prepass and the Nanite material pass painted over the meshes in front. The word layouts are Mesa radv's for GFX10. Metadata with some tiles cleared is not modelled, and the compression state draws leave in HTILE is not tracked, so a title reading its HTILE after a draw sees the stored expanded words. A recognized HTILE fill or clear makes the next bind read the words again. A slice other than 0 of a depth array is read at its own HTILE (DB_HTILE_DATA_BASE plus the slice times HtileMetadataBytes) but the HTILE word layout of slices 1 and up is not modelled (PPSA05684's tile masks need X8 and Y8 to take Z0 and Z1 as well, slices 4 and up unmeasured), which neither the check that every word is cleared nor the store-back of each word in place needs, so such a slice takes the clear like slice 0. A depth-only HTILE whose tiles all have ZMask 0 but different Min/Max Z (per-tile clear values, PPSA05684's cascades) is taken as a whole-surface clear to DB_DEPTH_CLEAR, the value the DB's depth test reads for a cleared tile (HTILE_Z_CLEAR_REG in Mesa's src/amd/common/ac_descriptors.h: the clear word "only clears HiZ", "the DB register contain[s] the full clear values"); the HiZ test before it, which rejects against each tile's own Min/Max Z range (the HTILE definitions in the same file), is not modelled, so a draw that HiZ would reject over a tile cleared with Min/Max Z 0 is tested against DB_DEPTH_CLEAR instead. A multisampled depth target, and any target whose HTILE overlaps a resident native depth guard, skips this read and store-back and goes to the native multisample depth owner, which keeps its own refusal for a fast-clear transition.

  • 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_host imports of read-only or executable mappings (and shared mappings of regular files) with VK_ERROR_OUT_OF_DEVICE_MEMORY, after which every vkAllocateMemory on 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 where they fill a whole 8 MiB aligned window; ranges of a partly filled window, and ranges larger than the window, are imported one at a time. A buffer bound across adjacent ranges that are each larger than the window, or bound exactly over adjacent ranges of any size (from the first one's start to the last one's end), joins them into one import when it is bound, and their own imports are retired; a buffer that crosses any other two imports is copied. A heap grown in pieces re-imports its joined run only when a buffer is bound exactly over all of its pieces, or when a sparse descriptor heap over it is bound: a heap a shader binds whole while only the pieces from its base are mapped is bound in place from the import of that run, its views ending at the import's end as the copy's do, once the previous bind of that address saw the same last mapped byte (a heap that grows between every two of its binds stays copied; one bound more often than it grows is joined again after each growth) and at most 64 times per joined run base; past that, with a message, it is not joined again: it is bound in place while an existing import covers its run and copied otherwise, until no registered range starts at that base or the device changes. Ranges larger than the window are taken to be registered whole and never grown in pieces: a title that grows a heap in pieces larger than 8 MiB re-imports the joined run each time a bound buffer crosses into a new piece. 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_ARRAY and DEPTH expose six faces starting at BASE_ARRAY. Measured with image_load, image_sample and image_get_resinfo on 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 with DISABLE_CUBE_WRAP are created with VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT, the flag RADV maps to that bit (Mesa src/amd/vulkan/radv_sampler.c, src/amd/common/ac_descriptors.c); without VK_EXT_non_seamless_cube_map a cube texture sampled through such a sampler throws. PS5 equivalence has not been measured on console hardware.

  • Array layer views (libSceAgcDriver) - ArrayLayersServeView lets a 2D texture at a 2D array image's base address, or a 2D array texture over its first layers, view that image (Texture::CanCopyFrom). Mesa-built homebrew (ps5-opengl) writes the view's last slice plus one to CB_COLOR_ATTRIB3.MIP0_DEPTH, so a slice-0 render target is a two-layer array image, and the title samples it as a plain 2D texture (SuperTuxKart PPSA69421 drew a black 3D scene and minimap without this). The per-layer layout of a mipmapped array is assumed equal to a standalone surface's; it is not measured on the console. A 2D texture at a later slice's address still gets an image of its own.

  • 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. A uniform compute fill over exactly the whole color memory with an exact clear value is not stored into guest memory, since only the host image holds the samples. A target is retired only when its whole guarded range is unmapped or its allocation removed, so if a title reuses that memory for other data while it stays mapped, a read the guard does not check (a buffer read through a V# or a pointer) sees the bytes from before the fill.

  • Multisampled texture no draw rendered to (libSceAgcDriver) - sampling a multisampled texture whose surface no draw rendered to creates the host target from the T# and clears it to the color a single 32-bit value repeated over the whole footprint encodes, padding up to whole tile blocks included. Inferred, not measured on the console: that the samples of such a surface are the guest color memory, as a title that clears its canvas on the CPU (ps5-opengl ps5_clear_msaa4_color) relies on. Memory that is not one repeated value, tile modes other than Standard4KB, Standard64KB and RenderTarget, unmapped memory and values with no exact clear color throw. CPU writes to the surface after its host target exists are not seen by the host.

  • 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 and an FMASK decompress (CB_COLOR_CONTROL mode 5) are modeled. The FMASK decompress only applies the pending CMASK fast clear to the host image; it requires a CMASK address, and the target's DCC and FMASK contents are not interpreted. DCC over a multisampled target is accepted only in that pass and throws everywhere else. 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, index buffer read, fixed-function vertex buffer 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. Live targets remain cached per device, surface address, format, extent and sample count. Retired cache owners are removed on the next target, sampled or storage lookup; outstanding recorded work and sampled views retain their images.

  • 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 use a 2x2 sample location grid, anchored at the framebuffer origin like the guest's while the window offset is zero, on devices whose maxSampleLocationGridSize for the sample count is a multiple of 2x2, and throw on others. The RTX 5070 reports 2x4 for 2 samples, 2x2 for 4 and 1x2 for 8; an AMD Radeon integrated GPU reports 2x2 for each. Devices without the required sample count, four subpixel bits or the full guest coordinate range refuse custom patterns. A multisampled rasterizer drawing into one-sample color targets, and a one-sample depth target if any, with MSAA_ENABLE set, no alpha-to-coverage and no coverage pixel input is drawn with one sample at sample 0's location in each pixel of the grid, as the targets keep only sample 0's coverage. Without PS_ITER_SAMPLE such a draw also refuses centroid inputs, and keeps PERSP_SAMPLE, LINEAR_SAMPLE and ANCILLARY: a pixel shaded once is taken to get sample 0's barycentrics and sample index 0. With PS_ITER_SAMPLE only sample 0's invocation reaches the targets, so its sample and centroid barycentrics are taken to be sample 0's and the draw is shaded once. Both are inferred, not measured. The pixel shader runs once per pixel with sample number 0, so ANCILLARY reads sample 0 and POS_X_FLOAT and POS_Y_FLOAT keep the position SPI_BARYC_CNTL names. PERSP_CENTER and LINEAR_CENTER inputs on this path are interpolated at sample 0's location on the host, where the console uses the pixel center; one-sample depth images are created with SAMPLE_LOCATIONS_COMPATIBLE_DEPTH when the device supports it, as multisampled ones already were; this is how VK_AMD_mixed_attachment_samples defines the case, and it is not measured on PS5 hardware. Other mixed sample counts throw. 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.

  • Single-sample scan into a multisampled depth target (libSceAgcDriver) - a draw with PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES = 0 and PA_SC_MODE_CNTL_0.MSAA_ENABLE clear into a multisampled depth target rasterizes the depth target's sample count with every sample at the pixel center, as a single-sample scan into a multisampled color target already does. radeonsi programs this MSAA_ENABLE and PA_SC_AA_CONFIG with the depth buffer's own sample count when GL multisampling is disabled on a multisampled framebuffer (L1066-L1068, L3485-L3512), although its comment lists Z samples as at most the coverage samples (L3439) and it sets DB_EQAA.MAX_ANCHOR_SAMPLES to the Z sample count, where PPSA18216 sets DB_EQAA to 0. GL then lets every sample take the single-sample depth (L185-L190) and the fragment be treated as in multisampling with full coverage (L422-L425), which is what this draws: each depth and stencil sample is tested and written with the depth at the pixel center. GL also lets an implementation test only a centermost sample and set every sample from it (L425-L434), which differs when a pixel's stored samples differ; which one the console does, whether it instead evaluates the depth plane at each sample position, and how its occlusion queries count such a draw, is not measured on hardware. Points, lines, alpha-to-coverage, DB_EQAA Z interpolation or overrasterization, a pixel program reading the sample index, a sample-mask export and a single-sample color target next to the multisampled depth target throw

  • 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 0xC001000622000000 and 0xC001000600000000 are 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 of TextureSwizzleEquations.hpp applied to full coordinates, as addrlib applies it, by analogy with the 32-bit scanout; ColorTargetLayout applies 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 through WriteColorTarget and 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 are 0x06, as in the 10-bit formats SharpProspero lists without sRGB transfer, while its sRGB formats have 0x00, its 10-bit PQ formats 0x04 (with 0x07 in bits 40-47) and its float BT.2100 PQ formats 0x07: 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.

  • BT.2100 PQ display buffers (libSceAgcDriver, libSceVideoOut) - pixel formats 0x8100070422000000 and 0x8100070400000000 are taken as 10:10:10:2 texels with BT.2020 primaries and the SMPTE ST 2084 transfer, red or blue in the low bits, as SharpProspero names them (Rgb10A2Bt2100Pq, Bgr10A2Bt2100Pq) and KytyPS5 maps the first; PPSA01317 registers display buffers of the first when its output reports HDR. With HDR output they are presented unchanged through an HDR10 swapchain (VK_COLOR_SPACE_HDR10_ST2084_EXT, 10-bit, with VK_EXT_hdr_metadata); otherwise they are converted to 8-bit sRGB: ST 2084 EOTF, BT.2020 to BT.709 primaries with negative components clipped, a luminance tone map and the sRGB encoding. The tone map is ours: min(203 nits, white level) is SDR white (the BT.2408 HDR reference white), luminance passes unchanged up to 0.75 of it and an extended Reinhard shoulder above reaches 1.0 at the white level, the peak sceSystemServiceGetHdrToneMapLuminance last reported to the title (or the current one when it never asked), since titles read it once. How a console shows such a buffer on an SDR display is unknown. Alpha is opaque.

  • 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_KEYS prints the length of the fills classified as keys, not of the refused ones).

  • Comp-to-single DCC keys (libSceAgcDriver) - key 0x10 (GFX9_DCC_CLEAR_SINGLE in Mesa ac_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's build_clear_dcc_comp_to_single_shader stores 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.

  • 5_6_5 color targets (libSceAgcDriver) render into R5G6B5. The field holding X is in bits 0-4, Y in 5-10 and Z in 11-15, as for the other AMD formats; the component swap picks which export fills them, following ac_translate_colorswap in Mesa 26.2.2 ac_formats.c, which programs SWAP_STD_REV (2) for a surface with red in bits 11-15. With SWAP_STD_REV red goes to bits 11-15, R5G6B5's red, so the exports are written as they are; with SWAP_STD (0) red goes to bits 0-4 and the exports are written with X and Z exchanged, as for 1_5_5_5. The decoded format, component mapping and write mask are tested, and an execution test draws 32_ABGR exports into tiled and linear targets in both orders and compares every stored texel with that layout on the host device; the export direction is not measured on a PS5. Only UNORM with those two component orders is accepted; SWAP_ALT and SWAP_ALT_REV keep throwing. Destination alpha reads as 1, as Vulkan defines it for a format without alpha, which is not measured on the console. Constant blend colors with SWAP_STD go through the 0xc6 component-swap handling, which is not tested for this format. R5G6B5 has no entry in the DCC and multisample clear-encoding tables, so a CB DCC decompress or eliminate of DCC clear codes other than 0000 and a fast clear of a multisampled 5_6_5 target throw, as for 1_5_5_5. PPSA27305 renders into this format with SWAP_STD_REV (discussion #6434).

  • 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_5 doc 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_SEL 9) writes the host steady clock in 10 ns ticks when the packet runs, the same counter as the RELEASE_MEM timestamp, not the console's GPU clock. The COPY_DATA cache policy fields are accepted and have no effect.

  • COPY_DATA from a register (libSceAgcDriver, SRC_SEL 0) copies the value the queue holds for a shader, context or user config register (COUNT_SEL two consecutive ones) and throws for a register with no value. A dispatch records COMPUTE_DISPATCH_INITIATOR and, for DISPATCH_DIRECT, COMPUTE_DIM_X/Y/Z in the shader registers; DISPATCH_INDIRECT clears the three counts. Neither the register values the CP writes nor the copy's ordering against in-flight dispatches have been measured on hardware.

  • EVENT_WRITE stream-out statistics events (libSceAgcDriver, opcode 0x46) - SAMPLE_STREAMOUTSTATS1-3 and SAMPLE_STREAMOUTSTATS (event types 0x01-0x03 and 0x20 in navi10_enum.h) are accepted on the graphics queue as a 2-dword packet with event index 0 and do nothing. nvd.h documents these events only with the sampling index (3, or EVENT_INDEX__SAMPLE_STREAMOUTSTATS* 8-11 on gfx10); the index-0 packet has no destination dwords, and what the CP does with it is neither documented nor measured on hardware. AnyPS5 does not emulate the VGT stream-out counters, so there is nothing to sample. The sampling indices throw that sampling to memory is not implemented, and other indices, other sizes and the compute queue throw. sceAgcDcbEventWrite ignores the address for every event type except 0x38 and 0x39, so a call with one of these event types and an address builds the index-0 packet: nothing is written and nothing throws. Whether the console's builder emits a sampling packet for an address is not known.

  • INDEX_ATTRIBUTES_INDIRECT (libSceAgcDriver, opcode 0x91) loads INDEX_BASE, INDEX_BUFFER_SIZE (in indices) and INDEX_TYPE from the 16-byte record at attribute_base + attribute_index * 16, read when the packet runs. The record layout (address low, address high, size, type) is PAL's IndexAttribIndirect and the packet is the one BuildIndexAttributesIndirect writes; neither is measured on a PS5 or seen in a title. indirect_mode 1 (addr_offset, which only PAL's ExecuteIndirect path emits) throws, and so does a record whose index type is above 3, as INDEX_TYPE does.

  • 16-bit RGBA color targets (libSceAgcDriver) support the standard and the alternate (COMP_SWAP 1) component order. Alternate swaps red and blue in shader exports, write masks and constant-color blend factors, following ac_translate_colorswap in Mesa 26.2.2 ac_formats.c. Blending into the reverse orders (COMP_SWAP 2 and 3) is covered by the reversed-order blending entry, 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

  • Layered color targets (libSceAgcDriver) - only 3D color targets with 64 KiB render-target or 4 KiB standard tiling render several slices: a CB_COLOR_VIEW slice range on an array target, a layered draw with a depth target, and color targets of different slice counts throw. The vertex layer export (PA_CL_VS_OUT_CNTL USE_VTX_RENDER_TARGET_INDX) reaches the shader only for a layered draw and is otherwise ignored, as before; a layer beyond the view's last slice is undefined in Vulkan, while the console's handling (clamping to SLICE_MAX) is not modeled, and a view whose last slice lies past the surface is clamped to its last slice. DCC keys of a 3D color target are taken as one key per 256 bytes over the whole surface, every slice included, and only uniform keys (all uncompressed, or one fast-clear code) are interpreted, as for 2D targets; mixed keys leave the stored texels as they are. The console's DCC key layout for 3D surfaces is not checked (LEGO Horizon Adventures' (PPSA14632) lighting volumes are DCC)

  • 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_5 and 4_4_4_4 are 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 of 5_6_5 reads X. Measured on RDNA2 (gfx1035), not on the console: through image_load and image_sample_lz, X, Y, Z (and W of 1_5_5_5 and 4_4_4_4) come from the low, middle and high fields, and W of 5_6_5 returns X (the low 5 bits), not 1.

  • Min/max S# reduction (libSceAgcDriver) - FILTER_MODE 1 and 2 use Vulkan's min and max reduction (the same SQ_IMG_FILTER_MODE values 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 other subTexelPrecisionBits reduces a different set there. Gathers ignore the mode.

  • Min/max S# (libSceAgcDriver, shader recompiler) - throws with a linear mip filter, depth or color-texture compare, or bilinear or anisotropic filtering of an image the recompiler point-samples (sint, converted, depth-bits). With anisotropy the host sampler reduces over its own footprint; RADV programs the same S# for such a Vulkan sampler. Measured on RDNA2 (gfx1030 in #5156, gfx1035 in #5821), not on the console: without derivatives (image_sample_lz), min or max with anisotropic bilinear returns the bilinear minimum or maximum and with anisotropic point the point result, at every ratio field 0 to 4; with derivatives (image_sample_d, a 4:1 footprint), anisotropic bilinear at ratio field 2 or 4 reduces over the texels of all its anisotropic taps (the minimum or maximum along the major axis), not one bilinear footprint. A host that is not AMD reduces over its own anisotropic footprint, and Vulkan leaves nearest filtering with anisotropy to the host driver.

  • 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_8 and 8_8_8_8 sRGB 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 UNORM 8, 8_8 and 8_8_8_8 forms sample bit for bit as the sRGB format (decoded before filtering).

  • Tessellation factor ring (libSceAgcDriver) - sceAgcDriverSetTFRing records the ring and sceAgcDriverGetTFRing reports 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 calls sceAgcDriverSetTFRing(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_records no longer throws for OOB_SELECT 0 and 1. The copy still spans the whole fetch, but the bytes from num_records * stride onward read as zeros, so the out-of-range vertex is (0, 0, 0, 0). Measured with buffer_load_format_xyzw (idxen) through the hardware oracle on RDNA2 gfx1036 for PPSA01325's word 3 0x0004dfac (OOB_SELECT 0) in #2241: indices 3, 4 and 5 over 3 records each return 00000000 00000000 00000000 00000000, W included, unlike Vulkan robustBufferAccess, 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 a num_records 6 control returns them. RESOURCE_LEVEL 1, OOB_SELECT 1 and FORMAT 74 32_32_32_FLOAT give the same zeros, in wave32 and wave64; gfx1013 (the console) is not measured. OOB_SELECT 2 returns the stored values past the records and OOB_SELECT 3 compares num_records against the byte offset, both measured on the same setup in #2241; the two keep throwing past num_records here, since each needs its own rule. Zero-stride descriptors (stride 0) 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 i instead of start + i for 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.

  • The driver binds at most 262144 table images per pipeline, fewer under lower sampled-image limits; the rest fault when selected. A device without runtime descriptor arrays or variable-count bindings throws.

  • Image storage tables (libSceAgcDriver) - every bound element of a storage table is marked written and stored back after each dispatch, whether or not a lane wrote it. Two elements over the same bytes are therefore both stored back, and the order of their write-backs decides the result: PPSA26344's compute 0x80001e5f00 binds the stencil plane at 0x505f350000 twice, as R8_UINT in its own tile mode and as R8_UNORM in another. Rejecting the second writer of the same bytes per element, and per-element written bits from the GPU, are not implemented. The storage table binding is declared with its full capacity, so a set that holds one does not fit a chain descriptor pool and gets a dedicated pool. That capacity is a choice, not a device limit: the smallest of the sampled table capacity, 4096 and a quarter of maxPerStageDescriptorStorageImages. The sampled table capacity is not reduced by it, so a device with tight storage-image limits can fail layout validation. A storage element is rejected when its absolute index in the set reaches that capacity, even when few of the set's table elements are storage: the index counts every table element before it in the set, sampled ones included, and the element faults when a lane selects it. Atomic, packed, BY2/BY4 and dynamic-mip stores through a table still fault on use. PresyncSurfaces walks only the sampled table, so a store-only table's images are not presynced. Tables that mix sampled and storage elements, a T# that is both read and written through a table, non-uniform storage indexing and non-2D storage modes are not covered by tests.

  • Image-table S#s reading opaque black borders fault on selection: no runtime BC-swizzle pair check, even for valid swizzles 0/4 with direct or table T#s. Unselected entries do not fault. Table T#/direct S# keeps the direct check: only opaque-black swizzles moving alpha fault.

  • Table S#s that clamp to border fault only when selected: four variants cannot encode dynamic integer/float pairing. This also rejects otherwise-valid float pairs.

  • Table T#s paired with direct S#s fault when a paired border class differs from the decoded image class. Depth-bit views use float borders. Matching float pairs and inactive mismatches do not fault.

  • Table S# DISABLE_CUBE_WRAP faults on selection without VK_EXT_non_seamless_cube_map: no runtime dimension check, including valid non-cube direct/table T#s. Unselected entries do not fault; with the feature this bit alone is accepted. Table T#/direct S# faults only for cubes needing the missing feature.

  • An image table whose record range overlaps a buffer or storage image written by its own dispatch faults (table written by its dispatch) when any entry is selected, even if the write misses the selected record. A selected column's range is its record and the bytes of the record before the descriptor.

  • An image table T# whose surface has no mapped byte faults when selected instead of becoming a zero texture as a direct T# does; what the console returns for it is not measured.

  • User-data callees (libSceAgcDriver) - a compute shader's s_swappc_b64 whose source pair is a user-data pair, or a copy of one made with s_mov_b32 or s_mov_b64 in the same block, calls the code at the address that user data holds. At dispatch the callee is read from guest memory up to its s_setpc_b64 on the link pair, one copy of it is appended per call after the caller's last instruction, and the call is rewritten to s_call_b64 to that copy. The words after the caller's last instruction are dropped from the recompiled code; in the shaders seen they are metadata or constants the caller reads relative to its program counter, and those reads go to the captured guest code, which linking does not change. A caller that jumps through a table among those words, which the control flow analysis reads from the recompiled code, 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.

  • Table callees (libSceAgcDriver) - a compute shader's s_swappc_b64 whose source pair is two consecutive dwords of an s_buffer_load_dword*, or a copy of them made with s_mov_b32 or s_mov_b64 in the same block (a conditional branch forward past the call between them is crossed), calls one of the targets that buffer holds when the load's buffer descriptor comes from an s_load_dwordx4 without SOFFSET from a user-data pointer, or from a pointer pair that two s_mov_b32 of a literal or inline constant set, and the load's SOFFSET from an s_lshl_b32 by 2 to 24 bits. At dispatch the descriptor and the buffer are read from guest memory, and every non-zero 64-bit value at the loaded offset plus a multiple of the shift's stride, below the descriptor's size (stride times num_records, or num_records bytes with stride 0), is read as a user-data callee and appended once per call. The call becomes a branch to a chain that compares the source pair with each target (s_cmp_lg_u32 with a literal and s_cbranch_scc1 per dword) and s_call_b64s the copy that matches; no match runs the original s_swappc_b64 as an unlinked call. There is one chain for each value SCC has at the call, and the chain sets SCC back to that value (s_cmp_eq_u32 0, 0 or s_cmp_lg_u32 0, 0) before the callee or the unlinked call runs. The searched range is the descriptor's whole size, which covers the range the load can reach whether it is checked in bytes or in strides; which one the PS5 uses for s_buffer_load was not measured. More than 64 distinct targets, a buffer above 64 MiB or a copy out of s_call_b64 range throws. As with user-data callees, the buffer is read again only when a dispatch misses the dispatch cache, so a target a title adds to the buffer later is an unlinked call when a cached program runs.

  • AGC FS table (libSceAgcDriver) - the driver maps 2 MiB at 0xF_E0000000 when it is created and fills the table that libSceAgc keeps at 0xF_E0040000 (six 16-byte V# slots, slot 0 describing eight 16-byte records) the way PS5 12.00 libSceAgcDriver and libSceAgc do, but every record points at one AnyPS5 routine that only returns (s_setpc_b64 on the link pair). Sony's routines, which order work before a title's ds_ordered_count, are not modeled, and the other five slots stay zero.

  • 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_RECORDS 0, DST_SEL 0 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, or D32_SFLOAT_S8_UINT, on a device without D16_UNORM_S8_UINT as 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.

  • Depth surfaces created over storage writes (libSceAgcDriver): a depth surface created without HTILE copies its depth plane from the storage image at the plane's address, a pending one first, when that image has the plane's extent, one level, a 2D view or a 2D array view (its layer 0 is copied) and a 16-bit format for a 16-bit plane or a 32-bit format for a 32-bit plane. A multisampled surface, a surface the device stores in another format, a surface with HTILE and a surface without such an image start from DB_DEPTH_CLEAR, and the stencil plane from DB_STENCIL_CLEAR; CPU writes to the plane's memory are not read. Not measured on the console, where the plane is that memory.

  • Flip packets raise the graphics end-of-pipe event (libSceAgcDriver) - a flip delivers the queue's end-of-pipe event (sceAgcDriverAddEqEvent) once the work recorded before it completes, as a RELEASE_MEM with INT_SEL does, but only on a queue that has never delivered an interrupt from such a release: the queue is latched the first time a release interrupt reaches the event queue, and flips on that queue deliver nothing while its own release interrupts keep arriving. The latch is cleared when the queue has flipped five times without another interrupt from its own releases: the fifth flip delivers the event and unlatched the queue, so a title that waits on the flip event after latching recovers only if it submits further flips. The bound cannot be by time: the next release interrupt can arrive seconds later than a suppressed flip across loading stalls, and delivering an unexpected flip event on a queue that pairs flips with releases can crash; no fixed window short enough to unblock a waiting title keeps such a title alive. Whether the console raises the event from the flip itself or from an interrupt its flip sequence contains is not measured; nor is how the console behaves for a title that waits on the flip event and also raises release interrupts on the same queue.

  • Host import windows and budget (libSceAgcDriver): a registered range larger than 64 MiB is imported one 64 MiB window at a time for buffer and fill accesses; an access across window edges imports the cells it touches, grown to the union of the range's imports it overlaps. An address-based build (AcquireRegistered) imports the range whole and replaces its windows. Live imports stay under a budget: APS5_HOST_IMPORT_MIB, else the larger of 6 GiB and half the physical memory, at most the largest host heap less the larger of 2 GiB and half that heap (imports, type-3 mirrors and buffers and textures placed in host memory share it; on a 16 GiB machine whose host heap is half the memory, a 2 GiB reserve left too little for the rest). A new import beyond it retires the least recently used imports not used in the last 256 lookups; one that would not fit even then is refused before anything is retired, and imports made when a range is mapped never evict. A refused import is tried again at its next use. The 64 MiB window, the 256-lookup protection and the 2 GiB reserve are choices, not measured.

  • Registration preparation at submit (libSceAgcDriver) - Driver::Submit waits for every registration-time shader preparation queued before the call, not only those of the shaders the commands use, so a submission behind a long queue of unrelated preparations waits for all of them on the submitting thread. Waiting there instead of in the queue worker keeps the GPU side of a submission short: a title that sees a submission take seconds treats the GPU as hung (Goat Simulator 3, PPSA04159, then stops submitting and its garbage collector fails a 10 s timeout). On the console shaders are compiled ahead of time, so neither wait exists; how long a title tolerates a slow submit call is not known.

  • Resource cache entries holding released textures (libSceAgcDriver) - a sampled texture the texture cache drops while something else holds it, or makes without caching (admitTexture refusing a first use), is marked released, and ResourceCache::Insert refuses descriptor sets that hold one and removes the entries that do. The removal waits for the next insert: until then Find still returns those entries and their textures stay alive. One process-wide flag starts the sweep, so while the sampled cache is over its budget every insert scans all entries (1,024 at start, up to 16,384 after growth, or APS5_RESOURCE_CACHE_ENTRIES, each with its textures); that cost is not measured. When ReleaseTextureCaches runs on device memory exhaustion, the sampled textures these entries hold are only marked released and are freed at the next insert, so the immediate allocation retry gets no memory back from them. A draw or dispatch whose textures the cache refused builds its descriptor sets again on each use for as long as the cache stays full; the count bounds (APS5_RESOURCE_CACHE_ENTRIES, APS5_RESOURCE_CACHE_TABLE_ELEMENTS) still bound nothing by bytes, and sampled textures made outside the texture cache (the disabled cache, depth-surface copies) are never marked. Measured on one title on one RTX 4090: with the default bounds, sampled textures held outside the cache peaked at 2,836 MiB (7,830 MiB before) and device-local heap 0 stayed under its 22.9 GB budget for the whole run (26,357 MiB before, which aborted with VK_ERROR_OUT_OF_DEVICE_MEMORY at the wolf scene). Whether the extra descriptor-set builds cost frame time elsewhere is not measured.

  • Sampled MSAA surface geometry supplies the fragment-aware linear size and one mip for a tiled 2D surface at two, four or eight fragments. Unsupported arrays, mip chains, extents and block sizes throw. Linear descriptors retain the existing geometry path, including its use when sampling an existing native target; a linear MSAA guest layout is not modeled. The range calculation follows AMD Addrlib's thin-block dimensions. Console layout equivalence and title effects of the additional mip metadata remain unmeasured. This does not add sample upload or readback.

  • Unclamped normalized color targets (libSceAgcDriver) - CB_COLOR_INFO targets with BLEND_CLAMP clear (bit 15) on UNORM and SNORM formats are accepted when blending is disabled (ENABLE clear or BLEND_BYPASS set). Vulkan converts unblended shader values and clamps to the normalized range automatically; blending into an unclamped normalized target remains unmodeled because Vulkan always clamps inputs prior to blending.

  • Thick SW_4KB_S_X volumes use AMD addrlib's GFX10 non-RB+ 16-pipe GFX10_SW_4K_S3_X_PATINFO equations for 1, 2, 4, 8 and 16-byte elements, including packed mip tails. The layout follows AMD PAL c5e80007; it has not been measured on console hardware. Other thick swizzle modes outside the supported families still throw. Mipmapped 3D color targets remain refused by the color-register decoder.