mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-31 23:19:44 +08:00
[AGC/Vulkan] Extend PS5 runtime and rendering compatibility (#216)
* [Core] Add POSIX native execution and PS5 SELF support Extend the native backend, guest TLS, fixed-address memory, and loader paths needed by PS5 titles on Windows, Linux, and macOS. Keep workstation GC so high-core-count hosts do not reserve over fixed guest image bases. * [HLE] Expand PS5 service and media compatibility Add the kernel, threading, save-data, networking, audio, video-codec, font, dialog, and service exports required by newer PS5 software. Preserve every SysAbi NID currently registered by main while adding the compatibility surface used by ASTRO BOT. * [AGC/Vulkan] Extend Gen5 shader and presentation support Expand PM4 handling, Gen5 shader translation, MRT and packed export support, guest image tracking, depth initialization, texture aliasing, and Vulkan presentation. Add the performance overlay and address-filtered diagnostics used to validate ASTRO BOT with original shaders. * [Core] Align static TLS reservation across hosts * [Pad] Align primary user ID with UserService * [Gpu] Preserve runtime scalar buffers across renderer seam * [AGC] Restore omitted command helper exports * [Vulkan] Reuse primary views for promoted MRT targets * [Vulkan] Preserve scratch storage bindings in compute dispatches
This commit is contained in:
+6084
-1504
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,143 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Libs.Agc;
|
||||
|
||||
public static partial class AgcExports
|
||||
{
|
||||
// Exact Gen5 primary register defaults originally reverse-engineered by Kyty (MIT).
|
||||
// Hashes, offsets, and values match the tables consumed by the PS5 AGC SDK.
|
||||
private static RegisterDefaultGroup[] CreatePrimaryRegisterDefaults() =>
|
||||
[
|
||||
// context register groups
|
||||
new(0u, 0u, 0xE24F806Du, [new(0x202u, 0x00CC0010u)]), // CB_COLOR_CONTROL
|
||||
new(0u, 1u, 0xF6C28182u, [new(0x109u, 0x00000000u)]), // CB_DCC_CONTROL
|
||||
new(0u, 2u, 0x6F6E55A5u, [new(0x104u, 0x00000000u)]), // CB_RMI_GL2_CACHE_CONTROL
|
||||
new(0u, 3u, 0x0BC65DA4u, [new(0x08Fu, 0x00000000u)]), // CB_SHADER_MASK
|
||||
new(0u, 4u, 0x9E5AD592u, [new(0x08Eu, 0x0000000Fu)]), // CB_TARGET_MASK
|
||||
new(0u, 5u, 0xBB513B98u, [new(0x2DCu, 0x0000AA00u)]), // DB_ALPHA_TO_MASK
|
||||
new(0u, 6u, 0xAB64B23Bu, [new(0x001u, 0x00000000u)]), // DB_COUNT_CONTROL
|
||||
new(0u, 7u, 0x53C39964u, [new(0x200u, 0x00000000u)]), // DB_DEPTH_CONTROL
|
||||
new(0u, 8u, 0x01396B11u, [new(0x201u, 0x00000000u)]), // DB_EQAA
|
||||
new(0u, 9u, 0x7D42019Au, [new(0x000u, 0x00000000u)]), // DB_RENDER_CONTROL
|
||||
new(0u, 10u, 0x3548F523u, [new(0x006u, 0x00000000u)]), // PS_SHADER_SAMPLE_EXCLUSION_MASK
|
||||
new(0u, 11u, 0xF43AD28Au, [new(0x01Fu, 0x00000000u)]), // DB_RMI_L2_CACHE_CONTROL
|
||||
new(0u, 12u, 0x6DE4C312u, [new(0x203u, 0x00000000u)]), // DB_SHADER_CONTROL
|
||||
new(0u, 13u, 0x00A77AE0u, [new(0x2B0u, 0x00000000u)]), // DB_SRESULTS_COMPARE_STATE0
|
||||
new(0u, 14u, 0x00A779B7u, [new(0x2B1u, 0x00000000u)]), // DB_SRESULTS_COMPARE_STATE1
|
||||
new(0u, 15u, 0x5100100Cu, [new(0x10Cu, 0x00000000u)]), // DB_STENCILREFMASK
|
||||
new(0u, 16u, 0x59958BBAu, [new(0x10Du, 0x00000000u)]), // DB_STENCILREFMASK_BF
|
||||
new(0u, 17u, 0x0C06F17Cu, [new(0x10Bu, 0x00000000u)]), // DB_STENCIL_CONTROL
|
||||
new(0u, 18u, 0x6F104B72u, [new(0x1FFu, 0x00000000u)]), // GE_MAX_OUTPUT_PER_SUBGROUP
|
||||
new(0u, 19u, 0x25C70D9Cu, [new(0x204u, 0x00000000u)]), // PA_CL_CLIP_CNTL
|
||||
new(0u, 20u, 0x3881201Eu, [new(0x20Du, 0x00000000u)]), // PA_CL_OBJPRIM_ID_CNTL
|
||||
new(0u, 21u, 0x09AFDDAFu, [new(0x206u, 0x0000043Fu)]), // PA_CL_VTE_CNTL
|
||||
new(0u, 22u, 0x367D63CFu, [new(0x2F8u, 0x00000000u)]), // PA_SC_AA_CONFIG
|
||||
new(0u, 23u, 0x43707DB8u, [new(0x083u, 0x0000FFFFu)]), // PA_SC_CLIPRECT_RULE
|
||||
new(0u, 24u, 0xF6AE26BAu, [new(0x313u, 0x00000000u)]), // PA_SC_CONSERVATIVE_RASTERIZATION_CNTL
|
||||
new(0u, 25u, 0x1B917652u, [new(0x800003FEu, 0x00000000u)]), // PA_SC_FSR_ENABLE
|
||||
new(0u, 26u, 0x94B1E4F7u, [new(0x0EAu, 0x00000000u)]), // PA_SC_HORIZ_GRID
|
||||
new(0u, 27u, 0xE3661B6Cu, [new(0x0E9u, 0x00000000u)]), // PA_SC_LEFT_VERT_GRID
|
||||
new(0u, 28u, 0x1EB8D73Au, [new(0x292u, 0x00000002u)]), // PA_SC_MODE_CNTL_0
|
||||
new(0u, 29u, 0x15051FA3u, [new(0x293u, 0x00000000u)]), // PA_SC_MODE_CNTL_1
|
||||
new(0u, 30u, 0x9C51A7F1u, [new(0x0E8u, 0x00000000u)]), // PA_SC_RIGHT_VERT_GRID
|
||||
new(0u, 31u, 0xA20EFC70u, [new(0x080u, 0x00000000u)]), // PA_SC_WINDOW_OFFSET
|
||||
new(0u, 32u, 0x0EC09F6Eu, [new(0x211u, 0x00000000u)]), // PA_STATE_STEREO_X
|
||||
new(0u, 33u, 0x34A7D6D3u, [new(0x210u, 0x00000000u)]), // PA_STEREO_CNTL
|
||||
new(0u, 34u, 0xCE831B94u, [new(0x08Du, 0x00000000u)]), // PA_SU_HARDWARE_SCREEN_OFFSET
|
||||
new(0u, 35u, 0x5CC72A74u, [new(0x282u, 0x00000008u)]), // PA_SU_LINE_CNTL
|
||||
new(0u, 36u, 0x3B77713Cu, [new(0x281u, 0xFFFF0000u)]), // PA_SU_POINT_MINMAX
|
||||
new(0u, 37u, 0x40F64410u, [new(0x280u, 0x00080008u)]), // PA_SU_POINT_SIZE
|
||||
new(0u, 38u, 0x69441268u, [new(0x2DFu, 0x00000000u)]), // PA_SU_POLY_OFFSET_CLAMP
|
||||
new(0u, 39u, 0x2E418B83u, [new(0x2DEu, 0x000001E9u)]), // PA_SU_POLY_OFFSET_DB_FMT_CNTL
|
||||
new(0u, 40u, 0xA00D0C8Du, [new(0x205u, 0x00000240u)]), // PA_SU_SC_MODE_CNTL
|
||||
new(0u, 41u, 0xB1289FB3u, [new(0x20Cu, 0x00000001u)]), // PA_SU_SMALL_PRIM_FILTER_CNTL
|
||||
new(0u, 42u, 0x144832FBu, [new(0x2F9u, 0x0000002Du)]), // PA_SU_VTX_CNTL
|
||||
new(0u, 43u, 0x9890D9FAu, [new(0x1BAu, 0x00000000u)]), // SPI_TMPRING_SIZE
|
||||
new(0u, 44u, 0x9016FAF1u, [new(0x2A6u, 0x00000000u)]), // VGT_DRAW_PAYLOAD_CNTL
|
||||
new(0u, 45u, 0x4B73CE27u, [new(0x2CEu, 0x00000400u)]), // VGT_GS_MAX_VERT_OUT
|
||||
new(0u, 46u, 0x5F5A3E7Bu, [new(0x29Bu, 0x00000002u)]), // VGT_GS_OUT_PRIM_TYPE
|
||||
new(0u, 47u, 0xD4AF3A51u, [new(0x2D6u, 0x00000000u)]), // VGT_LS_HS_CONFIG
|
||||
new(0u, 48u, 0x6CF4F543u, [new(0x2A3u, 0xFFFFFFFFu)]), // VGT_PRIMITIVEID_RESET
|
||||
new(0u, 49u, 0x5FB86CCBu, [new(0x2A1u, 0x00000000u)]), // VGT_PRIMITIVEID_EN
|
||||
new(0u, 50u, 0xEDEFA188u, [new(0x2ADu, 0x00000000u)]), // VGT_REUSE_OFF
|
||||
new(0u, 51u, 0xD0DE9EE6u, [new(0x2D5u, 0x00000000u)]), // VGT_SHADER_STAGES_EN
|
||||
new(0u, 52u, 0xC5831803u, [new(0x2D4u, 0x88101000u)]), // VGT_TESS_DISTRIBUTION
|
||||
new(0u, 53u, 0x8E6DE84Bu, [new(0x2DBu, 0x00000000u)]), // VGT_TF_PARAM
|
||||
new(0u, 54u, 0xD0771662u, [new(0x2F5u, 0x00000000u), new(0x2F6u, 0x00000000u)]), // PA_SC_CENTROID_PRIORITY_0, PA_SC_CENTROID_PRIORITY_1
|
||||
new(0u, 55u, 0x569F7444u, [new(0x2FEu, 0x00000000u)]), // PA_SC_AA_SAMPLE_LOCS_PIXEL_X0Y0_0
|
||||
new(0u, 56u, 0x5C6637CDu, [new(0x30Eu, 0xFFFFFFFFu), new(0x30Fu, 0xFFFFFFFFu)]), // PA_SC_AA_MASK_X0Y0_X1Y0, PA_SC_AA_MASK_X0Y1_X1Y1
|
||||
new(0u, 57u, 0xCAE3E690u, [new(0x311u, 0x00000002u), new(0x312u, 0x03FF0080u)]), // PA_SC_BINNER_CNTL_0, PA_SC_BINNER_CNTL_1
|
||||
new(0u, 58u, 0x43FBD769u, [new(0x105u, 0x00000000u), new(0x107u, 0x00000000u), new(0x106u, 0x00000000u), new(0x108u, 0x00000000u)]), // CB_BLEND_RED, CB_BLEND_BLUE, CB_BLEND_GREEN, CB_BLEND_ALPHA
|
||||
new(0u, 59u, 0xEF550356u, [new(0x1E0u, 0x20010001u)]), // CB_BLEND0_CONTROL
|
||||
new(0u, 60u, 0x8F52E279u, [new(0x020u, 0x00000000u), new(0x021u, 0x00000000u)]), // TA_BC_BASE_ADDR, TA_BC_BASE_ADDR_HI
|
||||
new(0u, 61u, 0x1F2D8149u, [new(0x084u, 0x00000000u), new(0x085u, 0x20002000u)]), // PA_SC_CLIPRECT_0_TL, PA_SC_CLIPRECT_0_BR
|
||||
new(0u, 62u, 0x853D0614u, [new(0x800003FFu, 0x00000000u)]), // CX_NOP
|
||||
new(0u, 63u, 0x4413C6F9u, [new(0x008u, 0x00000000u), new(0x009u, 0x00000000u)]), // DB_DEPTH_BOUNDS_MIN, DB_DEPTH_BOUNDS_MAX
|
||||
new(0u, 64u, 0x67096014u, [new(0x010u, 0x80000000u), new(0x011u, 0x20000000u), new(0x012u, 0x00000000u), new(0x013u, 0x00000000u), new(0x014u, 0x00000000u), new(0x015u, 0x00000000u), new(0x01Au, 0x00000000u), new(0x01Bu, 0x00000000u), new(0x01Cu, 0x00000000u), new(0x01Du, 0x00000000u), new(0x01Eu, 0x00000000u), new(0x002u, 0x00000000u), new(0x005u, 0x00000000u), new(0x007u, 0x00000000u), new(0x00Bu, 0x00000000u), new(0x00Au, 0x00000000u)]), // DB_Z_INFO, DB_STENCIL_INFO, DB_Z_READ_BASE, DB_STENCIL_READ_BASE, DB_Z_WRITE_BASE, DB_STENCIL_WRITE_BASE, DB_Z_READ_BASE_HI, DB_STENCIL_READ_BASE_HI, DB_Z_WRITE_BASE_HI, DB_STENCIL_WRITE_BASE_HI, DB_HTILE_DATA_BASE_HI, DB_DEPTH_VIEW, DB_HTILE_DATA_BASE, DB_DEPTH_SIZE_XY, DB_DEPTH_CLEAR, DB_STENCIL_CLEAR
|
||||
new(0u, 65u, 0x88F5E915u, [new(0x0EBu, 0xFF00FF00u), new(0x0ECu, 0x00000000u)]), // PA_SC_FOV_WINDOW_LR, PA_SC_FOV_WINDOW_TB
|
||||
new(0u, 66u, 0x033F1EFFu, [new(0x800003FCu, 0x00000000u), new(0x800003FDu, 0x00000000u)]), // FSR_RECURSIONS0, FSR_RECURSIONS1
|
||||
new(0u, 67u, 0x918106BBu, [new(0x090u, 0x80000000u), new(0x091u, 0x40004000u)]), // PA_SC_GENERIC_SCISSOR_TL, PA_SC_GENERIC_SCISSOR_BR
|
||||
new(0u, 68u, 0x95F0E7ACu, [new(0x2FAu, 0x4E7E0000u), new(0x2FBu, 0x4E7E0000u), new(0x2FCu, 0x4E7E0000u), new(0x2FDu, 0x4E7E0000u)]), // PA_CL_GB_VERT_CLIP_ADJ, PA_CL_GB_VERT_DISC_ADJ, PA_CL_GB_HORZ_CLIP_ADJ, PA_CL_GB_HORZ_DISC_ADJ
|
||||
new(0u, 69u, 0xB48CBAB2u, [new(0x2E2u, 0x00000000u), new(0x2E3u, 0x00000000u)]), // PA_SU_POLY_OFFSET_BACK_SCALE, PA_SU_POLY_OFFSET_BACK_OFFSET
|
||||
new(0u, 70u, 0x05BB3BC6u, [new(0x2E0u, 0x00000000u), new(0x2E1u, 0x00000000u)]), // PA_SU_POLY_OFFSET_FRONT_SCALE, PA_SU_POLY_OFFSET_FRONT_OFFSET
|
||||
new(0u, 71u, 0x94FABA07u, [new(0x003u, 0x00000000u), new(0x004u, 0x00000000u)]), // DB_RENDER_OVERRIDE, DB_RENDER_OVERRIDE2
|
||||
new(0u, 72u, 0x38E92C91u, [new(0x318u, 0x00000000u), new(0x31Bu, 0x00000000u), new(0x31Cu, 0x00000000u), new(0x31Du, 0x00000000u), new(0x31Eu, 0x00000048u), new(0x31Fu, 0x00000000u), new(0x321u, 0x00000000u), new(0x323u, 0x00000000u), new(0x324u, 0x00000000u), new(0x325u, 0x00000000u), new(0x390u, 0x00000000u), new(0x398u, 0x00000000u), new(0x3A0u, 0x00000000u), new(0x3A8u, 0x00000000u), new(0x3B0u, 0x00000000u), new(0x3B8u, 0x0006C000u)]), // CB_COLOR0_BASE, CB_COLOR0_VIEW, CB_COLOR0_INFO, CB_COLOR0_ATTRIB, CB_COLOR0_DCC_CONTROL, CB_COLOR0_CMASK, CB_COLOR0_FMASK, CB_COLOR0_CLEAR_WORD0, CB_COLOR0_CLEAR_WORD1, CB_COLOR0_DCC_BASE, CB_COLOR0_BASE_EXT, CB_COLOR0_CMASK_BASE_EXT, CB_COLOR0_FMASK_BASE_EXT, CB_COLOR0_DCC_BASE_EXT, CB_COLOR0_ATTRIB2, CB_COLOR0_ATTRIB3
|
||||
new(0u, 73u, 0x0B177B43u, [new(0x00Cu, 0x00000000u), new(0x00Du, 0x40004000u)]), // PA_SC_SCREEN_SCISSOR_TL, PA_SC_SCREEN_SCISSOR_BR
|
||||
new(0u, 74u, 0x48531062u, [new(0x191u, 0x00000000u)]), // SPI_PS_INPUT_CNTL_0
|
||||
new(0u, 75u, 0xAAA964B9u, [new(0x16Fu, 0x00000000u), new(0x170u, 0x00000000u), new(0x171u, 0x00000000u), new(0x172u, 0x00000000u)]), // PA_CL_UCP_0_X, PA_CL_UCP_0_Y, PA_CL_UCP_0_Z, PA_CL_UCP_0_W
|
||||
new(0u, 76u, 0x7690AF6Fu, [new(0x10Fu, 0x4E7E0000u), new(0x111u, 0x4E7E0000u), new(0x113u, 0x4E7E0000u), new(0x110u, 0x00000000u), new(0x112u, 0x00000000u), new(0x114u, 0x00000000u), new(0x094u, 0x80000000u), new(0x095u, 0x40004000u), new(0x0B4u, 0x00000000u), new(0x0B5u, 0x00000000u)]), // PA_CL_VPORT_XSCALE, PA_CL_VPORT_YSCALE, PA_CL_VPORT_ZSCALE, PA_CL_VPORT_XOFFSET, PA_CL_VPORT_YOFFSET, PA_CL_VPORT_ZOFFSET, PA_SC_VPORT_SCISSOR_0_TL, PA_SC_VPORT_SCISSOR_0_BR, PA_SC_VPORT_ZMIN_0, PA_SC_VPORT_ZMAX_0
|
||||
new(0u, 77u, 0x078D7060u, [new(0x081u, 0x80000000u), new(0x082u, 0x40004000u)]), // PA_SC_WINDOW_SCISSOR_TL, PA_SC_WINDOW_SCISSOR_BR
|
||||
// shader register groups
|
||||
new(1u, 0u, 0x5D6E3EC7u, [new(0x212u, 0x00000000u)]), // COMPUTE_PGM_RSRC1
|
||||
new(1u, 1u, 0x57E7079Au, [new(0x213u, 0x00000000u)]), // COMPUTE_PGM_RSRC2
|
||||
new(1u, 2u, 0x7467FAFDu, [new(0x228u, 0x00000000u)]), // COMPUTE_PGM_RSRC3
|
||||
new(1u, 3u, 0x9E826B50u, [new(0x215u, 0x00000000u)]), // COMPUTE_RESOURCE_LIMITS
|
||||
new(1u, 4u, 0xDC484F18u, [new(0x218u, 0x00000000u)]), // COMPUTE_TMPRING_SIZE
|
||||
new(1u, 5u, 0x5DA8BCA3u, [new(0x08Au, 0x00000000u)]), // SPI_SHADER_PGM_RSRC1_GS
|
||||
new(1u, 6u, 0x5CA726D8u, [new(0x10Au, 0x00000000u)]), // SPI_SHADER_PGM_RSRC1_HS
|
||||
new(1u, 7u, 0x5DD28360u, [new(0x00Au, 0x00000000u)]), // SPI_SHADER_PGM_RSRC1_PS
|
||||
new(1u, 8u, 0x57EFA0BEu, [new(0x08Bu, 0x00000000u)]), // SPI_SHADER_PGM_RSRC2_GS
|
||||
new(1u, 9u, 0x502363D5u, [new(0x10Bu, 0x00000000u)]), // SPI_SHADER_PGM_RSRC2_HS
|
||||
new(1u, 10u, 0x506D14BDu, [new(0x00Bu, 0x00000000u)]), // SPI_SHADER_PGM_RSRC2_PS
|
||||
new(1u, 11u, 0xB2609506u, [new(0x224u, 0x00000000u)]), // COMPUTE_USER_ACCUM_0
|
||||
new(1u, 12u, 0x9E5CFB8Au, [new(0x107u, 0x00000000u), new(0x087u, 0x00000000u), new(0x007u, 0x00000000u)]), // SPI_SHADER_PGM_RSRC3_HS, SPI_SHADER_PGM_RSRC3_GS, SPI_SHADER_PGM_RSRC3_PS
|
||||
new(1u, 13u, 0xC918DF3Eu, [new(0x20Cu, 0x00000000u), new(0x20Du, 0x00000000u)]), // COMPUTE_PGM_LO, COMPUTE_PGM_HI
|
||||
new(1u, 14u, 0xC9751C9Cu, [new(0x0C8u, 0x00000000u), new(0x0C9u, 0x00000000u)]), // SPI_SHADER_PGM_LO_ES, SPI_SHADER_PGM_HI_ES
|
||||
new(1u, 15u, 0xC97EF77Au, [new(0x088u, 0x00000000u), new(0x089u, 0x00000000u)]), // SPI_SHADER_PGM_LO_GS, SPI_SHADER_PGM_HI_GS
|
||||
new(1u, 16u, 0xC927C6B9u, [new(0x108u, 0x00000000u), new(0x109u, 0x00000000u)]), // SPI_SHADER_PGM_LO_HS, SPI_SHADER_PGM_HI_HS
|
||||
new(1u, 17u, 0xC92A1EC5u, [new(0x148u, 0x00000000u), new(0x149u, 0x00000000u)]), // SPI_SHADER_PGM_LO_LS, SPI_SHADER_PGM_HI_LS
|
||||
new(1u, 18u, 0xC9E01B31u, [new(0x008u, 0x00000000u), new(0x009u, 0x00000000u)]), // SPI_SHADER_PGM_LO_PS, SPI_SHADER_PGM_HI_PS
|
||||
new(1u, 19u, 0x50685F29u, [new(0x800002FFu, 0x00000000u)]), // SH_NOP
|
||||
new(1u, 20u, 0xB26219CAu, [new(0x0B2u, 0x00000000u)]), // SPI_SHADER_USER_ACCUM_ESGS_0
|
||||
new(1u, 21u, 0xB25B6CF9u, [new(0x132u, 0x00000000u)]), // SPI_SHADER_USER_ACCUM_LSHS_0
|
||||
new(1u, 22u, 0xB2F86101u, [new(0x032u, 0x00000000u)]), // SPI_SHADER_USER_ACCUM_PS_0
|
||||
new(1u, 23u, 0x07E3B155u, [new(0x082u, 0x00000000u), new(0x083u, 0x00000000u)]), // SPI_SHADER_USER_DATA_ADDR_LO_GS, SPI_SHADER_USER_DATA_ADDR_HI_GS
|
||||
new(1u, 24u, 0x07E383C6u, [new(0x102u, 0x00000000u), new(0x103u, 0x00000000u)]), // SPI_SHADER_USER_DATA_ADDR_LO_HS, SPI_SHADER_USER_DATA_ADDR_HI_HS
|
||||
new(1u, 25u, 0xBDA98653u, [new(0x240u, 0x00000000u)]), // COMPUTE_USER_DATA_0
|
||||
new(1u, 26u, 0xBDBD1D0Fu, [new(0x08Cu, 0x00000000u)]), // SPI_SHADER_USER_DATA_GS_0
|
||||
new(1u, 27u, 0xBD946FD4u, [new(0x10Cu, 0x00000000u)]), // SPI_SHADER_USER_DATA_HS_0
|
||||
new(1u, 28u, 0xBDF02A4Cu, [new(0x00Cu, 0x00000000u)]), // SPI_SHADER_USER_DATA_PS_0
|
||||
// uconfig register groups
|
||||
new(2u, 0u, 0x19E93E85u, [new(0x41Fu, 0x00000000u)]), // GDS_OA_ADDRESS
|
||||
new(2u, 1u, 0x3B5C2AF3u, [new(0x41Du, 0x00000000u)]), // GDS_OA_CNTL
|
||||
new(2u, 2u, 0x47974A35u, [new(0x41Eu, 0x00000000u)]), // GDS_OA_COUNTER
|
||||
new(2u, 3u, 0x105971C2u, [new(0x25Bu, 0x00000000u)]), // GE_CNTL
|
||||
new(2u, 4u, 0x7D137765u, [new(0x24Au, 0x00000000u)]), // GE_INDX_OFFSET
|
||||
new(2u, 5u, 0xD187FEBCu, [new(0x24Bu, 0x00000000u)]), // GE_MULTI_PRIM_IB_RESET_EN
|
||||
new(2u, 6u, 0x12F854ACu, [new(0x25Fu, 0x00000000u)]), // GE_STEREO_CNTL
|
||||
new(2u, 7u, 0x40D49AD1u, [new(0x262u, 0x00000000u)]), // GE_USER_VGPR_EN
|
||||
new(2u, 8u, 0x8C0923DAu, [new(0x80003FF4u, 0x00000000u)]), // FSR_EXTEND_SUBPIXEL_ROUNDING
|
||||
new(2u, 9u, 0xBB8DF494u, [new(0x80003FFDu, 0x00000000u)]), // TEXTURE_GRADIENT_CONTROL
|
||||
new(2u, 10u, 0xF6D8A76Eu, [new(0x382u, 0x40000040u)]), // TEXTURE_GRADIENT_FACTORS
|
||||
new(2u, 11u, 0x7620F1E9u, [new(0x248u, 0x00000000u)]), // VGT_OBJECT_ID
|
||||
new(2u, 12u, 0x9EBFAB10u, [new(0x242u, 0x00000000u)]), // VGT_PRIMITIVE_TYPE
|
||||
new(2u, 13u, 0x98A09D0Eu, [new(0x380u, 0x00000000u), new(0x381u, 0x00000000u)]), // TA_CS_BC_BASE_ADDR, TA_CS_BC_BASE_ADDR_HI
|
||||
new(2u, 14u, 0x195D37D2u, [new(0x80003FF5u, 0x00000000u), new(0x80003FF6u, 0x00000000u)]), // FSR_ALPHA_VALUE0, FSR_ALPHA_VALUE1
|
||||
new(2u, 15u, 0xF9EC4F85u, [new(0x80003FF7u, 0x00000000u), new(0x80003FF8u, 0x00000000u), new(0x80003FF9u, 0x00000000u), new(0x80003FFAu, 0x00000000u)]), // FSR_CONTROL_POINT0, FSR_CONTROL_POINT1, FSR_CONTROL_POINT2, FSR_CONTROL_POINT3
|
||||
new(2u, 16u, 0x4626B750u, [new(0x80003FFBu, 0x00000000u), new(0x80003FFCu, 0x00000000u)]), // FSR_WINDOW0, FSR_WINDOW1
|
||||
new(2u, 17u, 0x4CC673A0u, [new(0x80003FFEu, 0x00000000u)]), // MEMORY_MAPPING_MASK
|
||||
new(2u, 18u, 0xDE5B3431u, [new(0x80003FFFu, 0x00000000u)]), // UC_NOP
|
||||
new(2u, 19u, 0x036AC8A6u, [new(0x25Cu, 0x00000000u)]), // GE_USER_VGPR1
|
||||
];
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using SharpEmu.Libs.Kernel;
|
||||
using SharpEmu.Libs.VideoOut;
|
||||
using SharpEmu.ShaderCompiler;
|
||||
|
||||
namespace SharpEmu.Libs.Agc;
|
||||
@@ -26,5 +27,7 @@ internal static class AgcShaderCompilerHooks
|
||||
{
|
||||
Gen5ShaderScalarEvaluator.FallbackMemoryReader =
|
||||
KernelMemoryCompatExports.TryReadTrackedLibcHeap;
|
||||
Gen5ShaderScalarEvaluator.GlobalMemoryPool =
|
||||
VulkanVideoPresenter.GuestDataPool;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Libs.Agc;
|
||||
|
||||
/// <summary>
|
||||
/// Deswizzles RDNA2 (GFX10) tiled texture surfaces into linear layout so they
|
||||
/// can be uploaded to Vulkan. PS5 stores most textures in a swizzled layout
|
||||
/// selected by the 5-bit SWIZZLE_MODE in the image descriptor; uploading those
|
||||
/// bytes verbatim samples as garbage.
|
||||
///
|
||||
/// The GFX10 addressing uses power-of-two swizzle blocks (256 B / 4 KiB /
|
||||
/// 64 KiB) whose internal element order follows the standard (S), display (D),
|
||||
/// render (R), or z-order/depth (Z) equations. This implements the exact base
|
||||
/// S and Z 2D single-sample modes plus the PS5's RB+ 64 KiB Z_X/R_X equations;
|
||||
/// other D/R and pipe/bank-XOR modes stay opt-in while their complete AddrLib
|
||||
/// equations are being ported.
|
||||
/// </summary>
|
||||
internal static class GnmTiling
|
||||
{
|
||||
// Oberon uses the 16-pipe / 8-pixel-packer RB+ topology. These are the
|
||||
// single-sample 64 KiB equations generated by AMD AddrLib for that exact
|
||||
// topology. Each entry describes one address bit as an XOR of X/Y bits.
|
||||
private readonly record struct AddressBit(uint XMask, uint YMask);
|
||||
|
||||
private static readonly AddressBit[][] RbPlus64KRenderX =
|
||||
[
|
||||
// 1 byte/element: nibble01=0, nibble2=307, nibble3=379.
|
||||
[X(0), X(1), X(2), X(3), Y(0), Y(1), Y(2), Y(3),
|
||||
XY(7, 4, 7), XY(4, 4), XY(6, 5), XY(5, 6),
|
||||
X(6), Y(6), XY(7, 8), XY(8, 7)],
|
||||
// 2 bytes/element: nibble01=1, nibble2=307, nibble3=389.
|
||||
[Zero, X(0), X(1), X(2), Y(0), Y(1), Y(2), X(3),
|
||||
XY(7, 4, 7), XY(4, 4), XY(6, 5), XY(5, 6),
|
||||
Y(3), X(6), XY(7, 7), XY(8, 6)],
|
||||
// 4 bytes/element: nibble01=39, nibble2=307, nibble3=381.
|
||||
[Zero, Zero, X(0), X(1), Y(0), Y(1), X(2), Y(2),
|
||||
XY(7, 4, 7), XY(4, 4), XY(6, 5), XY(5, 6),
|
||||
X(3), Y(3), XY(6, 7), XY(7, 6)],
|
||||
// 8 bytes/element: nibble01=6, nibble2=307, nibble3=382.
|
||||
[Zero, Zero, Zero, X(0), Y(0), X(1), X(2), Y(1),
|
||||
XY(7, 4, 7), XY(4, 4), XY(6, 5), XY(5, 6),
|
||||
Y(2), X(3), XY(7, 3), XY(6, 6)],
|
||||
// 16 bytes/element: nibble01=7, nibble2=307, nibble3=390.
|
||||
[Zero, Zero, Zero, Zero, X(0), Y(0), X(1), Y(1),
|
||||
XY(7, 4, 7), XY(4, 4), XY(6, 5), XY(5, 6),
|
||||
X(2), Y(2), XY(6, 3), XY(3, 6)],
|
||||
];
|
||||
|
||||
private static readonly AddressBit[][] RbPlus64KDepthX =
|
||||
[
|
||||
// 1 byte/element: nibble01=8, nibble2=306, nibble3=379.
|
||||
[X(0), Y(0), X(1), Y(1), X(2), Y(2), X(3), Y(3),
|
||||
XY(7, 4, 7), XY(4, 4), XY(6, 5), XY(5, 6),
|
||||
X(6), Y(6), XY(7, 8), XY(8, 7)],
|
||||
// 2 bytes/element: nibble01=9, nibble2=306, nibble3=389.
|
||||
[Zero, X(0), Y(0), X(1), Y(1), X(2), Y(2), X(3),
|
||||
XY(7, 4, 7), XY(4, 4), XY(6, 5), XY(5, 6),
|
||||
Y(3), X(6), XY(7, 7), XY(8, 6)],
|
||||
// 4 bytes/element: nibble01=10, nibble2=306, nibble3=381.
|
||||
[Zero, Zero, X(0), Y(0), X(1), Y(1), X(2), Y(2),
|
||||
XY(7, 4, 7), XY(4, 4), XY(6, 5), XY(5, 6),
|
||||
X(3), Y(3), XY(6, 7), XY(7, 6)],
|
||||
// 8 bytes/element: nibble01=11, nibble2=307, nibble3=382.
|
||||
[Zero, Zero, Zero, X(0), Y(0), X(1), Y(1), X(2),
|
||||
XY(7, 4, 7), XY(4, 4), XY(6, 5), XY(5, 6),
|
||||
Y(2), X(3), XY(7, 3), XY(6, 6)],
|
||||
// 16 bytes/element is identical to R_X for a 2D single-sample image.
|
||||
[Zero, Zero, Zero, Zero, X(0), Y(0), X(1), Y(1),
|
||||
XY(7, 4, 7), XY(4, 4), XY(6, 5), XY(5, 6),
|
||||
X(2), Y(2), XY(6, 3), XY(3, 6)],
|
||||
];
|
||||
|
||||
private static readonly AddressBit[][] RbPlus64KStandard =
|
||||
[
|
||||
// GFX10_SW_64K_S_RBPLUS_PATINFO, 1 byte/element.
|
||||
[X(0), X(1), X(2), X(3), Y(0), Y(1), Y(2), Y(3),
|
||||
Y(4), X(4), Y(5), X(5), Y(6), X(6), Y(7), X(7)],
|
||||
// 2 bytes/element.
|
||||
[Zero, X(0), X(1), X(2), Y(0), Y(1), Y(2), X(3),
|
||||
Y(3), X(4), Y(4), X(5), Y(5), X(6), Y(6), X(7)],
|
||||
// 4 bytes/element.
|
||||
[Zero, Zero, X(0), X(1), Y(0), Y(1), Y(2), X(2),
|
||||
Y(3), X(3), Y(4), X(4), Y(5), X(5), Y(6), X(6)],
|
||||
// 8 bytes/element (also BC1/BC4 compressed blocks).
|
||||
[Zero, Zero, Zero, X(0), Y(0), Y(1), X(1), X(2),
|
||||
Y(2), X(3), Y(3), X(4), Y(4), X(5), Y(5), X(6)],
|
||||
// 16 bytes/element (also 16-byte BC compressed blocks).
|
||||
[Zero, Zero, Zero, Zero, Y(0), Y(1), X(0), X(1),
|
||||
Y(2), X(2), Y(3), X(3), Y(4), X(4), Y(5), X(5)],
|
||||
];
|
||||
|
||||
// GFX10 4K_S has a separate 12-bit micro-tile equation. It is not the
|
||||
// generic x/y interleave used by the 64K standard block; using that larger
|
||||
// equation leaves a regular grid in linearized atlases.
|
||||
private static readonly AddressBit[][] Standard4K =
|
||||
[
|
||||
[X(0), X(1), X(2), X(3), Y(0), Y(1), Y(2), Y(3),
|
||||
Y(4), X(4), Y(5), X(5)],
|
||||
[Zero, X(0), X(1), X(2), Y(0), Y(1), Y(2), X(3),
|
||||
Y(3), X(4), Y(4), X(5)],
|
||||
[Zero, Zero, X(0), X(1), Y(0), Y(1), Y(2), X(2),
|
||||
Y(3), X(3), Y(4), X(4)],
|
||||
[Zero, Zero, Zero, X(0), Y(0), Y(1), X(1), X(2),
|
||||
Y(2), X(3), Y(3), X(4)],
|
||||
[Zero, Zero, Zero, Zero, Y(0), Y(1), X(0), X(1),
|
||||
Y(2), X(2), Y(3), X(3)],
|
||||
];
|
||||
|
||||
private static readonly bool _enabled = string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_DETILE"),
|
||||
"1",
|
||||
StringComparison.Ordinal);
|
||||
|
||||
private static readonly bool _disabled = string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_DETILE"),
|
||||
"0",
|
||||
StringComparison.Ordinal);
|
||||
|
||||
private static readonly HashSet<uint> _reportedModes = new();
|
||||
|
||||
public static bool Enabled => _enabled || !_disabled;
|
||||
|
||||
/// <summary>
|
||||
/// Base S/Z modes and the Oberon RB+ 64 KiB Z_X/R_X modes for which this
|
||||
/// implementation carries the exact AddrLib address equations. Other
|
||||
/// bank/pipe-XOR variants remain opt-in.
|
||||
/// </summary>
|
||||
private static bool IsTrustedByDefault(uint swizzleMode) =>
|
||||
// Exact base S/Z modes. D/R use different GFX10 swizzle equations and
|
||||
// the T/X modes additionally apply pipe/bank XOR between blocks.
|
||||
swizzleMode is 1 or 4 or 5 or 8 or 9 or 24 or 27;
|
||||
|
||||
// Detile a surface when it is verified-correct by default (trusted base mode),
|
||||
// or when the user opts the approximate modes in with SHARPEMU_DETILE=1.
|
||||
// SHARPEMU_DETILE=0 forces the old raw-upload behavior for everything.
|
||||
private static bool ShouldDetile(uint swizzleMode) =>
|
||||
swizzleMode != 0 && !_disabled && (_enabled || IsTrustedByDefault(swizzleMode));
|
||||
|
||||
/// <summary>
|
||||
/// True when a surface with the given swizzle mode needs deswizzling.
|
||||
/// Mode 0 is linear and never needs it.
|
||||
/// </summary>
|
||||
public static bool NeedsDetile(uint swizzleMode) => ShouldDetile(swizzleMode);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the physical byte span occupied by a tiled mip. GNM allocates whole
|
||||
/// swizzle blocks even when the logical image is much smaller than a block;
|
||||
/// reading only the logical texel count truncates most source offsets during
|
||||
/// detiling (a 64x64 BC1 mode-9 image is 2 KiB logically but occupies one
|
||||
/// 64 KiB swizzle block).
|
||||
/// </summary>
|
||||
public static bool TryGetTiledByteCount(
|
||||
uint swizzleMode,
|
||||
int elementsWide,
|
||||
int elementsHigh,
|
||||
int bytesPerElement,
|
||||
out ulong byteCount)
|
||||
{
|
||||
byteCount = 0;
|
||||
if (!ShouldDetile(swizzleMode) ||
|
||||
elementsWide <= 0 ||
|
||||
elementsHigh <= 0 ||
|
||||
bytesPerElement <= 0 ||
|
||||
!TryGetSwizzleKind(swizzleMode, out _, out var blockBytes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bppLog2 = BitLog2((uint)bytesPerElement);
|
||||
if (bppLog2 < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var blockElements = blockBytes >> bppLog2;
|
||||
var (blockWidth, blockHeight) = SquareBlockDimensions(blockElements);
|
||||
if (blockWidth == 0 || blockHeight == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var blocksWide = ((ulong)elementsWide + (ulong)blockWidth - 1) / (ulong)blockWidth;
|
||||
var blocksHigh = ((ulong)elementsHigh + (ulong)blockHeight - 1) / (ulong)blockHeight;
|
||||
try
|
||||
{
|
||||
byteCount = checked(blocksWide * blocksHigh * (ulong)blockBytes);
|
||||
return true;
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
byteCount = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deswizzles <paramref name="tiled"/> into linear row-major order.
|
||||
/// Elements are pixels for uncompressed formats and 4x4 blocks for
|
||||
/// block-compressed formats, so callers pass the element grid dimensions
|
||||
/// and the bytes per element. Returns false (leaving output untouched) for
|
||||
/// unsupported swizzle modes so the caller can fall back to the raw bytes.
|
||||
/// </summary>
|
||||
public static bool TryDetile(
|
||||
ReadOnlySpan<byte> tiled,
|
||||
Span<byte> linear,
|
||||
uint swizzleMode,
|
||||
int elementsWide,
|
||||
int elementsHigh,
|
||||
int bytesPerElement)
|
||||
{
|
||||
if (!ShouldDetile(swizzleMode) || elementsWide <= 0 || elementsHigh <= 0 || bytesPerElement <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetSwizzleKind(swizzleMode, out var kind, out var blockBytes))
|
||||
{
|
||||
ReportUnsupported(swizzleMode);
|
||||
return false;
|
||||
}
|
||||
|
||||
var bppLog2 = BitLog2((uint)bytesPerElement);
|
||||
if (bppLog2 < 0)
|
||||
{
|
||||
// Non-power-of-two element size (e.g. 24-bit) is not swizzled in a
|
||||
// way this equation models.
|
||||
ReportUnsupported(swizzleMode);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Block dimensions in elements: a swizzle block holds blockBytes bytes,
|
||||
// laid out as a square-ish power-of-two element grid scaled by bpp.
|
||||
var blockElements = blockBytes >> bppLog2;
|
||||
var (blockWidth, blockHeight) = SquareBlockDimensions(blockElements);
|
||||
if (blockWidth == 0 || blockHeight == 0)
|
||||
{
|
||||
ReportUnsupported(swizzleMode);
|
||||
return false;
|
||||
}
|
||||
|
||||
var blocksPerRow = (elementsWide + blockWidth - 1) / blockWidth;
|
||||
var requiredLinear = (long)elementsWide * elementsHigh * bytesPerElement;
|
||||
if (linear.Length < requiredLinear)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Precompute the within-block element offset for each (x, y) inside a
|
||||
// single block. The swizzle equation only depends on the in-block
|
||||
// coordinates, so this table is reused for every block — turning the
|
||||
// per-pixel bit-interleave (a loop + calls) into a single array lookup.
|
||||
// Detiling a 2048x2048 texture is millions of elements; without this the
|
||||
// per-pixel math makes DETILE unusably slow during asset streaming.
|
||||
var hasExactXorPattern = TryGetExactXorPattern(swizzleMode, bppLog2, out var xorPattern);
|
||||
var blockTable = hasExactXorPattern ? [] : new int[blockWidth * blockHeight];
|
||||
for (var by = 0; !hasExactXorPattern && by < blockHeight; by++)
|
||||
{
|
||||
for (var bx = 0; bx < blockWidth; bx++)
|
||||
{
|
||||
blockTable[by * blockWidth + bx] = (int)(kind == SwizzleKind.ZOrder
|
||||
? MortonInterleave((uint)bx, (uint)by, blockWidth, blockHeight)
|
||||
: StandardSwizzleOffset((uint)bx, (uint)by, blockWidth, blockHeight));
|
||||
}
|
||||
}
|
||||
|
||||
for (var y = 0; y < elementsHigh; y++)
|
||||
{
|
||||
var blockY = y / blockHeight;
|
||||
var inBlockY = y % blockHeight;
|
||||
var rowBlockBase = (long)blockY * blocksPerRow;
|
||||
var tableRowBase = inBlockY * blockWidth;
|
||||
var destRowBase = (long)y * elementsWide * bytesPerElement;
|
||||
for (var x = 0; x < elementsWide; x++)
|
||||
{
|
||||
var blockX = x / blockWidth;
|
||||
var inBlockX = x % blockWidth;
|
||||
|
||||
var blockIndex = rowBlockBase + blockX;
|
||||
var sourceByte = hasExactXorPattern
|
||||
? blockIndex * blockBytes + ComputePatternOffset((uint)x, (uint)y, xorPattern)
|
||||
: (blockIndex * blockElements + blockTable[tableRowBase + inBlockX]) *
|
||||
(long)bytesPerElement;
|
||||
var destByte = destRowBase + (long)x * bytesPerElement;
|
||||
if (sourceByte + bytesPerElement > tiled.Length ||
|
||||
destByte + bytesPerElement > linear.Length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
tiled.Slice((int)sourceByte, bytesPerElement)
|
||||
.CopyTo(linear.Slice((int)destByte, bytesPerElement));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private enum SwizzleKind
|
||||
{
|
||||
Standard,
|
||||
ZOrder,
|
||||
}
|
||||
|
||||
private static readonly AddressBit Zero = new(0, 0);
|
||||
|
||||
private static AddressBit X(int bit) => new(1u << bit, 0);
|
||||
|
||||
private static AddressBit Y(int bit) => new(0, 1u << bit);
|
||||
|
||||
private static AddressBit XY(int xBit, int yBit) => new(1u << xBit, 1u << yBit);
|
||||
|
||||
private static AddressBit XY(int xBit, int yBit1, int yBit2) =>
|
||||
new(1u << xBit, (1u << yBit1) | (1u << yBit2));
|
||||
|
||||
private static bool TryGetExactXorPattern(
|
||||
uint swizzleMode,
|
||||
int bytesPerElementLog2,
|
||||
out AddressBit[] pattern)
|
||||
{
|
||||
pattern = [];
|
||||
if ((uint)bytesPerElementLog2 >= RbPlus64KRenderX.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pattern = swizzleMode switch
|
||||
{
|
||||
5 => Standard4K[bytesPerElementLog2],
|
||||
9 => RbPlus64KStandard[bytesPerElementLog2],
|
||||
24 => RbPlus64KDepthX[bytesPerElementLog2],
|
||||
27 => RbPlus64KRenderX[bytesPerElementLog2],
|
||||
_ => [],
|
||||
};
|
||||
return pattern.Length != 0;
|
||||
}
|
||||
|
||||
private static long ComputePatternOffset(uint x, uint y, AddressBit[] pattern)
|
||||
{
|
||||
uint offset = 0;
|
||||
for (var bit = 0; bit < pattern.Length; bit++)
|
||||
{
|
||||
var equation = pattern[bit];
|
||||
var parity = (System.Numerics.BitOperations.PopCount(x & equation.XMask) +
|
||||
System.Numerics.BitOperations.PopCount(y & equation.YMask)) & 1;
|
||||
offset |= (uint)parity << bit;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
private static bool TryGetSwizzleKind(uint swizzleMode, out SwizzleKind kind, out int blockBytes)
|
||||
{
|
||||
// GFX10 AddrLib SWIZZLE_MODE enumeration:
|
||||
// 1-3 = 256 B S/D/R
|
||||
// 4-7 = 4 KiB Z/S/D/R
|
||||
// 8-11 = 64 KiB Z/S/D/R
|
||||
// 16-19 = 64 KiB Z/S/D/R _T
|
||||
// 20-23 = 4 KiB Z/S/D/R _X
|
||||
// 24-27 = 64 KiB Z/S/D/R _X
|
||||
// The pipe/bank XOR (_T/_X) affects which block a given tile lands in,
|
||||
// but the *within-block* element order matches the base S/Z equation,
|
||||
// which is what dominates visible correctness. We model the block
|
||||
// interior and treat blocks as linear-ordered.
|
||||
kind = SwizzleKind.Standard;
|
||||
blockBytes = 0;
|
||||
switch (swizzleMode)
|
||||
{
|
||||
case 4: case 8: case 16: case 20: case 24:
|
||||
kind = SwizzleKind.ZOrder;
|
||||
break;
|
||||
case 1: case 2: case 3:
|
||||
case 5: case 6: case 7:
|
||||
case 9: case 10: case 11:
|
||||
case 17: case 18: case 19:
|
||||
case 21: case 22: case 23:
|
||||
case 25: case 26: case 27:
|
||||
kind = SwizzleKind.Standard;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
blockBytes = swizzleMode switch
|
||||
{
|
||||
>= 1 and <= 3 => 256,
|
||||
>= 4 and <= 7 => 4096,
|
||||
>= 20 and <= 23 => 4096,
|
||||
_ => 65536,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
// Standard (S) 2D swizzle: within a block, the element order interleaves x
|
||||
// and y bits with x taking the low bit — the AMD "standard" microtile.
|
||||
private static long StandardSwizzleOffset(uint x, uint y, int blockWidth, int blockHeight)
|
||||
{
|
||||
var widthBits = BitLog2((uint)blockWidth);
|
||||
var heightBits = BitLog2((uint)blockHeight);
|
||||
long offset = 0;
|
||||
var outBit = 0;
|
||||
var xi = 0;
|
||||
var yi = 0;
|
||||
while (xi < widthBits || yi < heightBits)
|
||||
{
|
||||
if (xi < widthBits)
|
||||
{
|
||||
offset |= (long)((x >> xi) & 1u) << outBit++;
|
||||
xi++;
|
||||
}
|
||||
|
||||
if (yi < heightBits)
|
||||
{
|
||||
offset |= (long)((y >> yi) & 1u) << outBit++;
|
||||
yi++;
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
// Z-order (Morton) swizzle: pure bit interleave, y taking the low bit.
|
||||
private static long MortonInterleave(uint x, uint y, int blockWidth, int blockHeight)
|
||||
{
|
||||
var widthBits = BitLog2((uint)blockWidth);
|
||||
var heightBits = BitLog2((uint)blockHeight);
|
||||
long offset = 0;
|
||||
var outBit = 0;
|
||||
var xi = 0;
|
||||
var yi = 0;
|
||||
while (xi < widthBits || yi < heightBits)
|
||||
{
|
||||
if (yi < heightBits)
|
||||
{
|
||||
offset |= (long)((y >> yi) & 1u) << outBit++;
|
||||
yi++;
|
||||
}
|
||||
|
||||
if (xi < widthBits)
|
||||
{
|
||||
offset |= (long)((x >> xi) & 1u) << outBit++;
|
||||
xi++;
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
private static (int Width, int Height) SquareBlockDimensions(int blockElements)
|
||||
{
|
||||
if (blockElements <= 0 || (blockElements & (blockElements - 1)) != 0)
|
||||
{
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
var totalBits = BitLog2((uint)blockElements);
|
||||
// Split as evenly as possible with width >= height (x gets the extra bit).
|
||||
var widthBits = (totalBits + 1) / 2;
|
||||
var heightBits = totalBits - widthBits;
|
||||
return (1 << widthBits, 1 << heightBits);
|
||||
}
|
||||
|
||||
private static int BitLog2(uint value)
|
||||
{
|
||||
if (value == 0 || (value & (value - 1)) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return System.Numerics.BitOperations.TrailingZeroCount(value);
|
||||
}
|
||||
|
||||
private static void ReportUnsupported(uint swizzleMode)
|
||||
{
|
||||
lock (_reportedModes)
|
||||
{
|
||||
if (!_reportedModes.Add(swizzleMode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] GNM detile: unsupported swizzle mode {swizzleMode}; texture uploaded linear.");
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,17 @@
|
||||
|
||||
namespace SharpEmu.Libs.Agc;
|
||||
|
||||
// Holds DCBs whose parsing was suspended on an unsatisfied WAIT_REG_MEM condition.
|
||||
// AgcExports re-checks every waiter against guest memory on each submit and resumes
|
||||
// the ones whose condition became true (labels are advanced by ReleaseMem/WriteData/
|
||||
// DmaData packets or by direct CPU writes).
|
||||
/// <summary>
|
||||
/// Holds DCBs whose parsing was suspended on an unsatisfied WAIT_REG_MEM
|
||||
/// condition. AgcExports re-checks every waiter against guest memory on each
|
||||
/// submit and resumes the ones whose condition became true (labels are advanced
|
||||
/// by ReleaseMem / WriteData / DmaData packets, or by direct CPU writes).
|
||||
///
|
||||
/// This preserves cross-submit ordering: the work that follows a wait inside a
|
||||
/// DCB is only queued once the awaited completion label is genuinely written,
|
||||
/// instead of being force-satisfied at parse time and running ahead of the
|
||||
/// compute/graphics work it depends on (which produced a black composite).
|
||||
/// </summary>
|
||||
internal static class GpuWaitRegistry
|
||||
{
|
||||
public struct WaitingDcb
|
||||
@@ -19,13 +26,56 @@ internal static class GpuWaitRegistry
|
||||
public ulong ReferenceValue;
|
||||
public ulong Mask;
|
||||
public uint CompareFunction;
|
||||
public uint ControlValue;
|
||||
public bool Is64Bit;
|
||||
public bool IsStandard;
|
||||
public object? Memory;
|
||||
public string? QueueName;
|
||||
public ulong SubmissionId;
|
||||
// Stopwatch timestamp captured at registration. Stale waiters remain
|
||||
// registered; this only controls one-shot diagnostics.
|
||||
public long RegisteredTicks;
|
||||
public bool StaleReported;
|
||||
public object? State;
|
||||
}
|
||||
|
||||
private static readonly object _gate = new();
|
||||
private static readonly Dictionary<ulong, List<WaitingDcb>> _waiters = new();
|
||||
|
||||
public static int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var (_, list) in _waiters)
|
||||
{
|
||||
total += list.Count;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int CountForMemory(object memory)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var (_, list) in _waiters)
|
||||
{
|
||||
foreach (var waiter in list)
|
||||
{
|
||||
total += ReferenceEquals(waiter.Memory, memory) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Register(ulong address, WaitingDcb waiter)
|
||||
{
|
||||
waiter.WaitAddress = address;
|
||||
@@ -41,9 +91,15 @@ internal static class GpuWaitRegistry
|
||||
}
|
||||
}
|
||||
|
||||
// Re-evaluates every registered waiter. readValue receives (address, is64Bit) and
|
||||
// returns null when the memory is unreadable; such waiters are kept registered.
|
||||
public static List<WaitingDcb>? CollectSatisfied(Func<ulong, bool, ulong?> readValue)
|
||||
/// <summary>
|
||||
/// Re-evaluates every registered waiter. <paramref name="readValue"/>
|
||||
/// receives (address, is64Bit) and returns null when the memory is
|
||||
/// unreadable; such waiters are kept registered. Returns the waiters whose
|
||||
/// condition is now satisfied (removed from the registry), or null.
|
||||
/// </summary>
|
||||
public static List<WaitingDcb>? CollectSatisfied(
|
||||
object memory,
|
||||
Func<ulong, bool, ulong?> readValue)
|
||||
{
|
||||
List<WaitingDcb>? woken = null;
|
||||
lock (_gate)
|
||||
@@ -53,6 +109,11 @@ internal static class GpuWaitRegistry
|
||||
{
|
||||
for (var i = list.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!ReferenceEquals(list[i].Memory, memory))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = readValue(address, list[i].Is64Bit);
|
||||
if (value is null || !Compare(list[i], value.Value))
|
||||
{
|
||||
@@ -71,7 +132,7 @@ internal static class GpuWaitRegistry
|
||||
}
|
||||
}
|
||||
|
||||
if (emptied != null)
|
||||
if (emptied is not null)
|
||||
{
|
||||
foreach (var address in emptied)
|
||||
{
|
||||
@@ -83,20 +144,122 @@ internal static class GpuWaitRegistry
|
||||
return woken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns waiters that have remained unsatisfied longer than
|
||||
/// <paramref name="maxAgeTicks"/> exactly once, without removing them or
|
||||
/// changing their labels. Missing GPU work must fail closed: advancing a
|
||||
/// command buffer without its real producer corrupts cross-queue ordering.
|
||||
/// </summary>
|
||||
public static List<WaitingDcb>? CollectUnreportedStale(
|
||||
object memory,
|
||||
long nowTicks,
|
||||
long maxAgeTicks)
|
||||
{
|
||||
List<WaitingDcb>? stale = null;
|
||||
lock (_gate)
|
||||
{
|
||||
foreach (var (_, list) in _waiters)
|
||||
{
|
||||
for (var i = list.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var waiter = list[i];
|
||||
if (!ReferenceEquals(waiter.Memory, memory) ||
|
||||
waiter.StaleReported ||
|
||||
nowTicks - waiter.RegisteredTicks < maxAgeTicks)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
stale ??= new List<WaitingDcb>();
|
||||
waiter.StaleReported = true;
|
||||
list[i] = waiter;
|
||||
stale.Add(waiter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns watched labels overlapped by a newly discovered producer. Used
|
||||
/// only for diagnostics; producer completion still wakes through the
|
||||
/// normal CollectSatisfied path after the ordered memory write executes.
|
||||
/// </summary>
|
||||
public static List<(ulong Address, int Count)> SnapshotInRange(
|
||||
object memory,
|
||||
ulong start,
|
||||
ulong length)
|
||||
{
|
||||
var matches = new List<(ulong Address, int Count)>();
|
||||
if (length == 0)
|
||||
{
|
||||
return matches;
|
||||
}
|
||||
|
||||
var end = start > ulong.MaxValue - length ? ulong.MaxValue : start + length;
|
||||
lock (_gate)
|
||||
{
|
||||
foreach (var (address, list) in _waiters)
|
||||
{
|
||||
var matchingCount = 0;
|
||||
var any64Bit = false;
|
||||
foreach (var waiter in list)
|
||||
{
|
||||
if (!ReferenceEquals(waiter.Memory, memory))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
matchingCount++;
|
||||
any64Bit |= waiter.Is64Bit;
|
||||
}
|
||||
|
||||
if (matchingCount == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var width = any64Bit
|
||||
? sizeof(ulong)
|
||||
: sizeof(uint);
|
||||
var waitEnd = address > ulong.MaxValue - (ulong)width
|
||||
? ulong.MaxValue
|
||||
: address + (ulong)width;
|
||||
if (start < waitEnd && address < end)
|
||||
{
|
||||
matches.Add((address, matchingCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
public static bool Compare(in WaitingDcb waiter, ulong value)
|
||||
{
|
||||
var masked = value & waiter.Mask;
|
||||
var reference = waiter.ReferenceValue & waiter.Mask;
|
||||
return waiter.CompareFunction switch
|
||||
{
|
||||
1 => masked < waiter.ReferenceValue,
|
||||
2 => masked <= waiter.ReferenceValue,
|
||||
3 => masked == waiter.ReferenceValue,
|
||||
4 => masked != waiter.ReferenceValue,
|
||||
5 => masked >= waiter.ReferenceValue,
|
||||
6 => masked > waiter.ReferenceValue,
|
||||
// 0 is "always" in the PM4 encoding and 7 is reserved; treating both as
|
||||
// satisfied keeps a malformed packet from suspending its DCB forever.
|
||||
0 => true,
|
||||
1 => masked < reference,
|
||||
2 => masked <= reference,
|
||||
3 => masked == reference,
|
||||
4 => masked != reference,
|
||||
5 => masked >= reference,
|
||||
6 => masked > reference,
|
||||
// 7 is reserved; treating it as satisfied keeps a malformed packet
|
||||
// from suspending forever.
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_waiters.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user