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>
153 KiB
libSceAgcDriver technical debt
Silent stubs
- sceAgcDriverRegisterDefaultOwner (libSceAgcDriver) - takes one uint32 owner by value (OpenAGC
include/agcdriver.h, SharpEmuAgcExports.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_SIZEis accepted for lines, triangles and rectangles withGS_ENandHS_ENclear, 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) sets0x01210000on 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
0x8A6C1000without reading their arguments or writing caller memory, andsceAgcDriverGetShaderDebuggingStatusreturns 1, as OpenAGC reads them from Sony's libSceAgcDriver.sprx: each validation export ismov eax, 0x8a6c1000; retin the copy Dragon Quest VII Reimagined bundles (0x7bb0-0x7bf0), and the debugging status is 1 in the firmware 5.50 library (0x6740). The parameters (a 32-bit mode or frequency, a mode output, a configuration pointer) follow OpenAGC and are never read. Whether later retail firmware enables submit validation or shader debugging is unverified; no title in this repository is known to call them -
Indices far past every vertex fetch (libSceAgcDriver) - when a draw's highest index is at least 64Ki vertices past the largest per-vertex record count, every index past that count is moved to it in a copy of the indices, so the zero-padded vertex copies are not sized to the original index (PPSA04159 draws a list of
0xffffffffwith primitive restart off, which asked for 160 GiB). Each attribute fetched by vertex index reads zeros there as at the original index (out-of-bounds select 0 or 1); a draw with another out-of-bounds select keeps its indices. The vertex index the shader sees changes for the moved indices, which a shader that reads it directly would notice -
Depth clear draws (AGC driver) - a draw with
DB_RENDER_CONTROL.DEPTH_CLEAR_ENABLEis taken to writeDB_DEPTH_CLEARto every pixel it covers whatever the depth test, depth bounds and depth bias say: it is drawn with an always-passing depth write and the viewport depth range pinned to the clear value. The draw is rasterized as submitted, so its coverage is the draw's own. Whether the DB still applies the depth test or HiZ to such a draw is not measured -
GS_FAST_LAUNCH geometry (AGC driver) -
VGT_SHADER_STAGES_EN.GS_FAST_LAUNCH = 1with a point list andGE_CNTLgroups of one vertex and one primitive decodes as one point per subgroup, with the primitive limit taken fromGE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTORand 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 withOutputLinesEXT: 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 getsVGT_GS_MAX_VERT_OUT - 1lines where a triangle strip gets- 2triangles. A subgroup gets at leastGE_MAX_OUTPUT_PER_SUBGROUPline 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- 2per input. A fast-launch subgroup takes its line count fromGE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTORas 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_CNTLis 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 = 0xaa01is accepted: the enable bit with the four quad offsets at 2 and noOFFSET_ROUND, the undithered setting radeonsi programs. Draws are single-sampled, so the pixel shader writesSampleMask0 when its MRT0 alpha is below one half or NaN and all ones otherwise, and runs with late fragment tests. That rule was measured on a Radeon 660M (gfx1035, RADV withRADV_DEBUG=noatocdithering, which programs the same register) for 0, -0, 0.25, the two floats below one half, one half, the two above, 0.75, 1, 2, -1, both infinities and both NaNs, and is pinned in AlphaToCoverage.cpp; it is not measured on gfx1013. Vulkan'salphaToCoverageEnableis not used: the NVIDIA driver 615.78.08 dithers single-sample coverage (an alpha of 0.25 writes half the pixels of an RTX 3050 Laptop target). Other offsets,OFFSET_ROUND, a sample-mask export, and an MRT0 that is missing, swizzled, packed or exported without a floating-point alpha throw. A draw without a color target on MRT0 keeps alpha-to-coverage from its MRT0 export, as radeonsi exports MRT0 as32_ARfor 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 ditheredDB_ALPHA_TO_MASK = 0x18701additionally 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 withPA_SC_MODE_CNTL_0.MSAA_ENABLEclear, 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 ruleVK_AMD_mixed_attachment_samplesgives 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 readsPA_SC_AA_CONFIGonly for aSAMPLE_COVERAGEinput (L174-L177). This is not measured on hardware.PS_ITER_SAMPLE, anANCILLARYorSAMPLE_COVERAGEpixel 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 itsPS_ITER_SAMPLEand pixel input bits are ignored. Points, lines and alpha-to-coverage throw as for a multisampled target. With a one-sample depth target,DB_EQAAZ 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_ENABLEwithPA_SC_AA_CONFIG.MSAA_NUM_SAMPLES = 0is drawn like the same state without the bit when sample 0 of the fourPA_SC_AA_SAMPLE_LOCS_PIXEL_*_0registers is at the pixel center and the draw rasterizes filled triangles with alpha-to-coverage and conservative rasterization off. RADV programs this state for one-sample custom sample locations on GFX10 and later, where the bit makes the rasterizer use the programmed location of sample 0 (061bc615); that the bit changes nothing else for such a draw is inferred from that and is not measured on hardware. Other locations of sample 0, absent location registers, points, lines, rectangles, alpha-to-coverage and conservative rasterization throw -
A multisampled target rasterized without MSAA_ENABLE (AGC driver) -
PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES> 0 withPA_SC_MODE_CNTL_0.MSAA_ENABLEclear 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 withMSAA_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_EQAAZ 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, soidentis that context's id (titles register 0 or 1 for graphics and0x20 + 8 * pipe + queuefor compute). The console's value is not known; KytyPS5 returns the event'sdata, which it fills with the interrupt's context id, whereasdatahere is the EV_CLEAR trigger count. Other event filters throw -
User data registers (AGC driver) - a
SPI_SHADER_USER_DATA_*orCOMPUTE_USER_DATA_*register the title never wrote reads as 0 when a stage's RSRC2 count covers it, as Kyty (zeroedUserSgprInfo) and SharpEmu (missing registers read 0) do, and as AMD PAL and Mesa initialize them; the console's value is not confirmed -
SET_PREDICATION (AGC driver) - boolean predication follows the AMD PM4 encoding Mesa uses: operation 3 reads 64 bits and 4 reads 32 bits, and bit 8 runs predicated packets when the value is non-zero (clear: when it is zero); the bit 12 hint is ignored. The address must have its low 4 bits clear, as
sceAgcDcbSetPredicationwrites it. Query predication (Z-pass, primitive count), a predicated COND_EXEC, predicated command buffer chains and flips in predicated command buffers throw -
NGG vertex programs on the mesh path (AGC driver) - an unregistered vertex program with
PRIMGEN_ENthat uses LDS ors_barrierruns on the geometry (mesh) path. Each input primitive is assumed to yield at most one output primitive, the subgroup is bounded byGE_MAX_OUTPUT_PER_SUBGROUPas a vertex budget, and the GS/ES VGPR count checks are skipped, because the merged inputs keep their positions. Taken from the code Mesa's ACO emits for NGG culling and from PPSA59322 and PPSA99010 draws, not measured on hardware. Registered programs keep the vertex path -
VGT_SHADER_STAGES_EN 0x02002008 (AGC driver) - this one value,
ES_EN= 1 withoutHS_ENon a non-patch draw, is drawn as the vertex path. AMD's CIK register reference callsES_EN= 1ES_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 withES_ENset 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_recordsof the last record (RADV rounds it up), which no fetch inside the buffer reads; a guest that really keeps a vertex split across two adjacent allocations would read zeros for the cut tail. Taken from the RetroArch Dolphin core case in #2912, not measured on hardware. -
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 inShaderResources::addGuestBuffer. On GFX10 (gfx1013), bit 30 of V# word 1 isCACHE_SWIZZLE(Mesa'sgfx10-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
maxStorageBufferRangeis 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 bindsc8010002 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,sceAgcDriverFindResourcesPublicandsceAgcDriverUnregisterAllResourcesForOwnerreturn the same error without reading their arguments. Their parameter lists follow the libSceGnmDriver counterparts and are unverified.sceAgcDriverGetResourceRegistrationMaxNameLengthwrites 0xfc and returns 0, as prosper does from a title's registration configuration; theuint32_t*parameter matches the SDK wrappersce::Agc::ResourceRegistration::getMaxNameLength(unsigned int*). sharpemu writes 256 and KytyPS5 returnsRESOURCE_REGISTRATION_UNAVAILABLE, so the value is unverified; a null output throws -
sceAgcDriverRegisterMultipleResources uses the existing unavailable-registration state and leaves caller memory untouched. Its four arguments (uint32 handle output, uint32 owner, descriptor pointer, uint32 count) follow the original PPSA34502 / 01.010.000 codzm call. The caller builds 40-byte descriptors and explicitly accepts 0x8A6C9018 with an invalid output handle. Enabled registration and console argument-validation precedence are unverified.
-
Shader headers (libSceAgcDriver) - registration and ABI resolution read fixed header fields through aligned copies, so the header pointer itself may have any alignment. PPSA26344 passes headers 4 and 1 bytes past an 8-byte boundary, and PPSA01317 passes 4-byte-aligned headers to ABI resolution; console alignment requirements are unknown
-
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_barrierare prepared with the draw's routing and primitive registers on the existing mesh path. The workgroup requirement follows Mesa's NGG lowering; the mesh path supplies LDS and two guest lanes per host invocation for wave64 on a 32-lane host subgroup. Console routing has not been measured. Passthrough and tessellation keep their existing paths; unsupported mesh assembly and subgroup budgets throw. -
Shader header copies (libSceAgcDriver) - a header bound for a draw or dispatch at another address than the registered one is accepted when its fixed fields match the registered header except
user_data; the registered header's metadata is used. PPSA21567 binds such a copy of a vertex shader header withuser_dataset to null. Whether the console reads the copy'suser_datais unknown -
HtileFillCovers (libSceAgcDriver) - a fill is taken to cover a depth surface's whole HTILE when it spans 32 KiB per 1024x512 pixel block from DB_HTILE_DATA_BASE, the pipe-aligned SW_64KB_Z_X metadata extent AMD PAL's gfx10 addrlib gives for GB_ADDR_CONFIG 0x00100044 (1-8 samples, D16/D32); other metadata layouts are not decoded. A partial fill is ignored (the surface keeps its contents); not measured on the console.
-
Depth fast clear through HTILE (libSceAgcDriver) - a surface cleared by an HTILE fill is cleared to DB_DEPTH_CLEAR, also when it is next sampled. For a depth-only HTILE the clear word also carries a value (radv writes
minz = maxz = round(depth * 0x3fff),HTILE_Z_CLEAR_REGin Mesa'ssrc/amd/common/ac_descriptors.h, and allows TC-compatible fast clears only to 0.0 or 1.0); which of the two the texture unit returns for a cleared tile was not measured. -
Depth-only and stencil HTILE fill (libSceAgcDriver) - in a whole-HTILE fill, ZMask (bits 0-3) 0 clears the depth and SMem (bits 8-9) 0 clears the stencil, while SMem 3 leaves the stencil alone (
HtileFillClears); other SMem values are taken to leave it alone too. The bit meanings follow the gfx10 HTILE layout as the code reads it, not a measurement;agc_driver_depth_fast_clear_submitpins them for the paths the Driver takes. -
Depth and stencil plane fills (libSceAgcDriver) - a fill that covers a known depth surface's whole stencil plane (
DepthSliceBytesof 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_fillpins the rules. -
HTILE address of a depth array slice (libSceAgcDriver) -
decodeDepthadds theDB_DEPTH_VIEWslice offset to the Z and stencil addresses but not tohtileAddress, so every slice of a layered depth target shares slice 0's HTILE address and a fill of slice 0's HTILE marks all slices cleared. Astro's Playroom fills the HTILE of each of its three shadow cascades 0x40000 apart (0x24000 bytes of tiles each) back to back before drawing, so the result is still right there; a title that clears one slice between draws to another would lose depth. The per-slice HTILE stride is not derived yet. -
sceAgcDriverGetHsOffchipParam (libSceAgcDriver, NID
r28hEh6cNH0) - declared as a throwing stub. The twouint16_toutput pointers and integer return type follow ps5-vulkan, which reads the pair before it binds a tessellation factor ring. The meaning of the two values, their initial state and how they relate tosceAgcDriverSetHsOffchipParam, which keeps nothing here, are unknown; the stub does not write caller memory. PPSA99011 imports it -
Vertex input and color target overlap (AGC driver) - direct draws exclude the descriptor prefix skipped by
firstVertexorfirstInstancefrom the alias check; indexed draws conservatively use index zero as the lower bound before addingfirstVertex. 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 reservesTextureCacheBudgetfor 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_TYPE21 (PrimitiveType::Polygon) decodes toVK_PRIMITIVE_TOPOLOGY_TRIANGLE_FANand 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_STENCILREFMASKandDB_STENCILREFMASK_BF(context0x10b-0x10d), including afterCLEAR_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 uses0x01000000for 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_loadandimage_get_resinforeturn 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_exportsupports 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# inuser[8..11]stored to every texel of the T# inuser[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 withoutVK_EXT_depth_range_unrestricted.agc_driver_depth_storage_dispatchpins 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# whoseBASE_ARRAYis past slice 0 reads slicesBASE_ARRAYtoLAST_ARRAYof the copy, which holds slices 0 toLAST_ARRAY, and a 2D binding of it reads sliceBASE_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, aBASE_ARRAYpastLAST_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_UFLOATimage 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 packedR32_UINTpath. -
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 forSW_64KB_R_Xtargets 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_UINTwith 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_SWAP1 holds the alpha export, but Vulkan blends it with the color equation of the R8G8 attachment, so a draw whose alpha equation differs from its color equation is recorded twice, the first channel with the color equation and the second with the alpha factors and operation moved into the color equation. The vertex stages run twice, so a vertex shader that writes memory writes twice, and occlusion counts include both passes. Draws that write depth or stencil, pixel shaders that write memory and factors of the destination alpha throw. That the hardware blends the second channel with the alpha equation is taken from Mesa'ssi_translate_colorswapand radeonsi's use of it for luminance-alpha formats, not measured on hardware. -
Unexported pixel inputs (libSceAgcDriver): a pixel input whose
SPI_PS_INPUT_CNTLparameter the previous stage does not export is programmed as a default-value input (OFFSETbit 5,DEFAULT_VAL0, FP16 high half too) and reads (0, 0, 0, 0), as radeonsi'sSI_PS_INPUT_CNTL_UNUSEDdoes and as rect-list draws already do. The console's value for an unwritten parameter cache entry is not measured on hardware. -
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 lackssampleRateShading. 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 ofSampleId, 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_X1Y0andPA_SC_AA_MASK_X0Y1_X1Y1hold one 16-bit sample mask for each pixel of the aligned 2x2 quad (Mesa gfx10.json); only the bits of theMSAA_NUM_SAMPLESrasterized 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 VulkanpSampleMask, the case radv and radeonsi program; with early fragment tests it needsearlyFragmentSampleMaskTestBeforeSampleCounting(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 bygl_FragCoordparity 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 intogl_SampleMask(with alpha-to-coverage and the mask export) after demoting invocations whoseSampleMaskinput 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_DTMN0) 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 topSampleMaskis implementation-defined, and any partial mask is refused with conservative rasterization (radv setsPREZ_AA_MASK_ENABLEandPOSTZ_AA_MASK_ENABLEthere, 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 + 1dwords, 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 withoutshaderBufferInt64Atomics), shared by all such dispatches and grown in 1 MiB steps up tomaxStorageBufferRange; a dispatch whose slices do not fit throws. It is slower than shared memory and its cost was not measured. An indirect dispatch of such a program always reads its group counts on the CPU. Its results are pinned against the in-limit semantics inagc_driver_lds_beyond_device_limit, not measured on hardware. -
AGC buffer allocations retry host/device out-of-memory once after reclaiming idle pool buffers and fully unused slab blocks. Live buffers and blocks with live slots are retained. Live images and other retained resources have no global memory bound; allocation failure still throws when reclaim cannot satisfy the request.
-
Host placement after a device-memory refusal (libSceAgcDriver): when the driver refuses a device-local allocation with
VK_ERROR_OUT_OF_DEVICE_MEMORYafter 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_ENTRIESpipelines 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 pastAPS5_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 layoutDescribeSetLayoutsderives from the result's bindings, which a dispatch that finds it resident checks against itsShaderResourceslayout only underAPS5_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,CanonicalPipelineStateor 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_EXTon a background thread, and binds the optimized one from the next draw on. Draws recorded before that use the fast link, whose vertex stage keeps the outputs the fragment stage does not read: on RADV (Radeon 660M, Mesa 26.2.4) 346 of 483 Astro's Playroom vertex shaders paired with a fragment stage that reads none of them used more VGPRs (median 8 more, up to 2.5 times), and on an RTX 4090 13 of 15 PPSA36082 vertex shaders up to 68 instead of 38 (#2242). The optimized link compiles to the same code as a whole pipeline -
Prebuilt pipeline libraries (libSceAgcDriver): each registered vertex or pixel shader in registration form gets its pre-rasterization or fragment-shader library built on one background thread, with a fixed state the draw may not use: triangle list, counter-clockwise front face, no depth clamp, depth bias or conservative rasterization, first provoking vertex, one sample. Libraries are kept until the device is torn down; on Astro's Playroom 1948 of them added 0.44 GB of peak memory on RADV.
-
APS5_THREAD_PRIORITY=rt(libSceAgcDriver) lowers the process's hardRLIMIT_RTTIMEwhen it is aboveAPS5_THREAD_RTTIME_USor rtkit'sRTTimeUSecMax, because rtkit (verify_process_rttime) reads the hard limit from/proc/<pid>/limitsand refuses real-time scheduling when it is higher. An unprivileged process cannot raise the hard limit again, so it stays lowered for the process and its threads even when rtkit then refuses the request (burst limit, user or start-time checks) and the thread throws. rtkit'sMaxRealtimePriorityandRTTimeUSecMaxare read first, so an unreachable rtkit or a priority above its maximum leaves the limit untouched. The lowered limit only matters for threads that later run under a real-time policy -
AMD image descriptors with
TYPE=0are treated as null for read-only image slots even when the base and other words are nonzero:image_loadon 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=1is treated as null in the same read-only slots: gfx1035 reads it as zeros too, and gfx1010 returns zeros forimage_sample_lz,image_sample_b,image_loadandimage_get_resinfowith 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, asnullDescriptorallows, but stores and atomics to it are kept, so a later read of a null storage image can return them, and a depth comparison compares against depth 0. A heap whose placeholder the device cannot create, for example 1D or 3D depth, 64-bit atomics without 64-bit image atomics, or multisampled storage withoutshaderStorageImageMultisample, throws when a null descriptor reaches it. -
Draw input snapshots (libSceAgcDriver):
PrepareDrawBindingscaps read-only input buffer snapshots at 64 MiB (overridable viaAPS5_DRAW_SNAPSHOT_MAX_MIB). Larger inputs (streaming heaps, bindless pools) or draws exceeding the cumulative budget skip the per-draw snapshot and bind in place, avoiding multi-gigabyte CPU copy stalls (such as Ratchet & Clank: Rift Apart PPSA01474 stalling at frame ~380 copying 36 GB every 5 s). If the title rewrites those large ranges on the CPU before GPU completion, the in-place binding observes the modification, as verified bydrawSnapshotCapTests. -
Pending storage-image results (libSceAgcDriver) are stored when something reads their memory, not when the GPU work that wrote them completes. A title that waits for that work, frees the image and reuses its memory can have the new data overwritten by the late store (#1970). 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 andfwrite) synchronize their source ranges through the host-read hook; other host readers are not covered.APS5_STORE_AT_FLIP=1stores every pending result at each flip andAPS5_STORE_AT_LABEL=1before 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_WATCHmemory 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_WATCHmemory 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 underGpuMutexwhile 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'ssize_limit_mbrefuses 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_UNNORMALIZEDuses a VulkanunnormalizedCoordinatessampler, so the S# LOD range, LOD bias and mip filter are not applied, which is exact only on a one-level view. -
An unnormalized S# (libSceAgcDriver) 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_LTOdisables the relink on every driver. -
8_SRGB and 8_8_SRGB textures (libSceAgcDriver) that the device samples as R8_SRGB or R8G8_SRGB use its own decode, which need not match gfx1035: NVIDIA's R8_SRGB is one bf16 step off in 29 of 256 codes. 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'sSleep(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 byAPS5_STALL_WATCHDOG_SECS. -
WAIT_REG_MEM reports a diagnostic after
APS5_GPU_WAIT_TIMEOUT_MSwithout treating the timeout as completion. An unsatisfied wait keeps polling and checking driver failures; a missing producer can therefore leave its queue waiting indefinitely. Existing recorded-label shortcuts remain in use. -
COND_EXEC reads its condition after the earlier packets of its queue complete. Without PFP_SYNC_ME the console's graphics PFP reads it ahead of the ME, so a title that races the read sees a later value here.
-
A flip inside a COND_EXEC range throws: the flip is reserved with the video output when the buffer is submitted, and how the console withdraws a skipped one is unknown.
-
A COND_EXEC range must hold whole packets and end inside its command buffer. A chained INDIRECT_BUFFER or a REWIND inside a range throws, as what the CP does with the skipped dwords then is unknown.
-
A COND_INDIRECT_BUFFER (
sceAgcCbBranch) runs only with the always-pass compare function; a comparison or a predicated packet throws. Modes 1 and 2 run the first buffer, assumed to be the if-then and if-then-else modes of AMD's PM4 packet. sceAgcCbBranch does not check the compare address of an always-pass branch or the address of an empty buffer, as PPSA14632 passes an uninitialised compare address and a null else buffer of 0 dwords. -
A label or store (
RELEASE_MEM,WRITE_DATA,DMA_DATA) into guest memory the device has no host import of waits for all recorded work and is then written by the CPU, so the queue worker stalls once per such label. The console's CP stores it in stream order without that stall. -
WRITE_DATA 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 GFX10SQ_THREAD_TRACE_USERDATA_0..7definitions 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 thatRELEASE_MEMandWAIT_REG_MEMthen use; the console must write that label or the title would wait on it forever. Whether the console routes it through the GDS or a cache is unknown -
SET_*_REG_INDIRECT (libSceAgcDriver) reads its register list when the command buffer is submitted, with the packets. The console's graphics PFP reads it when it reaches the packet, so a list written after the submission (by the CPU, or by GPU work ahead of the packet behind PFP_SYNC_ME) is read here before that write.
-
The shader float mode (
FLOAT_MODE,DX10_CLAMP,IEEE_MODEandFP16_OVFLofCOMPUTE_PGM_RSRC1andSPI_SHADER_PGM_RSRC1_PS/GS/HS, bit positions from Mesa'sgfx10.jsonandgfx103.json) is decoded intoGuestContext::floatModeand is part of the shader cache keys and the serialized request (#981).IEEE_MODEis read byv_min_f32,v_max_f32,v_min3_f32,v_max3_f32andv_med3_f32, their f16 forms,v_min_f64andv_max_f64,IEEE_MODEand theFLOAT_MODEoutput denormal bits by the output modifier,DX10_CLAMPby the clamp modifier,FP16_OVFLby every instruction with an f16 result (see its entry), and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous f32 lowering, which matches IEEE=0. The f16 forms choose their NaN results on the bits: a NaN operand returns the other operand unchanged (src0 when both are NaN), and with IEEE=1 a signaling NaN operand returns that NaN quieted, in the same order as f32.v_min_f64andv_max_f64quiet a signaling NaN operand the same way with IEEE=1; with IEEE=0 they return src0 unchanged when src1 is NaN, otherwise src1 when src0 is NaN. Both were measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f32 denormals kept and flushed and f16/f64 denormals kept; RDNA2 has no f64 min3, max3 or med3. Unknown modes follow IEEE=0.v_min_f16andv_max_f16flush each denormal input to its signed zero when bit 0 of the f16/f64 denormal field is clear (0 and 2); fields 1 and 3 preserve inputs, even when output flushing is enabled. Measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1, including signed zeros, NaNs and neg/abs modifiers. Unknown modes keep f16 denormals. With f16/f64 denormals flushed the hardware also flushes denormal inputs of f64 min/max, which those lowerings still do not. NaN quieting in f16 arithmetic (the unary instructions and the f32 arithmetic follow it, see below) andFLOAT_MODE(f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header'ssh_registers, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none. -
A WAIT_REG_MEM (libSceAgcDriver) that a compute queue passes on another queue's recorded label store, before the GPU has executed it, is landed before the queue's next dispatch, draw, COND_EXEC or predicated packet reads guest memory: the worker polls until memory satisfies the wait or no unfinished batch writes the label. The graphics queue (queue 0) is exempt, as landing its waits costs PPSA01341 about a quarter of its frame rate; a title that fills memory a graphics command buffer reads after it submits the buffer behind such a wait reads it early there.
APS5_LAND_WAITS_ALL_QUEUES=1lands the graphics queue's waits too. -
The dispatches that follow a compute queue's wait (libSceAgcDriver), up to the next wait that memory does not satisfy, a packet that writes guest memory other than a label, or a conditional packet, are resolved (memory captured, shader recompiled) when the wait passes, as the CP reads them right after it; the graphics queue is exempt (
APS5_GROUP_CAPTURE_ALL_QUEUES=1includes it). The device still reads guest memory when the dispatch is issued; when that memory no longer matches the capture, the dispatch is resolved again from current memory. -
PA_CL_CLIP_CNTL (libSceAgcDriver) -
DX_LINEAR_ATTR_CLIP_ENA(bit 24) clear is drawn like bit 24 set, with Vulkan's clipping ofNoPerspectiveoutputs; what a clear bit changes on the console is undocumented. -
10_11_11 unorm color targets (libSceAgcDriver) render into R32_UINT attachments: the pixel shader packs each export, rounded to nearest even like format-30 image stores, which is assumed to match the CB's conversion.
-
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_firstmapping; 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 withoutVK_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_f32andv_interp_p2_f32read P10 and P20 against the guest's I and J, which come fromgl_BaryCoordKHRin 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 throughv_interp_mov_f32P10 and not P20 returns the provoking vertex's value, like P0. ps5-opengl reads every flat input that way, and its driver passes the GL 4.6 CTS on a PS5 (#1413). The lane itself was not measured on the console, so with the first provoking vertex such a shader throws. -
Dual-source blending (libSceAgcDriver) takes the MRT1 export as the second source of the MRT0 blend, as RADV and radeonsi program it; what the CB does with other export or target layouts is unknown, so they throw.
-
Blending into reversed-order color targets (libSceAgcDriver) - 8_8_8_8 and 16_16_16_16 targets with
COMP_SWAP2 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 applyCOMP_SWAPafterwards: the alpha equation and every source-alpha factor use the exported alpha, and the destination alpha is the first channel. This follows theCB_TARGET_MASKtext 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 buildsCB_BLENDn_CONTROLwithout regard to the swap (radv_pipeline_graphics.c) and renders R4G4B4A4, B4G4R4A4, R5G5B5A1 and B5G5R5A1 withSWAP_STD_REVandSWAP_ALT_REV(vk_format.c, ac_formats.c). Mesa CI's Navi10 (gfx1010) CTS run (job 112381553, 112381554) passes all 400blend.format.<format>.statescases 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 throughSRC1_COLOR/SRC1_ALPHAfactors. 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 aSWAP_STDtarget, which the test checks. Factor combinations that copies cannot express (for exampleONE_MINUS_SRC_ALPHAin the alpha equation withSRC_COLORin the color equation), sRGB, SNORM and integer targets, constant and second-source factors, destination-alpha andSRC_ALPHA_SATURATEfactors 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 withoutdualSrcBlendthrow. Gran Turismo 7 (PPSA01317) blendsCB_BLEND0_CONTROL0x65010504 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
FragStencilRefEXTwhen 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 whatREPLACE_TESTwrites, so a stencil operation that writes all ones or the operation value throws with it, as do a stencil clear draw, the UINT16_ABGR layout andSTENCIL_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 reachableREPLACEthrough 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 doesZ_ORDEREARLY_Z_THEN_LATE_ZorEARLY_Z_THEN_RE_Zwithout kill, Z export, mask export or stencil export; with alpha-to-coverage the tests stay late. The meaning of the bit and ofZ_ORDER3 follows Mesa (src/amd/registers/gfx10.json; RADV sets the bit forearly_fragment_testsinsrc/amd/vulkan/radv_shader.c). What the console does withDEPTH_BEFORE_SHADERand 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_ENABLEandUNDER_RAST_ENABLEclear is decoded as disabled even whenUNCERTAINTY_REGION_MODEor theOUTER_/INNER_UNCERTAINTY_EDGERULE_OVERRIDEbits (19:16) are set, as PPSA01474 writes 0xe6000; that these fields have no effect without over- or underestimation is assumed from their names, not measured. WithPA_SC_AA_CONFIG.MSAA_NUM_SAMPLES= 0 thePERSP_CENTROIDandLINEAR_CENTROIDinputs 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_SETpool place a set up to 32 bytes past the pool's buffer (util_vma_heap_initis givenbo_size + RADV_POOL_HEAP_OFFSETas a size), and with a page-multiple pool size those bytes are an unmapped page -
CMASK fast clears (libSceAgcDriver) - each fast-cleared CMASK tile (code 0) is stored as
CB_COLOR_CLEAR_WORDinto its 8x8 texels at the first draw into the target or at its fast-clear eliminate or DCC decompress pass, not as the color block writes the tile, and the CMASK is then stored as expanded (0xF): a texture or copy read of the target after that draw and before the title's eliminate sees the clear value in tiles no draw wrote, where the console keeps their previous bytes. Tile addresses follow the pipe-aligned SW_64KB_Z_X CMASK equation for 16 pipes; other CMASK swizzle modes, 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 AddrlibGfx10Lib::HwlComputeCmaskInforeverse mip-chain ordering preceding base mip 0, unverified on PS5 hardware; mipmapped targets with 8 or 16 bpp throw untildsMipmapHtileFixmip-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 withviewMip > 0skip CMASK fast-clear decoding since CMASK metadata only covers base mip 0 on AMD hardware (Mesasi_state.csimilarly clearsFAST_CLEARfor level > 0). -
HTILE depth fast clears (libSceAgcDriver) - HTILE is not modeled. Only RADV's
clear_htile_maskcompute kernel (17 GFX10 words, 64x1x1 groups) is recognized, and only when it leaves ZMASK at 0 (user[4]anduser[5]low nibbles both 0); the dispatch still runs, and the host depth image is cleared toDB_DEPTH_CLEARthe next time the surface with thatDB_HTILE_DATA_BASEaddress is bound. The address match is exact, so a clear of a different HTILE range does not hit. HTILE initialization, the stencil-only update, and fast clears made by the SDK's own paths, CP DMA fills or other kernels are not covered and leave the depth image as it was. The kernel words are the ones the PR author saw RADV submit; they are not compared with Mesa's source. A second kernel, the 9-word dword fill PPSA01802 (Kena: Bridge of Spirits) clears its depth HTILE with (64x1x1 groups, onebuffer_store_format_xper thread through the stride-4 V# inuser[0..3], the value read bys_buffer_load_dwordthrough the V# inuser[4..7]), is taken as a fill of that range when the store format is 32_UINT, 32_SINT or 32_FLOAT and the value V# covers the dword (NUM_RECORDS times its stride, or NUM_RECORDS bytes when the stride is 0) (MatchMemoryValueFill, then the whole-HTILE and ZMask rules ofNoteDepthMetadataFill). The value is read from guest memory when the dispatch is decoded, so a value that earlier GPU work in the same submission has not written back yet is not seen. 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 extentHtileFillCoverstakes) 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 (InvokeHTileClearComputeShaderin itsClearMRTpass); 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_BASEplus the slice timesHtileMetadataBytes) 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 toDB_DEPTH_CLEAR, the value the DB's depth test reads for a cleared tile (HTILE_Z_CLEAR_REGin Mesa'ssrc/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 againstDB_DEPTH_CLEARinstead. 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_hostimports of read-only or executable mappings (and shared mappings of regular files) withVK_ERROR_OUT_OF_DEVICE_MEMORY, after which everyvkAllocateMemoryon the device fails the same way. Read-only ranges are never imported, even on drivers that accept them. Shared mappings of regular files are not checked for. On Windows, an alias of adjacent registered ranges is refused if any page is not a shared view; a range made entirely of shared views is aliased run by run, including across sections or nonconsecutive section offsets. Adjacent registered ranges are joined into one import 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_ARRAYandDEPTHexpose six faces starting atBASE_ARRAY. Measured withimage_load,image_sampleandimage_get_resinfoon RX 6600 (gfx1032), using 64x64 R32_UINT faces and base slices 0, 5 and 6. Other ranges containing fewer than six faces or incomplete cube arrays still throw: hardware load bounds and filtered-sample addressing differ. Out-of-range cube-array sampling remains unimplemented. Samplers withDISABLE_CUBE_WRAPare created withVK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT, the flag RADV maps to that bit (Mesasrc/amd/vulkan/radv_sampler.c,src/amd/common/ac_descriptors.c); withoutVK_EXT_non_seamless_cube_mapa cube texture sampled through such a sampler throws. PS5 equivalence has not been measured on console hardware. -
Array layer views (libSceAgcDriver) -
ArrayLayersServeViewlets 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 toCB_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_CONTROLmode 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
maxSampleLocationGridSizefor 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 positionSPI_BARYC_CNTLnames. 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 withSAMPLE_LOCATIONS_COMPATIBLE_DEPTHwhen 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 andPA_SC_MODE_CNTL_0.MSAA_ENABLEclear 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 thisMSAA_ENABLEandPA_SC_AA_CONFIGwith 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 setsDB_EQAA.MAX_ANCHOR_SAMPLESto the Z sample count, where PPSA18216 setsDB_EQAAto 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_EQAAZ 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
0xC001000622000000and0xC001000600000000are taken as R16G16B16A16 and B16G16R16A16 float, eight bytes per texel, as KytyPS5 maps them and SharpProspero names them (Rgba16Float,Bgra16Float); PPSA11386 registers 3840x2160 tiled buffers of the first. The tiled layout is assumed to be the 8-byte SW_64KB_R_X equation ofTextureSwizzleEquations.hppapplied to full coordinates, as addrlib applies it, by analogy with the 32-bit scanout;ColorTargetLayoutapplies the equations to in-block coordinates, which for 8-byte texels differs in every odd 64-row block row (address bit 11 takes y bit 6) and for 16-byte texels also differs, so a float display buffer written throughWriteColorTargetand presented from guest memory would show those rows misplaced. The values are assumed to be linear light and are encoded with the sRGB curve for the 8-bit output. Bits 32-39 of these formats are0x06, as in the 10-bit formats SharpProspero lists without sRGB transfer, while its sRGB formats have0x00, its 10-bit PQ formats0x04(with0x07in bits 40-47) and its float BT.2100 PQ formats0x07: they show only that these formats are not the sRGB ones. NaN and negative values give 0, values of 1 and above give 255, alpha is linear. Neither the layout nor the transfer is verified against console or title frames, and HDR output is not modelled. DCC metadata on a float buffer throws. -
BT.2100 PQ display buffers (libSceAgcDriver, libSceVideoOut) - pixel formats
0x8100070422000000and0x8100070400000000are 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, withVK_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_KEYSprints the length of the fills classified as keys, not of the refused ones). -
Comp-to-single DCC keys (libSceAgcDriver) - key 0x10 (
GFX9_DCC_CLEAR_SINGLEin Mesaac_descriptors.h) uniform over a surface makes every texel the element at the start of its 256-byte block, the top-left texel of the block where radv'sbuild_clear_dcc_comp_to_single_shaderstores the clear color. Sampled textures and storage images read that expansion (a storage image's first write stores the keys uncompressed, as under a clear code), and a CB DCC decompress pass stores it over the texels. A fast-clear eliminate over such keys throws, as a metadata pass over mixed keys does; color targets that render over such keys use their stored texels, and textures read mixed keys, and comp-to-single keys of a format whose element does not divide 256 bytes, as stored. "Uniform" is judged over the key bytes the driver reads, one per 256 surface bytes (see DCC key stores above), so a pipe-aligned surface whose keys differ past that range is expanded as if all were comp-to-single, as for the other clear codes. Not verified on hardware -
8_SRGB color targets (libSceAgcDriver) on a device that cannot attach and blend R8_SRGB render into an RGBA8_SRGB copy of the target, so the sRGB encode, decode and blend rounding are the device's; the console's are not measured.
-
8_SRGB color targets (libSceAgcDriver) on such a device throw unless they are single-mip 2D surfaces with a resident image; linear targets have none.
-
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_colorswapin Mesa 26.2.2 ac_formats.c, which programsSWAP_STD_REV(2) for a surface with red in bits 11-15. WithSWAP_STD_REVred goes to bits 11-15, R5G6B5's red, so the exports are written as they are; withSWAP_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_ALTandSWAP_ALT_REVkeep 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 withSWAP_STDgo through the0xc6component-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 withSWAP_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_5doc comment and as measured for textures in the packed 16-bit textures entry), while A1R5G5B5 keeps R in bits 10-14 and B in bits 0-4, so the pixel exports are written with X and Z exchanged, which makes the stored bits follow the guest layout. The decoded format, component mapping and write mask are tested, and an execution test draws 32_ABGR exports into tiled and linear targets and compares every stored texel with the guest layout on the host device; the export direction is not measured on a PS5. Only UNORM with the standard component swap is accepted. Constant blend colors go through the same component-swap handling as other targets with that export mapping (0xc6), which is not tested for this format. PPSA06699 renders into this format. -
COPY_DATA from the GPU clock (libSceAgcDriver,
SRC_SEL9) writes the host steady clock in 10 ns ticks when the packet runs, the same counter as theRELEASE_MEMtimestamp, not the console's GPU clock. TheCOPY_DATAcache policy fields are accepted and have no effect. -
COPY_DATA from a register (libSceAgcDriver,
SRC_SEL0) copies the value the queue holds for a shader, context or user config register (COUNT_SELtwo consecutive ones) and throws for a register with no value. A dispatch recordsCOMPUTE_DISPATCH_INITIATORand, forDISPATCH_DIRECT,COMPUTE_DIM_X/Y/Zin the shader registers;DISPATCH_INDIRECTclears 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-3andSAMPLE_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, orEVENT_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'sIndexAttribIndirectand the packet is the oneBuildIndexAttributesIndirectwrites; neither is measured on a PS5 or seen in a title.indirect_mode1 (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_SWAP1) component order. Alternate swaps red and blue in shader exports, write masks and constant-color blend factors, followingac_translate_colorswapin Mesa 26.2.2 ac_formats.c. Blending into the reverse orders (COMP_SWAP2 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_VIEWslice 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_CNTLUSE_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 toSLICE_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_5and4_4_4_4are viewed as R5G6B5, A1R5G5B5 and B4G4R4A4 (the 4-bit format every Vulkan device can sample; R4G4B4A4 is optional and lavapipe lacks it) with a swizzle that reads X from the low bits, and a DST_SEL of the missing W of5_6_5reads X. Measured on RDNA2 (gfx1035), not on the console: throughimage_loadandimage_sample_lz, X, Y, Z (and W of1_5_5_5and4_4_4_4) come from the low, middle and high fields, and W of5_6_5returns X (the low 5 bits), not 1. -
Min/max S# reduction (libSceAgcDriver) -
FILTER_MODE1 and 2 use Vulkan's min and max reduction (the sameSQ_IMG_FILTER_MODEvalues RADV programs). Measured on RDNA2 (gfx1035), not on the console: only texels of non-zero weight are reduced, and the weight is taken after rounding to the 8-bit subtexel grid (a sample within 1/512 texel of a centre reduces that texel alone); a driver with othersubTexelPrecisionBitsreduces a different set there. Gathers ignore the mode. -
Min/max S# (libSceAgcDriver, shader recompiler) - throws with 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_8and8_8_8_8sRGB textures sample bit for bit the same with and without the bit (point and bilinear; BC formats not measured). Other textures throw; on the same hardware their UNORM8,8_8and8_8_8_8forms sample bit for bit as the sRGB format (decoded before filtering). -
Tessellation factor ring (libSceAgcDriver) -
sceAgcDriverSetTFRingrecords the ring andsceAgcDriverGetTFRingreports it. Before a title sets one, it reports the console's default, 0xFF0000000 and 128 KiB, as measured in PS5_Vulkan's hardware findings. Nothing is written to the recorded ring: tessellated draws run as Vulkan tessellation stages, which keep their factors on the host, so a title that reads tessellation factors from the ring finds none. A null base or a zero size throws; ps5-opengl's AGC runtime callssceAgcDriverSetTFRing(0, 0)in its cleanup, and what that does on the console is unknown. -
VertexBufferReadSize and the vertex staging copy (libSceAgcDriver) - a vertex fetch whose index is at or past the descriptor's
num_recordsno longer throws forOOB_SELECT0 and 1. The copy still spans the whole fetch, but the bytes fromnum_records * strideonward read as zeros, so the out-of-range vertex is(0, 0, 0, 0). Measured withbuffer_load_format_xyzw(idxen) through the hardware oracle on RDNA2 gfx1036 for PPSA01325's word 30x0004dfac(OOB_SELECT0) in #2241: indices 3, 4 and 5 over 3 records each return00000000 00000000 00000000 00000000, W included, unlike VulkanrobustBufferAccess, which returns(0, 0, 0, 1). The zeros come from the record check, not from memory: stored values placed past the records are ignored, and anum_records6 control returns them.RESOURCE_LEVEL1,OOB_SELECT1 and FORMAT 7432_32_32_FLOATgive the same zeros, in wave32 and wave64; gfx1013 (the console) is not measured.OOB_SELECT2 returns the stored values past the records andOOB_SELECT3 comparesnum_recordsagainst the byte offset, both measured on the same setup in #2241; the two keep throwing pastnum_recordshere, since each needs its own rule. Zero-stride descriptors (stride0) keep their separate byte-count check and are not covered here. Each out-of-range fetch is copied alone and outside the draw-snapshot cache, which is neither read nor written for it: a reused snapshot would carry another draw's bytes into the zero region, and a stored zero-padded copy would hand zeros to a later in-range draw at the same address. The valid prefix is still flushed for GPU writes before it is read. -
Draw snapshot buffer descriptors rewrite each affected array from element zero, preserving unchanged descriptors. MoltenVK 1.4.2 writes auxiliary buffer lengths at index
iinstead ofstart + ifor partial array updates (MVKDescriptorSet.mm, BufferAuxSize). Per-element writes therefore let one buffer length replace another even when Vulkan buffer handles and ranges are correct. -
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.PresyncSurfaceswalks 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_b64whose source pair is a user-data pair, or a copy of one made withs_mov_b32ors_mov_b64in the same block, calls the code at the address that user data holds. At dispatch the callee is read from guest memory up to itss_setpc_b64on the link pair, one copy of it is appended per call after the caller's last instruction, and the call is rewritten tos_call_b64to that copy. The words after the caller's last instruction are dropped 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_b64whose source pair is two consecutive dwords of ans_buffer_load_dword*, or a copy of them made withs_mov_b32ors_mov_b64in 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 ans_load_dwordx4without SOFFSET from a user-data pointer, or from a pointer pair that twos_mov_b32of a literal or inline constant set, and the load's SOFFSET from ans_lshl_b32by 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_u32with a literal ands_cbranch_scc1per dword) ands_call_b64s the copy that matches; no match runs the originals_swappc_b64as 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, 0ors_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 fors_buffer_loadwas not measured. More than 64 distinct targets, a buffer above 64 MiB or a copy out ofs_call_b64range 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_b64on the link pair). Sony's routines, which order work before a title'sds_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_RECORDS0,DST_SEL0 selects 0), not measured on hardware. -
Descriptor pool recycling avoids individually freed sets: RADV 26.2.2 initializes its free-set heap with 32 bytes beyond the backing allocation. An allocation-only RX 6600 test reproduces acceptance of a 4,128-byte set in a 4,096-byte pool; the path without individual frees rejects it. Pools reset after their last set is released. A cached set can retain unused slots in an exhausted pool until eviction; peak gameplay pool memory remains unmeasured.
-
16-bit depth with stencil (libSceAgcDriver) is stored as
D24_UNORM_S8_UINT, orD32_SFLOAT_S8_UINT, on a device withoutD16_UNORM_S8_UINTas an attachment. Depth comparisons, clears and the depth image see the wider format's precision, so results at the 16-bit quantization boundary can differ from the console; the constant depth bias is scaled by 256 for D24, and a non-zero constant on the D32 float fallback throws. Not measured on console hardware. -
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 fromDB_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 aRELEASE_MEMwithINT_SELdoes, 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::Submitwaits 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 (
admitTexturerefusing a first use), is marked released, andResourceCache::Insertrefuses descriptor sets that hold one and removes the entries that do. The removal waits for the next insert: until thenFindstill 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, orAPS5_RESOURCE_CACHE_ENTRIES, each with its textures); that cost is not measured. WhenReleaseTextureCachesruns 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 withVK_ERROR_OUT_OF_DEVICE_MEMORYat 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_PATINFOequations 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.