Compare commits

...
Author SHA1 Message Date
nmzik ade32e7ba7 kernel: implement AIO request polling 2026-08-27 22:01:13 +02:00
nmzik cc5c0ec6b8 graphics: support PS5 1D color render targets 2026-08-27 14:47:17 +02:00
nmzik c167375fe1 shader: centralize program compilation and permutation caching 2026-08-27 13:37:46 +02:00
nmzik 65871a53a6 graphics: collapse redundant Vulkan and command buffer types 2026-08-27 06:22:15 +02:00
nmzik 1ff580498c shader: support V_CMPX_NGT_F16 SDWA 2026-08-27 04:25:11 +02:00
nmzik ae896d0bf6 shader: support V_CMPX_GT_U16 SDWA 2026-08-27 04:13:12 +02:00
nmzik e738535294 graphics: add RDNA2 FLAT BDA buffer cache and faults 2026-08-27 03:45:41 +02:00
nmzik 8072718462 graphics: replace command fences with timeline reuse 2026-08-26 10:03:43 +02:00
nmzik 7adade0df5 Revert "graphics: require unrestricted depth ranges"
This reverts commit 7012634f32.
2026-08-26 09:04:06 +02:00
nmzik ab50c10d08 graphics: split shader translation into explicit opcode dispatchers 2026-08-26 08:49:39 +02:00
nmzik ffa69d48de shader: remove second IR layer 2026-08-26 08:49:39 +02:00
nmzik e2c840cb23 agc: fix abis 2026-08-26 01:39:10 +02:00
nmzik fa891536df graphics: do not copy PM4 streams & route executed packet interrupts properly 2026-08-25 23:22:54 +02:00
nmzik 7012634f32 graphics: require unrestricted depth ranges 2026-08-25 03:11:12 +02:00
nmzik 663b524fb3 Revert "loader: preserve TLS patch instruction boundaries"
This reverts commit ffd07dbfce.
2026-08-25 02:46:02 +02:00
nmzik 0b3302af70 audio: parse NGS2 ATRAC9 waveform metadata 2026-08-24 08:42:07 +02:00
nmzik ffd07dbfce loader: preserve TLS patch instruction boundaries 2026-08-24 07:06:59 +02:00
nmzik 6eed46b024 renderer: simplify image view cache 2026-08-24 01:31:15 +02:00
nmzik e6ad87072d renderer: avoid speculative buffer LRU touches 2026-08-24 01:12:41 +02:00
nmzik dd2fbd6c45 tests: cover large host buffer copies 2026-08-24 00:52:53 +02:00
nmzik 1779142805 renderer: invalidate buffers on memory unmap 2026-08-24 00:40:02 +02:00
nmzik 186621c6ff renderer: use LRU buffer cache collection 2026-08-24 00:17:02 +02:00
nmzik 11d4c0d2a2 renderer: synchronize bounded buffer downloads 2026-08-23 19:22:17 +02:00
nmzik aa69b5b184 renderer: rewrite buffer lookup around page index 2026-08-23 19:22:17 +02:00
nmzik cb378365e1 renderer: avoid allocating for dirty range probes 2026-08-23 19:22:17 +02:00
nmzik 30fa9dddc5 renderer: respect buffer ownership in image downloads 2026-08-23 19:22:17 +02:00
nmzik 4d8d3b6d25 renderer: batch buffer cache uploads 2026-08-23 19:22:17 +02:00
nmzik 523cc17ffe renderer: keep small buffer uploads nonblocking 2026-08-23 19:22:17 +02:00
nmzik 825e5f1162 renderer: defer clean buffer destruction on unmap 2026-08-23 19:22:17 +02:00
nmzik 3c016bead1 renderer: defer texture destruction on unmap 2026-08-23 17:08:59 +02:00
nmzik 800a7719cf target Vulkan 1.3 for bundled shaders 2026-08-23 16:51:37 +02:00
nmzik e3fb05c1a8 logging: report build and host details at startup 2026-08-23 16:47:15 +02:00
nmzik bc9c70d50f shader: implement RDNA2 V_LSHLREV_B64 2026-08-23 16:39:01 +02:00
nmzik a8fe4e7a2d renderer: honor null pixel shaders in depth-only draws 2026-08-23 04:23:59 +02:00
nmzikandSenaxx bed19e5d8c shader: preserve concurrent subword buffer stores
Co-authored-by: Senaxx <Senaxx@users.noreply.github.com>
2026-08-23 03:53:47 +02:00
nmzik 4adbf40861 renderer: simplify dynamic params 2026-08-23 02:14:58 +02:00
nmzik 8d01fb1c18 shader: DS_READ_U16_D16, V_LSHRREV_B64, 2026-08-23 01:19:47 +02:00
nmzik a610528075 shader: V_CMPX_NE_I64 2026-08-23 00:59:44 +02:00
nmzik 3e25eb0998 Fix wave64 LDS synchronization on subgroup32 hosts 2026-08-23 00:46:37 +02:00
nmzik 0d92ce49da shader: support RDNA2 V_CMPX_NE_U64 2026-08-22 04:38:13 +02:00
nmzik 4179bb5b06 shader: route inner shared exits first 2026-08-22 04:00:37 +02:00
nmzik d581703ab4 shader: preserve folded control-flow values 2026-08-22 03:31:59 +02:00
nmzik 9241fdc32a shader: align float semantics with RDNA2 default mode 2026-08-22 03:16:45 +02:00
nmzik a2baaccc6e shader: fix auxiliary position exports 2026-08-22 02:58:35 +02:00
nmzik c130d74efc graphics: synthesize always-visible occlusion query results 2026-08-22 01:47:46 +02:00
nmzikandSenaxx 5e64efce4f graphics: support RDNA2 custom pixel interpolation
Co-authored-by: Senaxx <Senaxx@users.noreply.github.com>
2026-08-21 23:33:40 +02:00
nmzik 17c2b4cc7d fix regression TryReadBacking 2026-08-21 22:45:40 +02:00
nmzik bad6726f5e Rewrite descriptor bindings and unify graphics push constants 2026-08-21 22:28:10 +02:00
nmzik 26c4168160 refactor(renderer): simplify buffer and texture cache ownership 2026-08-21 20:11:38 +02:00
nmzik 49ea1680f8 graphics: use per-invocation shader masks 2026-08-21 08:03:22 +02:00
nmzik 79443aa104 shader: decode compressed UNORM16 MRT exports 2026-08-21 05:23:09 +02:00
nmzik 342fae06a6 graphics: name game Vulkan resources in debug dumps 2026-08-21 04:48:43 +02:00
nmzik 7022afa86e commandProcessor: WriteData fix 2026-08-21 03:32:49 +02:00
nmzik a44dd88806 agc: new ABI 2026-08-21 03:32:49 +02:00
nmzik d0a4c908a1 shader: align address-memory buffer bindings 2026-08-21 03:32:49 +02:00
nmzik 61dd8a3031 shader: accept byte-selected CNDMASK SDWA operands 2026-08-21 03:32:49 +02:00
770bdb9571 shader: separate formatless storage from image atomics
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
Co-authored-by: Senaxx <Senaxx@users.noreply.github.com>
2026-08-21 03:32:49 +02:00
nmzik 719e20d470 shader: support bit-packed image surfaces 2026-08-21 03:32:49 +02:00
nmzik fedd1a4335 gpu: describe packed 11-11-10 uint textures 2026-08-21 03:32:49 +02:00
nmzik ebd47081ca agc: initialize interpolant mapping registers 2026-08-21 03:32:49 +02:00
nmzik 1546e93d67 gpu: handle native blend-color register writes 2026-08-21 03:32:49 +02:00
nmzik 94325b5ed6 launcher: window title fix 2026-08-21 03:32:49 +02:00
nmzik 86dbf89188 agc: emit native wait-on-address packets 2026-08-21 03:32:48 +02:00
nmzik f119e4baca gpu: handle partial guard-band register ranges 2026-08-21 03:32:48 +02:00
nmzik 494467406a gpu: route direct shader register ranges through handlers 2026-08-21 03:32:48 +02:00
nmzik 0966938dc8 shader: honor RDNA2 128-bit image descriptors 2026-08-21 03:32:48 +02:00
nmzik bc5709e1bd agc: new ABIs 2026-08-21 03:32:48 +02:00
nmzik 767d0b7da9 videoout: support PS5 auxiliary buses 2026-08-21 03:32:48 +02:00
nmzik d5410a93c8 graphics: preserve FP16 inline constants 2026-08-21 03:32:48 +02:00
nmzik 129c64235f graphics: implement RDNA2 D16 image packing 2026-08-21 03:32:48 +02:00
nmzik 1190d5de7d graphics: honor read-only stencil targets 2026-08-21 03:32:48 +02:00
nmzik 8021150ea2 graphics: preserve coherent layered depth layouts 2026-08-21 03:32:48 +02:00
nmzik 88c351f0fa shader: canonicalize SampleAdjust sampler scratch bits 2026-08-21 03:32:48 +02:00
nmzik e78bda60a0 graphics: accept texture-compatible FMASK metadata mode 2026-08-21 03:32:48 +02:00
nmzik 22bdafecbb graphics: preserve raw D16 uint texture reads 2026-08-21 03:32:48 +02:00
nmzik 6f9ed6ffe5 graphics: support depth targets with read-only backing 2026-08-21 03:32:48 +02:00
nmzik fbdc1e8714 shader: implement RDNA2 scalar quad-mask reduction 2026-08-21 03:32:48 +02:00
nmzik bac6bf65af renderdoc: fix 2026-08-21 03:32:48 +02:00
nmzik 5ad7d0ebc2 graphics: grow undersized render target backings 2026-08-21 03:32:48 +02:00
nmzik 7f66a61801 shader: reject rejoining early-exit routes 2026-08-21 03:32:48 +02:00
nmzik 2bd9f4124f shader: structurize nested early-exit tails 2026-08-21 03:32:48 +02:00
nmzik 705fc93363 shader: initialize compute EXEC from active subgroup 2026-08-21 03:32:48 +02:00
nmzik 77db9164b8 graphics: continue compute on native subgroup size 2026-08-21 03:32:48 +02:00
Senaxx a42e3bb09e graphics: derive compute wave size from dispatch mode 2026-08-21 03:32:48 +02:00
nmzik 69156b2f0e graphics: accept independent stencil mask writes 2026-08-21 03:32:48 +02:00
Senaxx 56a15c2036 Implement RDNA2 per-invocation scratch memory 2026-08-21 03:32:47 +02:00
nmzik 2db0b3cfa7 shader: fix shared-terminal selection merges 2026-08-21 03:32:47 +02:00
nmzik 41bfba515c shader: dump original RDNA2 binaries in graphics debug mode 2026-08-21 03:32:47 +02:00
nmzik 4710522e87 shader: canonicalize CFG loop headers 2026-08-21 03:32:47 +02:00
nmzik 85cf965d54 refactor(shader): centralize typed resource metadata 2026-08-21 03:32:47 +02:00
nmzik 87166eed94 refactor(shader): reduce dispatcher state overhead 2026-08-21 03:32:47 +02:00
nmzik 6e9f162bd4 refactor(shader): compact paired 64-bit lowering 2026-08-21 03:32:47 +02:00
nmzik 8063822243 refactor(shader): preserve native scalar memory operations 2026-08-21 03:32:47 +02:00
nmzik c236ba713d refactor(shader): preserve native wide shared operations 2026-08-21 03:32:47 +02:00
nmzik c9e942a972 refactor(shader): compact whole-quad mask lowering 2026-08-21 03:32:47 +02:00
nmzik 5383972e96 refactor(shader): preserve native wide buffer operations 2026-08-21 03:32:47 +02:00
nmzik 3ea2099245 refactor(shader): simplify buffer access emission 2026-08-21 03:32:47 +02:00
nmzik 49145c1902 refactor(shader): emit structured phis natively 2026-08-21 03:32:47 +02:00
nmzik d34e30d892 refactor(shader): emit SPIR-V declarations on demand 2026-08-21 03:32:47 +02:00
nmzik e1bcc298a0 refactor(shader): remove register-state compatibility paths 2026-08-21 03:32:47 +02:00
nmzik 33545ca6c4 test(shader): establish SPIR-V size baselines 2026-08-21 03:32:47 +02:00
nmzik 562f8a0895 fix(renderdoc): capture complete guest frames 2026-08-21 03:32:47 +02:00
Senaxxandnmzik 7e42513faf perf(shader): cache analysis (#259)
perf(shader): cache SPIR-V requirement analysis

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-19 00:50:13 +02:00
nmzik 596ecdcca0 Fix shader CFG structurization for nested early exits 2026-08-19 00:14:51 +02:00
nmzikandshadowbeat070 5d83ce82af renderer: track and materialize deferred DCC clears
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-08-18 19:10:36 +02:00
shadowbeat070andnmzik 4fb72e9db7 graphics: add --gpu-assisted-validation and report fatal VkResults (#268)
* graphics: report the VkResult when a submit or fence wait fails

* graphics: add a --gpu-assisted-validation flag

* graphics: imply Vulkan validation for GPU-assisted validation

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-18 14:44:48 +02:00
Stefanos Costaandnmzik c4ca8f4bf8 shader: fix the Linux and macOS build (#285)
* fixed linux build
- added correct uint long translation
-  replaced throw with non-constexpr function so that it fails in case of malformed table

* shader: remove redundant opcode table comment

* shader: use EXIT for invalid opcode tables

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-18 14:41:48 +02:00
Hashim Khan a4b970d97d launcher: explain unsupported mods-style patch JSON (#248)
When a _Patches file has no patches array (often a shadPS4/ETAHen mods
list), show a clear status instead of "Loaded 0 patch(es)". Fixes #174.
2026-08-18 14:22:40 +02:00
nmzik 8cfc9cd7a8 renderer: execute attachmentless PS side-effect draws 2026-08-18 14:03:36 +02:00
nmzik e39162fc52 shader: remove validation from program cache key 2026-08-18 14:03:36 +02:00
Hashim Khan 6bf69297c6 launcher: keep window open when game folder prompt is dismissed (#245)
Calling QApplication::quit() after the user closes the first-run game
folder dialog can segfault during nested modal shutdown. Leave the
launcher open so settings can still be configured. Fixes #121.
2026-08-18 04:18:56 +02:00
SoporteChilemat 95d282854d Build audio_out2_port_tests with kyty_tests (#242) 2026-08-18 04:08:16 +02:00
nmzik c59a4565bc renderer: simplify texture cache queries
Remove the duplicate texture page-overlap check and avoid shared-owner refcount traffic for already validated query candidates.
2026-08-18 03:53:45 +02:00
nmzik 79e424182b renderer: simplify image buffer owner lookup 2026-08-18 03:53:45 +02:00
nmzik fe867d03f6 renderer: remove duplicate buffer containment checks 2026-08-18 03:53:45 +02:00
nmzik ae4e695425 renderer: remove duplicate texture candidate checks 2026-08-18 03:53:45 +02:00
nmzik 6407add888 renderer: align texture overlap retirement 2026-08-18 03:53:45 +02:00
nmzik 840d742230 renderer: align buffer synchronization flow 2026-08-18 03:53:45 +02:00
nmzik a798e12d7f renderer: reuse buffer overlap merge for images 2026-08-18 03:53:45 +02:00
nmzik d80e914b34 renderer: align buffer overlap merging 2026-08-18 03:53:45 +02:00
nmzik 395c34ecab renderer: recreate insufficient texture cache matches 2026-08-18 03:53:45 +02:00
nmzik 1e8317159e renderer: align maybe-dirty image handling 2026-08-18 03:53:45 +02:00
nmzik d0e08496be renderer: handle clean DMA copies before overlap checks 2026-08-18 03:53:45 +02:00
nmzik 377fb8f50a shader: use ISA-style decoder opcode names 2026-08-18 03:53:45 +02:00
nmzik 4220a747cb libc: remove duplicate Mtx ABI implementations
No idea why these were reimplemented in the first place at all. The loaded guest libc already provides them; Kyty only needs the lower libkernel pthread imports.
2026-08-18 03:53:45 +02:00
nmzik e24f29d514 renderer: diagnostics 2026-08-18 03:53:45 +02:00
nmzikandshadowbeat070 54aa8efc98 libc: match PS5 mutex unlock result semantics
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-08-18 03:53:45 +02:00
nmzikandshadowbeat070 89fdb1214a agc: ignore empty multi-DCB submissions
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-08-18 03:53:45 +02:00
nmzik 01275fb211 shader_recompiler: rename/clean up typed SSA SPIR-V emitter 2026-08-18 03:53:45 +02:00
nmzik 5970c4ef95 shader_recompiler: remove legacy SPIR-V emitter 2026-08-18 03:53:44 +02:00
nmzik 24b40d4c03 renderer: add warning log 2026-08-18 03:53:44 +02:00
nmzik 146253afb6 renderer&guest_gpu: minor cleanup 2026-08-18 03:53:44 +02:00
nmzik c759236ccb renderer: support depth-compatible uint image views 2026-08-18 03:53:44 +02:00
nmzik ce39238210 renderer: preserve requested depth stencil view aspects 2026-08-18 03:53:44 +02:00
nmzik 330889e50b shader: implement RDNA2 frexp mantissa 2026-08-18 03:53:44 +02:00
nmzik 1be9568a02 shader: implement RDNA2 frexp exponent 2026-08-18 03:53:44 +02:00
nmzik 8ce06ccdf6 shader: route nested shared regions 2026-08-18 03:53:44 +02:00
nmzik 1dbac4fd79 shader: repair nested merge structurization 2026-08-18 03:53:44 +02:00
nmzik d80fbea51d shader: resolve subtractive dword SETPC tables 2026-08-18 03:53:44 +02:00
shadowbeat070 e239210279 Rewrite RenderDoc capture flow and host exceptions 2026-08-18 03:53:44 +02:00
nmzik e7c8bcb3a4 recompiler: deduplication cleanup 2026-08-18 03:53:44 +02:00
nmzik 0f3540b1ff shader: unify stage input metadata 2026-08-18 03:53:44 +02:00
nmzik 5ea92d95a7 add playgo hack 2026-08-18 03:53:44 +02:00
nmzik 5a32e270ad kernel: export scePthreadCancel 2026-08-18 03:53:44 +02:00
nmzik e857e332ac graphics: handle PS5 DMA_DATA prefetch 2026-08-18 03:53:44 +02:00
nmzik 44302cbf96 shader: implement RDNA2 V_MED3_I16 2026-08-18 03:53:44 +02:00
nmzik 6b306f64d5 graphics: execute valid PM4 rewind packets 2026-08-18 03:53:44 +02:00
nmzik e1366eb91d graphics: resolve Standard4KB color targets 2026-08-18 03:53:44 +02:00
nmzik 2a1a8e72e3 shader: preserve exec-masked FLAT address roots 2026-08-18 03:53:44 +02:00
nmzik 191cdff231 graphics: support tiled render target mip views 2026-08-18 03:53:43 +02:00
nmzik 7d8ee8b6b5 audio: provide live NGS2 system info 2026-08-18 03:53:43 +02:00
nmzik 01a1036640 audio: retire destroyed NGS2 racks 2026-08-18 03:53:43 +02:00
nmzik 7850cbeaef rename legacy Graphics -> Agc 2026-08-18 03:53:43 +02:00
nmzik af931d73c7 remove logging in hot path 2026-08-18 03:53:43 +02:00
nmzik 3a7cd93765 agc: ABIs fix - EventWrite/WriteData 2026-08-18 03:53:43 +02:00
nmzik a34bcbd951 agc: ABIs fix - acb/dcb 2026-08-18 03:53:43 +02:00
nmzik b0bd6470f3 graphics: accept AGC vertex-buffer-only shaders 2026-08-18 03:53:43 +02:00
nmzik 93398d6d0f graphics: sign-extend vector bitfield extracts 2026-08-18 03:53:43 +02:00
nmzik 4e343408dc graphics: size compute LDS from shader state 2026-08-18 03:53:43 +02:00
nmzik 1375d650a7 graphics: support RDNA2 dependency waits 2026-08-18 03:53:43 +02:00
nmzik d7f1bcaa19 graphics: support signed 16-bit integer packing 2026-08-18 03:53:43 +02:00
nmzik 2bb396f58f graphics: support SDWA arithmetic right shifts 2026-08-18 03:53:43 +02:00
nmzik af1ab60786 graphics: intersect texture views with resource mips 2026-08-18 03:53:43 +02:00
nmzik 430de9bd15 graphics: support dynamic indirect image tables 2026-08-18 03:53:43 +02:00
nmzik 1c4171c9eb graphics: accept SDWA byte conversion sources 2026-08-18 03:53:43 +02:00
nmzik 443bbcbf90 graphics: separate scalar address bindings 2026-08-18 03:53:43 +02:00
nmzik 83ee040b4d graphics: specialize invariant indirect image tables 2026-08-18 03:53:43 +02:00
Senaxx 980d89f739 graphics: support dynamic storage mip writes 2026-08-18 03:53:43 +02:00
Senaxx f785974cc1 graphics: accept storage mip ranges 2026-08-18 03:53:43 +02:00
nmzik 547b9b96cc graphics: upload sparse PRT image backing 2026-08-18 03:53:43 +02:00
nmzik 989477efad graphics: derive pixel stage activity from shader outputs 2026-08-18 03:53:43 +02:00
nmzik 6d273e4d9c graphics: accept forced HTile resummarization 2026-08-18 03:53:43 +02:00
Senaxx d7fd4a2864 graphics: accept asymmetric Z clip state 2026-08-18 03:53:42 +02:00
nmzik 5e27a28ab0 shader: fix missing homogeneous semantics 2026-08-18 03:53:42 +02:00
nmzik 67e550f2e7 agc: implement owner resource unregister 2026-08-18 03:53:42 +02:00
nmzik aec9b97b0c dialog: implement common dialog usage query 2026-08-18 03:53:42 +02:00
nmzik e4a95b2c26 playgo: reject missing package metadata 2026-08-18 03:53:42 +02:00
nmzik 6e82c376f6 kernel: allow renaming open save files 2026-08-18 03:53:42 +02:00
nmzik 4f1bc505cc graphics: support depth-tiled color targets 2026-08-18 03:53:42 +02:00
nmzik 484433b180 shader: fix V_ADD_NC_U32 2026-08-18 03:53:42 +02:00
nmzik 70f6af407e new ABI stubs 2026-08-18 03:53:42 +02:00
nmzik 7556a74f19 shader: implement s_bfe_i32 2026-08-18 03:53:42 +02:00
nmzik b4809e7170 Fix SPIR-V float control contract (Preserve FP32 Inf and NaN) 2026-08-18 03:53:42 +02:00
nmzik 0df20eb300 fix opcode regression 2026-08-18 03:53:42 +02:00
nmzik 5bfdf4267e Optimize decoder 2026-08-18 03:53:42 +02:00
nmzik f3cdf217ea shader: evaluate unsigned min in runtime descriptors 2026-08-18 03:53:42 +02:00
nmzik f453a752bb shader: snapshot wide SMEM sources before writes 2026-08-18 03:53:42 +02:00
nmzik 28a913894d graphics: implement RDNA2 64-bit first-set-bit 2026-08-18 03:53:42 +02:00
nmzik 0ffacf22e6 graphics: accept phi-mediated SRT cycles 2026-08-18 03:53:42 +02:00
nmzik 13e87a8e58 graphics: implement RDNA2 alignbyte shader opcode 2026-08-18 03:53:42 +02:00
nmzik 30d57bed06 graphics: honor primitive restart state 2026-08-18 03:53:42 +02:00
nmzik 965c30956d graphics: clamp storage buffers to committed memory 2026-08-18 03:53:42 +02:00
nmzik 09fec6815f shader: handle disabled RDNA2 traps 2026-08-18 03:53:42 +02:00
nmzik 00bd19194a shader: mask vector carry outputs by exec 2026-08-18 03:53:42 +02:00
nmzik 4a7bc55afa shader: implement RDNA2 subrev borrow 2026-08-18 03:53:42 +02:00
nmzik 9c316e6988 shader: implement RDNA2 v_cmp_gt_u64 2026-08-18 03:53:42 +02:00
nmzik a3c3cbb722 agc: fix async DMA data ABI 2026-08-18 03:53:42 +02:00
nmzik b42380ced6 shader_recompiler: add SSA 2026-08-18 03:53:41 +02:00
Hashim Khanandnmzik bc2f077c37 launcher: quote emulator paths that contain spaces (#244)
* launcher: quote emulator paths that contain spaces

Run the Linux helper script as a bash file argument instead of bash -c,
and build a properly quoted cmd.exe /K command on Windows so install
folders like "Sony Playstation 5" still launch. Fixes #179.

* launcher: fix cmd command quoting

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-16 22:13:12 +02:00
Hashim Khan 498d63b620 docs: fix mismatched screenshot alt texts in README (#249)
Align Neptunia ReVerse, Hellboy, and Paleo Pines image alts with their
captions so screen readers and link previews describe the right titles.
2026-08-16 22:05:07 +02:00
Hashim Khanandnmzik 566a45017e window: toggle borderless fullscreen with F11 and Alt+Enter (#246)
* window: toggle borderless fullscreen with F11 and Alt+Enter

Match the existing SDL_WINDOW_FULLSCREEN_DESKTOP startup path so testers
can switch modes at runtime. Reserve F11 in host input mappings. Fixes #217.

* window: consume fullscreen hotkey input

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-15 04:07:07 +02:00
nmzik 7a40dad90f agc: ABI fixes 2026-08-11 17:44:57 +02:00
nmzik a9e843b38e ci: link IME common code into dialog tests 2026-08-11 16:48:20 +02:00
nmzik 9a76719544 gpu: validate depth and cube texture descriptors
Accept SDK-valid depth sampler modulation and validate cube storage images as complete six-layer groups.
2026-08-11 16:45:32 +02:00
nmzik 472c8213db gpu: accept CB/DB release without GL2 writeback 2026-08-11 16:12:38 +02:00
nmzik a4f9c022ff ampr: make virtual address range query non-destructive 2026-08-11 16:12:38 +02:00
nmzik 97514884b1 Unify stack walking 2026-08-11 16:12:38 +02:00
nmzik 4980efa9b7 refactor(subsystems&ime): RAII lifecycle 2026-08-11 16:12:38 +02:00
nmzik 34cf130e4d Refactor PM4 control register decoding 2026-08-11 16:12:38 +02:00
nmzik 21aa7b66ce graphics: replace raw GPU enum values with typed formats 2026-08-11 16:12:38 +02:00
nmzik 11b95bed20 graphics: simplify render target format and color mapping resolution 2026-08-11 16:12:38 +02:00
Ali Almohaya ca7c8f0074 agc: fix linux build of gs oversubscription clamps (#232) 2026-08-11 07:38:22 +02:00
Pouare514andnmzik d7063d0b81 vulkan: remove validation layer configuration from device creation (#221)
* vulkan: remove validation layer configuration from device creation

* Remove accidental dependency gitlinks

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-09 23:05:31 +02:00
nmzik 091e6b31dc avplayer: decode media on bounded worker queues 2026-08-09 06:49:25 +02:00
nmzik 4cd6132758 gpu: allow byte-granular memory DMA 2026-08-09 05:31:18 +02:00
nmzik 9c263a9886 gpu: accept disabled default clip planes 2026-08-09 05:23:43 +02:00
nmzik c11fc96019 pad: accept system remote-control port 2026-08-09 05:15:40 +02:00
nmzik 4f2b5eb749 libSystemService fixes 2026-08-09 04:44:43 +02:00
nmzik d7013dc2b3 add left/right joystick remapping 2026-08-09 04:24:47 +02:00
nmzik 0bbe301120 add website/compatibility db 2026-08-09 04:08:32 +02:00
nmzik 37aa6f88b0 ime: implement libIme and shared host overlay 2026-08-09 02:58:49 +02:00
nmzik e3890febaa agc: new abi 2026-08-09 01:32:32 +02:00
nmzik a4da2a9f00 json2: implement typed Value constructor 2026-08-09 01:09:41 +02:00
nmzik 3005a210c6 renderer: ignore inactive HTile depth address 2026-08-08 23:55:45 +02:00
nmzik 027433fcdc Update game status report form 2026-08-08 21:19:22 +02:00
DeathWhispers 7ff9e4907f Don't declare colorWriteEnable dynamic state on depth-only pipelines (#160)
fix(gpu): drop colorWriteEnable dynamic state on depth-only pipelines

Pipelines were created with VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT
unconditionally on non-Apple platforms, but the draw path only calls
vkCmdSetColorWriteEnableEXT when there is at least one color attachment.
A depth-only draw therefore reached vkCmdDrawIndexed with the dynamic
state declared and never set, which violates
VUID-vkCmdDrawIndexed-None-07749.

With validation enabled the error is fatal, so any title that issues a
depth-only pass aborts. The launcher enables validation by default
(vulkan_validation_enabled = true), so this is the default configuration:
PAC-MAN WORLD Re-PAC crashes from the launcher while the same build runs
normally with --vulkan-validation false.

Gate the declaration on the same color_count the draw path uses. Both
sides read state.color_count from the same draw, so the two conditions
are now identical by construction.
2026-08-08 08:21:18 +02:00
Stefanos Costaandnmzik 87428d7a4b kernel: do not release a waiter on a zero-count syncOnAddress wake (linux only) (#168)
* kernel: do not release a waiter on a zero-count syncOnAddress wake

sceKernelSyncOnAddressWake with a count of 0 must release no waiters. On
Linux it released exactly one.

WakeLinux passes the count straight to FUTEX_WAKE, which does not treat 0
as "wake nobody": the kernel's wake loop tests `if (++ret >= nr_wake) break`
after waking a waiter, so any waiter present is released before the loop can
break. A standalone futex program confirms it -- FUTEX_WAKE with val = 0
returns 1, and the waiter observes its wait return 0 with the word unchanged.

WakePortable already short-circuits count == 0, so the two backends of the
same ABI disagreed, with only the Linux one wrong.

TestWakeZeroIsNoOp in SyncOnAddressTests covers exactly this and fails on
Linux without the fix (15 consecutive failures on the assertion "zero-count
wake releases no waiters"); with the fix the suite passes 15 consecutive runs.

* kernel: shorten zero-count futex comment

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-08 08:17:56 +02:00
Ali Almohayaandnmzik 822ffba313 feat: identify build provenance in the crash log report (#196)
* feat: identify build provenance in the crash log report

* cmake: reuse git version dependency target

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-08 08:16:49 +02:00
Ali Almohaya 3c850b65c8 fix: stop a connected controller from disabling keyboard input (#194)
fix(input): stop a connected controller from disabling keyboard input
2026-08-08 08:05:52 +02:00
nmzik 698c9f802f renderer: remove global BufferCache locking 2026-08-08 08:02:35 +02:00
286 changed files with 71693 additions and 58417 deletions

No files matched your search

@@ -1,6 +1,6 @@
name: Game Emulation Bug Report
description: Report KytyPS5 game boot, crash, freeze, rendering, audio, input, or gameplay behavior problems.
title: "[GAME BUG]: "
name: Game Emulation Status Report
description: Submit a KytyPS5 game compatibility result, including boot, rendering, audio, input, or gameplay behavior.
title: "[GAME STATUS]: "
body:
- type: markdown
attributes:
@@ -10,6 +10,7 @@ body:
Before submitting, please:
- Search existing issues first.
- Re-test with a clean game dump.
- Use the latest KytyPS5 build available to you.
- Attach the complete log file instead of edited snippets.
- type: markdown
@@ -44,6 +45,28 @@ body:
validations:
required: true
- type: dropdown
id: compatibility-status
attributes:
label: Compatibility status
description: Select the furthest point the game consistently reaches on this build.
options:
- "Doesn't boot"
- Logo
- Main menu
- In game
validations:
required: true
- type: markdown
attributes:
value: |
**Status guide**
- **Doesn't boot:** The game does not show its first logo or startup screen.
- **Logo:** The game shows a logo or startup screen but does not reach the main menu.
- **Main menu:** The game reaches its menus but does not enter gameplay.
- **In game:** The game reaches controllable gameplay, even if bugs prevent completion.
- type: input
id: regression-builds
attributes:
@@ -54,22 +77,22 @@ body:
- type: markdown
attributes:
value: |
## Problem details
## Test result
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Describe the failure or incorrect behavior as directly as possible.
placeholder: "Example: The game reaches the main menu, then freezes after selecting Continue."
label: Result details
description: Describe what worked, what failed, and any visible or audible problems.
placeholder: "Example: The game reaches the main menu, then freezes after selecting Continue. Audio plays correctly until the freeze."
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to reproduce
description: List the shortest path that triggers the problem from a fresh KytyPS5 start.
label: Steps to reproduce the result
description: List the shortest path to the reported status or failure from a fresh KytyPS5 start.
placeholder: |
1. Open KytyPS5.
2. Boot the game.
@@ -81,7 +104,7 @@ body:
id: expected-behavior
attributes:
label: Expected behavior
description: Explain what should happen at the same point in the game.
description: Explain what should happen at the same point in the game. If there is no known problem, write "No known issue".
placeholder: "Example: The save should load and gameplay should continue normally."
validations:
required: true
+875
View File
@@ -0,0 +1,875 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015-2026 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#pragma once
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html
//
#if !defined(RENDERDOC_NO_STDINT)
#include <stdint.h>
#endif
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
#define RENDERDOC_CC __cdecl
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__sun__) || defined(__OpenBSD__)
#define RENDERDOC_CC
#elif defined(__APPLE__)
#define RENDERDOC_CC
#else
#error "Unknown platform"
#endif
#ifdef __cplusplus
extern "C" {
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////
// Constants not used directly in below API
// This is a GUID/magic value used for when applications pass a path where shader debug
// information can be found to match up with a stripped shader.
// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue =
// RENDERDOC_ShaderDebugMagicValue_value
#define RENDERDOC_ShaderDebugMagicValue_struct \
{ \
0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
}
// as an alternative when you want a byte array (assuming x86 endianness):
#define RENDERDOC_ShaderDebugMagicValue_bytearray \
{ \
0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
}
// truncated version when only a uint64_t is available (e.g. Vulkan tags):
#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL
// this is a magic value for vulkan user tags to indicate which dispatchable API objects are which
// for object annotations
#define RENDERDOC_APIObjectAnnotationHelper 0xfbb3b337b664d0adULL
//////////////////////////////////////////////////////////////////////////////////////////////////
// RenderDoc capture options
//
typedef enum RENDERDOC_CaptureOption
{
// Allow the application to enable vsync
//
// Default - enabled
//
// 1 - The application can enable or disable vsync at will
// 0 - vsync is force disabled
eRENDERDOC_Option_AllowVSync = 0,
// Allow the application to enable fullscreen
//
// Default - enabled
//
// 1 - The application can enable or disable fullscreen at will
// 0 - fullscreen is force disabled
eRENDERDOC_Option_AllowFullscreen = 1,
// Record API debugging events and messages
//
// Default - disabled
//
// 1 - Enable built-in API debugging features and records the results into
// the capture, which is matched up with events on replay
// 0 - no API debugging is forcibly enabled
eRENDERDOC_Option_APIValidation = 2,
eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum
// Capture CPU callstacks for API events
//
// Default - disabled
//
// 1 - Enables capturing of callstacks
// 0 - no callstacks are captured
eRENDERDOC_Option_CaptureCallstacks = 3,
// When capturing CPU callstacks, only capture them from actions.
// This option does nothing without the above option being enabled
//
// Default - disabled
//
// 1 - Only captures callstacks for actions.
// Ignored if CaptureCallstacks is disabled
// 0 - Callstacks, if enabled, are captured for every event.
eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4,
eRENDERDOC_Option_CaptureCallstacksOnlyActions = 4,
// Specify a delay in seconds to wait for a debugger to attach, after
// creating or injecting into a process, before continuing to allow it to run.
//
// 0 indicates no delay, and the process will run immediately after injection
//
// Default - 0 seconds
//
eRENDERDOC_Option_DelayForDebugger = 5,
// Verify buffer access. This includes checking the memory returned by a Map() call to
// detect any out-of-bounds modification, as well as initialising buffers with undefined contents
// to a marker value to catch use of uninitialised memory.
//
// NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
// not do the same kind of interception & checking and undefined contents are really undefined.
//
// Default - disabled
//
// 1 - Verify buffer access
// 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in
// RenderDoc.
eRENDERDOC_Option_VerifyBufferAccess = 6,
// The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites.
// This option now controls the filling of uninitialised buffers with 0xdddddddd which was
// previously always enabled
eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess,
// Hooks any system API calls that create child processes, and injects
// RenderDoc into them recursively with the same options.
//
// Default - disabled
//
// 1 - Hooks into spawned child processes
// 0 - Child processes are not hooked by RenderDoc
eRENDERDOC_Option_HookIntoChildren = 7,
// By default RenderDoc only includes resources in the final capture necessary
// for that frame, this allows you to override that behaviour.
//
// Default - disabled
//
// 1 - all live resources at the time of capture are included in the capture
// and available for inspection
// 0 - only the resources referenced by the captured frame are included
eRENDERDOC_Option_RefAllResources = 8,
// **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
// getting it will be ignored, to allow compatibility with older versions.
// In v1.1 the option acts as if it's always enabled.
//
// By default RenderDoc skips saving initial states for resources where the
// previous contents don't appear to be used, assuming that writes before
// reads indicate previous contents aren't used.
//
// Default - disabled
//
// 1 - initial contents at the start of each captured frame are saved, even if
// they are later overwritten or cleared before being used.
// 0 - unless a read is detected, initial contents will not be saved and will
// appear as black or empty data.
eRENDERDOC_Option_SaveAllInitials = 9,
// In APIs that allow for the recording of command lists to be replayed later,
// RenderDoc may choose to not capture command lists before a frame capture is
// triggered, to reduce overheads. This means any command lists recorded once
// and replayed many times will not be available and may cause a failure to
// capture.
//
// NOTE: This is only true for APIs where multithreading is difficult or
// discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
// and always capture all command lists since the API is heavily oriented
// around it and the overheads have been reduced by API design.
//
// 1 - All command lists are captured from the start of the application
// 0 - Command lists are only captured if their recording begins during
// the period when a frame capture is in progress.
eRENDERDOC_Option_CaptureAllCmdLists = 10,
// Mute API debugging output when the API validation mode option is enabled
//
// Default - enabled
//
// 1 - Mute any API debug messages from being displayed or passed through
// 0 - API debugging is displayed as normal
eRENDERDOC_Option_DebugOutputMute = 11,
// Option to allow vendor extensions to be used even when they may be
// incompatible with RenderDoc and cause corrupted replays or crashes.
//
// Default - inactive
//
// No values are documented, this option should only be used when absolutely
// necessary as directed by a RenderDoc developer.
eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12,
// Define a soft memory limit which some APIs may aim to keep overhead under where
// possible. Anything above this limit will where possible be saved directly to disk during
// capture.
// This will cause increased disk space use (which may cause a capture to fail if disk space is
// exhausted) as well as slower capture times.
//
// Not all memory allocations may be deferred like this so it is not a guarantee of a memory
// limit.
//
// Units are in MBs, suggested values would range from 200MB to 1000MB.
//
// Default - 0 Megabytes
eRENDERDOC_Option_SoftMemoryLimit = 13,
} RENDERDOC_CaptureOption;
// Sets an option that controls how RenderDoc behaves on capture.
//
// Returns 1 if the option and value are valid
// Returns 0 if either is invalid and the option is unchanged
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val);
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val);
// Gets the current value of an option as a uint32_t
//
// If the option is invalid, 0xffffffff is returned
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt);
// Gets the current value of an option as a float
//
// If the option is invalid, -FLT_MAX is returned
typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt);
typedef enum RENDERDOC_InputButton
{
// '0' - '9' matches ASCII values
eRENDERDOC_Key_0 = 0x30,
eRENDERDOC_Key_1 = 0x31,
eRENDERDOC_Key_2 = 0x32,
eRENDERDOC_Key_3 = 0x33,
eRENDERDOC_Key_4 = 0x34,
eRENDERDOC_Key_5 = 0x35,
eRENDERDOC_Key_6 = 0x36,
eRENDERDOC_Key_7 = 0x37,
eRENDERDOC_Key_8 = 0x38,
eRENDERDOC_Key_9 = 0x39,
// 'A' - 'Z' matches ASCII values
eRENDERDOC_Key_A = 0x41,
eRENDERDOC_Key_B = 0x42,
eRENDERDOC_Key_C = 0x43,
eRENDERDOC_Key_D = 0x44,
eRENDERDOC_Key_E = 0x45,
eRENDERDOC_Key_F = 0x46,
eRENDERDOC_Key_G = 0x47,
eRENDERDOC_Key_H = 0x48,
eRENDERDOC_Key_I = 0x49,
eRENDERDOC_Key_J = 0x4A,
eRENDERDOC_Key_K = 0x4B,
eRENDERDOC_Key_L = 0x4C,
eRENDERDOC_Key_M = 0x4D,
eRENDERDOC_Key_N = 0x4E,
eRENDERDOC_Key_O = 0x4F,
eRENDERDOC_Key_P = 0x50,
eRENDERDOC_Key_Q = 0x51,
eRENDERDOC_Key_R = 0x52,
eRENDERDOC_Key_S = 0x53,
eRENDERDOC_Key_T = 0x54,
eRENDERDOC_Key_U = 0x55,
eRENDERDOC_Key_V = 0x56,
eRENDERDOC_Key_W = 0x57,
eRENDERDOC_Key_X = 0x58,
eRENDERDOC_Key_Y = 0x59,
eRENDERDOC_Key_Z = 0x5A,
// leave the rest of the ASCII range free
// in case we want to use it later
eRENDERDOC_Key_NonPrintable = 0x100,
eRENDERDOC_Key_Divide,
eRENDERDOC_Key_Multiply,
eRENDERDOC_Key_Subtract,
eRENDERDOC_Key_Plus,
eRENDERDOC_Key_F1,
eRENDERDOC_Key_F2,
eRENDERDOC_Key_F3,
eRENDERDOC_Key_F4,
eRENDERDOC_Key_F5,
eRENDERDOC_Key_F6,
eRENDERDOC_Key_F7,
eRENDERDOC_Key_F8,
eRENDERDOC_Key_F9,
eRENDERDOC_Key_F10,
eRENDERDOC_Key_F11,
eRENDERDOC_Key_F12,
eRENDERDOC_Key_Home,
eRENDERDOC_Key_End,
eRENDERDOC_Key_Insert,
eRENDERDOC_Key_Delete,
eRENDERDOC_Key_PageUp,
eRENDERDOC_Key_PageDn,
eRENDERDOC_Key_Backspace,
eRENDERDOC_Key_Tab,
eRENDERDOC_Key_PrtScrn,
eRENDERDOC_Key_Pause,
eRENDERDOC_Key_Max,
} RENDERDOC_InputButton;
// Sets which key or keys can be used to toggle focus between multiple windows
//
// If keys is NULL or num is 0, toggle keys will be disabled
typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num);
// Sets which key or keys can be used to capture the next frame
//
// If keys is NULL or num is 0, captures keys will be disabled
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num);
typedef enum RENDERDOC_OverlayBits
{
// This single bit controls whether the overlay is enabled or disabled globally
eRENDERDOC_Overlay_Enabled = 0x1,
// Show the average framerate over several seconds as well as min/max
eRENDERDOC_Overlay_FrameRate = 0x2,
// Show the current frame number
eRENDERDOC_Overlay_FrameNumber = 0x4,
// Show a list of recent captures, and how many captures have been made
eRENDERDOC_Overlay_CaptureList = 0x8,
// Default values for the overlay mask
eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate |
eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList),
// Enable all bits
eRENDERDOC_Overlay_All = 0x7ffffff,
// Disable all bits
eRENDERDOC_Overlay_None = 0,
} RENDERDOC_OverlayBits;
// returns the overlay bits that have been set
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)(void);
// sets the overlay bits with an and & or mask
typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or);
// this function will attempt to remove RenderDoc's hooks in the application.
//
// Note: that this can only work correctly if done immediately after
// the module is loaded, before any API work happens. RenderDoc will remove its
// injected hooks and shut down. Behaviour is undefined if this is called
// after any API functions have been called, and there is still no guarantee of
// success.
typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)(void);
// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers.
typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown;
// This function will unload RenderDoc's crash handler.
//
// If you use your own crash handler and don't want RenderDoc's handler to
// intercede, you can call this function to unload it and any unhandled
// exceptions will pass to the next handler.
typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)(void);
// Sets the capture file path template
//
// pathtemplate is a UTF-8 string that gives a template for how captures will be named
// and where they will be saved.
//
// Any extension is stripped off the path, and captures are saved in the directory
// specified, and named with the filename and the frame number appended. If the
// directory does not exist it will be created, including any parent directories.
//
// If pathtemplate is NULL, the template will remain unchanged
//
// Example:
//
// SetCaptureFilePathTemplate("my_captures/example");
//
// Capture #1 -> my_captures/example_frame123.rdc
// Capture #2 -> my_captures/example_frame456.rdc
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate);
// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string
typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)(void);
// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers.
typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate;
typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate;
// returns the number of captures that have been made
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)(void);
// This function returns the details of a capture, by index. New captures are added
// to the end of the list.
//
// filename will be filled with the absolute path to the capture file, as a UTF-8 string
// pathlength will be written with the length in bytes of the filename string
// timestamp will be written with the time of the capture, in seconds since the Unix epoch
//
// Any of the parameters can be NULL and they'll be skipped.
//
// The function will return 1 if the capture index is valid, or 0 if the index is invalid
// If the index is invalid, the values will be unchanged
//
// Note: when captures are deleted in the UI they will remain in this list, so the
// capture path may not exist anymore.
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename,
uint32_t *pathlength, uint64_t *timestamp);
// Sets the comments associated with a capture file. These comments are displayed in the
// UI program when opening.
//
// filePath should be a path to the capture file to add comments to. If set to NULL or ""
// the most recent capture file created made will be used instead.
// comments should be a NULL-terminated UTF-8 string to add as comments.
//
// Any existing comments will be overwritten.
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath,
const char *comments);
// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)(void);
// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers.
// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for
// backwards compatibility with old code, it is castable either way since it's ABI compatible
// as the same function pointer type.
typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected;
// This function will launch the Replay UI associated with the RenderDoc library injected
// into the running application.
//
// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter
// to connect to this application
// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open
// if cmdline is NULL, the command line will be empty.
//
// returns the PID of the replay UI if successful, 0 if not successful.
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl,
const char *cmdline);
// RenderDoc can return a higher version than requested if it's backwards compatible,
// this function returns the actual version returned. If a parameter is NULL, it will be
// ignored and the others will be filled out.
typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch);
// Requests that the replay UI show itself (if hidden or not the current top window). This can be
// used in conjunction with IsTargetControlConnected and LaunchReplayUI to intelligently handle
// showing the UI after making a capture.
//
// This will return 1 if the request was successfully passed on, though it's not guaranteed that
// the UI will be on top in all cases depending on OS rules. It will return 0 if there is no current
// target control connection to make such a request, or if there was another error
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_ShowReplayUI)(void);
//////////////////////////////////////////////////////////////////////////
// Capturing functions
//
// A device pointer is a pointer to the API's root handle.
//
// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc
typedef void *RENDERDOC_DevicePointer;
// A window handle is the OS's native window handle
//
// This would be an HWND, GLXDrawable, etc
typedef void *RENDERDOC_WindowHandle;
// A helper macro for Vulkan, where the device handle cannot be used directly.
//
// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use.
//
// Specifically, the value needed is the dispatch table pointer, which sits as the first
// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and
// indirect once.
#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst)))
// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will
// respond to keypresses. Neither parameter can be NULL
typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device,
RENDERDOC_WindowHandle wndHandle);
// capture the next frame on whichever window and API is currently considered active
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)(void);
// capture the next N frames on whichever window and API is currently considered active
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames);
// When choosing either a device pointer or a window handle to capture, you can pass NULL.
// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify
// any API rendering to a specific window, or a specific API instance rendering to any window,
// or in the simplest case of one window and one API, you can just pass NULL for both.
//
// In either case, if there are two or more possible matching (device,window) pairs it
// is undefined which one will be captured.
//
// Note: for headless rendering you can pass NULL for the window handle and either specify
// a device pointer or leave it NULL as above.
// Immediately starts capturing API calls on the specified device pointer and window handle.
//
// If there is no matching thing to capture (e.g. no supported API has been initialised),
// this will do nothing.
//
// The results are undefined (including crashes) if two captures are started overlapping,
// even on separate devices and/oror windows.
typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device,
RENDERDOC_WindowHandle wndHandle);
// Returns whether or not a frame capture is currently ongoing anywhere.
//
// This will return 1 if a capture is ongoing, and 0 if there is no capture running
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)(void);
// Ends capturing immediately.
//
// This will return 1 if the capture succeeded, and 0 if there was an error capturing.
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device,
RENDERDOC_WindowHandle wndHandle);
// Ends capturing immediately and discard any data stored without saving to disk.
//
// This will return 1 if the capture was discarded, and 0 if there was an error or no capture
// was in progress
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device,
RENDERDOC_WindowHandle wndHandle);
// Only valid to be called between a call to StartFrameCapture and EndFrameCapture. Gives a custom
// title to the capture produced which will be displayed in the UI.
//
// If multiple captures are ongoing, this title will be applied to the first capture to end after
// this call. The second capture to end will have no title, unless this function is called again.
//
// Calling this function has no effect if no capture is currently running, and if it is called
// multiple times only the last title will be used.
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title);
// Annotations API:
//
// These functions allow you to specify annotations either on a per-command level, or a per-object
// level.
//
// Basic types of annotations are supported, as well as vector versions and references to API objects.
//
// The annotations are stored as keys, with the key being a dot-separated path allowing arbitrary
// nesting and user organisation. The keys are sorted in human order so `foo.2.bar` will be displayed
// before `foo.10.bar` to allow creation of arrays if desired.
//
// Deleting an annotation can be done by assigning an empty value to it.
// the type of an annotation value, or Empty to delete an annotation
typedef enum RENDERDOC_AnnotationType
{
eRENDERDOC_Empty,
eRENDERDOC_Bool,
eRENDERDOC_Int32,
eRENDERDOC_UInt32,
eRENDERDOC_Int64,
eRENDERDOC_UInt64,
eRENDERDOC_Float,
eRENDERDOC_Double,
eRENDERDOC_String,
eRENDERDOC_APIObject,
eRENDERDOC_AnnotationMax = 0x7FFFFFFF,
} RENDERDOC_AnnotationType;
// a union with vector annotation value data
typedef union RENDERDOC_AnnotationVectorValue
{
bool boolean[4];
int32_t int32[4];
int64_t int64[4];
uint32_t uint32[4];
uint64_t uint64[4];
float float32[4];
double float64[4];
} RENDERDOC_AnnotationVectorValue;
// a union with scalar annotation value data
typedef union RENDERDOC_AnnotationValue
{
bool boolean;
int32_t int32;
int64_t int64;
uint32_t uint32;
uint64_t uint64;
float float32;
double float64;
RENDERDOC_AnnotationVectorValue vector;
const char *string;
void *apiObject;
} RENDERDOC_AnnotationValue;
// a struct for specifying a GL object, as we don't have pointers we can use so instead we specify a
// pointer to this struct giving both the type and the name
typedef struct RENDERDOC_GLResourceReference
{
// this is the same GLenum identifier as passed to glObjectLabel
uint32_t identifier;
uint32_t name;
} GLResourceReference;
// simple C++ helpers to avoid the need for a temporary objects for value passing and GL object specification
#ifdef __cplusplus
struct RDGLObjectHelper
{
RENDERDOC_GLResourceReference gl;
RDGLObjectHelper(uint32_t identifier, uint32_t name)
{
gl.identifier = identifier;
gl.name = name;
}
operator RENDERDOC_GLResourceReference *() { return &gl; }
};
struct RDAnnotationHelper
{
RENDERDOC_AnnotationValue val;
RDAnnotationHelper(bool b) { val.boolean = b; }
RDAnnotationHelper(int32_t i) { val.int32 = i; }
RDAnnotationHelper(int64_t i) { val.int64 = i; }
RDAnnotationHelper(uint32_t i) { val.uint32 = i; }
RDAnnotationHelper(uint64_t i) { val.uint64 = i; }
RDAnnotationHelper(float f) { val.float32 = f; }
RDAnnotationHelper(double d) { val.float64 = d; }
RDAnnotationHelper(const char *s) { val.string = s; }
operator RENDERDOC_AnnotationValue *() { return &val; }
};
#endif
// The device is specified in the same way as other API calls that take a RENDERDOC_DevicePointer
// to specify the device.
//
// The object or queue/commandbuffer will depend on the graphics API in question.
//
// Return value:
// 0 - The annotation was applied successfully.
// 1 - The device is unknown/invalid
// 2 - The device is valid but the annotation is not supported for API-specific reasons, such as an
// unrecognised or invalid object or queue/commandbuffer
// 3 - The call is ill-formed or invalid e.g. empty is specified with a value pointer, or non-empty
// is specified with a NULL value pointer
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_SetObjectAnnotation)(RENDERDOC_DevicePointer device,
void *object, const char *key,
RENDERDOC_AnnotationType valueType,
uint32_t valueVectorWidth,
const RENDERDOC_AnnotationValue *value);
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_SetCommandAnnotation)(
RENDERDOC_DevicePointer device, void *queueOrCommandBuffer, const char *key,
RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth,
const RENDERDOC_AnnotationValue *value);
//////////////////////////////////////////////////////////////////////////////////////////////////
// RenderDoc API versions
//
// RenderDoc uses semantic versioning (http://semver.org/).
//
// MAJOR version is incremented when incompatible API changes happen.
// MINOR version is incremented when functionality is added in a backwards-compatible manner.
// PATCH version is incremented when backwards-compatible bug fixes happen.
//
// Note that this means the API returned can be higher than the one you might have requested.
// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned
// instead of 1.0.0. You can check this with the GetAPIVersion entry point
typedef enum RENDERDOC_Version
{
eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00
eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01
eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02
eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00
eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01
eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02
eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00
eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00
eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00
eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01
eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02
eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00
eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00
eRENDERDOC_API_Version_1_7_0 = 10700, // RENDERDOC_API_1_7_0 = 1 07 00
} RENDERDOC_Version;
// API version changelog:
//
// 1.0.0 - initial release
// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered
// by keypress or TriggerCapture, instead of Start/EndFrameCapture.
// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation
// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new
// function pointer is added to the end of the struct, the original layout is identical
// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote
// replay/remote server concept in replay UI)
// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these
// are captures and not debug logging files. This is the first API version in the v1.0
// branch.
// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be
// displayed in the UI program on load.
// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions
// which allows users to opt-in to allowing unsupported vendor extensions to function.
// Should be used at the user's own risk.
// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to
// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to
// 0xdddddddd of uninitialised buffer contents.
// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop
// capturing without saving anything to disk.
// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening
// 1.4.2 - Refactor: Renamed 'draws' to 'actions' in callstack capture option.
// 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected
// 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a
// capture made with StartFrameCapture() or EndFrameCapture()
// 1.7.0 - Added feature: SetObjectAnnotation() / SetCommandAnnotation() for adding rich
// annotations to objects and command streams
typedef struct RENDERDOC_API_1_7_0
{
pRENDERDOC_GetAPIVersion GetAPIVersion;
pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32;
pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32;
pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32;
pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32;
pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys;
pRENDERDOC_SetCaptureKeys SetCaptureKeys;
pRENDERDOC_GetOverlayBits GetOverlayBits;
pRENDERDOC_MaskOverlayBits MaskOverlayBits;
// Shutdown was renamed to RemoveHooks in 1.4.1.
// These unions allow old code to continue compiling without changes
union
{
pRENDERDOC_Shutdown Shutdown;
pRENDERDOC_RemoveHooks RemoveHooks;
};
pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;
// Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2.
// These unions allow old code to continue compiling without changes
union
{
// deprecated name
pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate;
// current name
pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;
};
union
{
// deprecated name
pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate;
// current name
pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;
};
pRENDERDOC_GetNumCaptures GetNumCaptures;
pRENDERDOC_GetCapture GetCapture;
pRENDERDOC_TriggerCapture TriggerCapture;
// IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1.
// This union allows old code to continue compiling without changes
union
{
// deprecated name
pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected;
// current name
pRENDERDOC_IsTargetControlConnected IsTargetControlConnected;
};
pRENDERDOC_LaunchReplayUI LaunchReplayUI;
pRENDERDOC_SetActiveWindow SetActiveWindow;
pRENDERDOC_StartFrameCapture StartFrameCapture;
pRENDERDOC_IsFrameCapturing IsFrameCapturing;
pRENDERDOC_EndFrameCapture EndFrameCapture;
// new function in 1.1.0
pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture;
// new function in 1.2.0
pRENDERDOC_SetCaptureFileComments SetCaptureFileComments;
// new function in 1.4.0
pRENDERDOC_DiscardFrameCapture DiscardFrameCapture;
// new function in 1.5.0
pRENDERDOC_ShowReplayUI ShowReplayUI;
// new function in 1.6.0
pRENDERDOC_SetCaptureTitle SetCaptureTitle;
// new functions in 1.7.0
pRENDERDOC_SetObjectAnnotation SetObjectAnnotation;
pRENDERDOC_SetCommandAnnotation SetCommandAnnotation;
} RENDERDOC_API_1_7_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_1;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_2;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_1;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_2;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_2_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_3_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_1;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_2;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_5_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_6_0;
//////////////////////////////////////////////////////////////////////////////////////////////////
// RenderDoc API entry point
//
// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available.
//
// The name is the same as the typedef - "RENDERDOC_GetAPI"
//
// This function is not thread safe, and should not be called on multiple threads at once.
// Ideally, call this once as early as possible in your application's startup, before doing
// any API work, since some configuration functionality etc has to be done also before
// initialising any APIs.
//
// Parameters:
// version is a single value from the RENDERDOC_Version above.
//
// outAPIPointers will be filled out with a pointer to the corresponding struct of function
// pointers.
//
// Returns:
// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested
// 0 - if the requested version is not supported or the arguments are invalid.
//
typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers);
#ifdef __cplusplus
} // extern "C"
#endif
+53 -22
View File
@@ -203,14 +203,21 @@ file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
"${KYTY_SOURCE_DIR}/graphics/shader/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/cfg/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/cfg/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/decompiler/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/decompiler/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/emitter/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/emitter/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/cfg/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/cfg/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/decode/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/decode/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/translate/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/translate/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/backend/spirv/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/backend/spirv/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/opcodes/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/opcodes/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/opcodes/*.inc"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/*.h"
"${KYTY_SOURCE_DIR}/graphics/presentation/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/presentation/*.h"
"${KYTY_SOURCE_DIR}/graphics/presentation/window/*.cpp"
@@ -247,7 +254,7 @@ foreach(shader_name IN LISTS gpu_tiler_shader_names)
add_custom_command(
OUTPUT "${shader_header}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_tiler_generated_dir}"
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
@@ -256,6 +263,22 @@ foreach(shader_name IN LISTS gpu_tiler_shader_names)
)
list(APPEND gpu_tiler_shader_headers "${shader_header}")
endforeach()
set(fault_buffer_shader_source "${gpu_tiler_shader_dir}/fault_buffer_process.comp")
set(fault_buffer_shader_spv "${gpu_tiler_generated_dir}/fault_buffer_process.spv")
set(fault_buffer_shader_header "${gpu_tiler_generated_dir}/fault_buffer_process_spv.h")
add_custom_command(
OUTPUT "${fault_buffer_shader_header}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_tiler_generated_dir}"
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
-DCACHING_PAGEBITS=14 -DMAX_PAGE_FAULTS=1024
-o "${fault_buffer_shader_spv}" "${fault_buffer_shader_source}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${fault_buffer_shader_spv} -DOUTPUT=${fault_buffer_shader_header}
-DSYMBOL=FAULT_BUFFER_PROCESS_SPV -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${fault_buffer_shader_source}"
VERBATIM
)
list(APPEND kyty_emulator_src "${fault_buffer_shader_header}")
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
@@ -271,7 +294,7 @@ foreach(shader_source IN LISTS gpu_blit_shader_sources)
add_custom_command(
OUTPUT "${shader_header}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
@@ -322,6 +345,7 @@ set(inc_headers
${KYTY_THIRD_PARTY_DIR}/fmt/include
${KYTY_THIRD_PARTY_DIR}/xxhash
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
${KYTY_THIRD_PARTY_DIR}/renderdoc
${KYTY_THIRD_PARTY_DIR}/stb
)
@@ -367,9 +391,8 @@ add_kyty_full_emulator_test(shader_cfg_tests "${KYTY_TESTS_DIR}/shaderCfgTests.c
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ScalarProvenanceTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ReadLaneElimination.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ScalarProvenance.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/SrtWalker.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ReadLaneElimination.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
)
target_link_libraries(scalar_provenance_tests fmt::fmt)
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
@@ -378,6 +401,7 @@ add_executable(page_manager_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
)
target_link_libraries(page_manager_tests common)
target_include_directories(page_manager_tests PRIVATE ${inc_headers})
add_executable(bit_array_tests EXCLUDE_FROM_ALL
@@ -387,6 +411,7 @@ target_include_directories(bit_array_tests PRIVATE ${inc_headers})
add_executable(ime_dialog_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ImeDialogTests.cpp"
"${KYTY_SOURCE_DIR}/libs/imeCommon.cpp"
"${KYTY_SOURCE_DIR}/libs/imeDialog.cpp"
)
target_include_directories(ime_dialog_tests PRIVATE ${inc_headers})
@@ -411,9 +436,8 @@ add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/shaderStageRuntime.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ResourceMaterialization.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ScalarProvenance.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/SrtWalker.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
)
target_link_libraries(shader_stage_runtime_tests fmt::fmt)
target_include_directories(shader_stage_runtime_tests PRIVATE ${inc_headers})
@@ -422,13 +446,12 @@ add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ResourceTrackingTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ScalarProvenance.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/SrtWalker.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/SrtPatcher.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ResourceTracking.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ResourceMaterialization.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ShaderInfoCollection.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/BindingLayout.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/shaderPixelParameter.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/BindingLayout.cpp"
)
target_link_libraries(resource_tracking_tests fmt::fmt)
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
@@ -449,6 +472,9 @@ add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
target_link_libraries(event_queue_lifetime_tests common fmt::fmt)
target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers})
add_kyty_full_emulator_test(kernel_file_system_tests
"${KYTY_TESTS_DIR}/KernelFileSystemTests.cpp")
add_executable(sync_on_address_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/SyncOnAddressTests.cpp"
"${KYTY_SOURCE_DIR}/kernel/syncOnAddress.cpp"
@@ -474,7 +500,7 @@ set(gpu_test_ms_depth_header
add_custom_command(
OUTPUT "${gpu_test_ms_depth_header}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_test_generated_dir}"
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
-o "${gpu_test_ms_depth_spv}" "${gpu_test_ms_depth_source}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${gpu_test_ms_depth_spv}
-DOUTPUT=${gpu_test_ms_depth_header} -DSYMBOL=GPU_TEST_MS_DEPTH_SPV
@@ -509,9 +535,12 @@ if(BUILD_TESTING)
add_test(NAME shader_stage_runtime COMMAND $<TARGET_FILE:shader_stage_runtime_tests>)
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
add_test(NAME kernel_file_system COMMAND $<TARGET_FILE:kernel_file_system_tests>)
add_test(NAME sync_on_address COMMAND $<TARGET_FILE:sync_on_address_tests>)
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
add_test(NAME shader_recompiler_alignbyte
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
add_test(NAME virtual_memory_allocation
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
add_test(NAME guest_red_zone_patcher
@@ -560,9 +589,11 @@ if(BUILD_TESTING)
shader_stage_runtime_tests
resource_tracking_tests
event_queue_lifetime_tests
kernel_file_system_tests
sync_on_address_tests
shader_recompiler_compute_tests
virtual_memory_allocation_tests
audio_out2_port_tests
)
endif()
+6 -3
View File
@@ -31,6 +31,9 @@ macOS support is experimental. The emulator is built for x86-64 and runs on Appl
Rosetta 2, with Vulkan provided by MoltenVK. A small number of titles have been verified in-game
on Apple Silicon hardware; see [Building on macOS](#building-on-macos).
Community game test results are available in the
[KytyPS5 Compatibility List](https://kytyps5.github.io/).
## Bugs and Issues
The project is in an early stage, so please be mindful when opening new issues. Expect crashes,
@@ -52,7 +55,7 @@ graphical glitches, low compatibility, and poor performance.
<tr>
<td align="center">
<strong>Neptunia ReVerse</strong><br>
<img src="docs/screenshots/ps5-04.png" width="300" alt="Minecraft Legends running in KytyPS5">
<img src="docs/screenshots/ps5-04.png" width="300" alt="Neptunia ReVerse running in KytyPS5">
</td>
<td align="center">
<strong>SILENT HILL: The Short Message</strong><br>
@@ -62,11 +65,11 @@ graphical glitches, low compatibility, and poor performance.
<tr>
<td align="center">
<strong>Hellboy</strong><br>
<img src="docs/screenshots/ps5-02.png" width="300" alt="Disgaea 6 running in KytyPS5">
<img src="docs/screenshots/ps5-02.png" width="300" alt="Hellboy running in KytyPS5">
</td>
<td align="center">
<strong>Paleo Pines</strong><br>
<img src="docs/screenshots/ps5-06.png" width="300" alt="Dreaming Sarah running in KytyPS5">
<img src="docs/screenshots/ps5-06.png" width="300" alt="Paleo Pines running in KytyPS5">
</td>
</tr>
</table>
+1
View File
@@ -24,6 +24,7 @@ target_link_libraries(common
Tracy::TracyClient
PRIVATE
cpuinfo
kyty_git_version
spdlog::spdlog
)
+3 -2
View File
@@ -3,6 +3,7 @@
#include "common/debug.h"
#include "common/logging/log.h"
#include "common/subsystems.h"
#include "kytyGitVersion.h"
#include <cstdio>
#include <cstdlib>
@@ -23,7 +24,7 @@ static std::string BuildFatalReport(const char* title, std::string_view text, co
DebugStack stack;
DebugStack::Trace(&stack);
std::string report = "--- Stack Trace ---\n";
std::string report = "--- Build ---\n" KYTY_BUILD_LABEL "\n--- Stack Trace ---\n";
for (int i = PRINT_STACK_FROM; i < stack.depth; i++) {
report += fmt::format("[{}] {:016x}\n", i - PRINT_STACK_FROM,
static_cast<uint64_t>(stack.GetAddr(i)));
@@ -34,7 +35,7 @@ static std::string BuildFatalReport(const char* title, std::string_view text, co
static int DbgReport(const char* title, std::string_view text, const char* file, int line) {
Log::WriteFatal(BuildFatalReport(title, text, file, line));
SubsystemsListSingleton::Instance()->ShutdownAll();
Subsystems::EmergencyShutdownActive();
return 1;
}
-21
View File
@@ -1,21 +0,0 @@
#include "common/commonSubsystem.h"
#include "common/byteBuffer.h" // IWYU pragma: associated
#include "common/common.h" // IWYU pragma: associated
#include "common/debug.h" // IWYU pragma: associated
#include "common/hash.h" // IWYU pragma: associated
#include "common/magicEnum.h" // IWYU pragma: associated
#include "common/singleton.h" // IWYU pragma: associated
#include "common/virtualMemory.h"
namespace Common {
KYTY_SUBSYSTEM_INIT(Common) {
VirtualMemory::Init();
}
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Common) {}
KYTY_SUBSYSTEM_DESTROY(Common) {}
} // namespace Common
-12
View File
@@ -1,12 +0,0 @@
#ifndef KYTY_COMMON_COMMON_SUBSYSTEM_H_
#define KYTY_COMMON_COMMON_SUBSYSTEM_H_
#include "common/subsystems.h"
namespace Common {
KYTY_SUBSYSTEM_DEFINE(Common);
} // namespace Common
#endif /* KYTY_COMMON_COMMON_SUBSYSTEM_H_ */
+12 -4
View File
@@ -9,15 +9,15 @@ namespace Config {
static std::unique_ptr<ConfigOptions> g_config;
KYTY_SUBSYSTEM_INIT(Config) {
void Initialize() {
EXIT_IF(g_config != nullptr);
g_config = std::make_unique<ConfigOptions>();
}
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Config) {}
KYTY_SUBSYSTEM_DESTROY(Config) {}
void Shutdown() {
g_config.reset();
}
void Load(const ConfigOptions& cfg) {
EXIT_IF(g_config == nullptr);
@@ -93,6 +93,10 @@ bool SpirvDebugPrintfEnabled() {
return g_config->spirv_debug_printf_enabled;
}
bool GpuAssistedValidationEnabled() {
return g_config->gpu_assisted_validation_enabled && g_config->vulkan_validation_enabled;
}
bool RenderDocEnabled() {
return g_config->renderdoc_enabled;
}
@@ -101,6 +105,10 @@ bool ReadbackLinearImagesEnabled() {
return g_config->readback_linear_images;
}
bool PlayGoHackEnabled() {
return g_config->playgo_hack_enabled;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool RedZoneProtectionEnabled() {
return g_config->red_zone_protection_enabled;
+13 -2
View File
@@ -2,7 +2,6 @@
#define KYTY_COMMON_EMULATOR_CONFIG_H_
#include "common/common.h"
#include "common/subsystems.h"
#include <filesystem>
#include <string>
@@ -10,7 +9,14 @@
namespace Config {
KYTY_SUBSYSTEM_DEFINE(Config);
void Initialize();
void Shutdown();
struct Lifecycle {
static constexpr const char* name = "Config";
static constexpr auto initialize = Config::Initialize;
static constexpr auto shutdown = Config::Shutdown;
};
enum class ShaderOptimizationType { None, Size, Performance };
@@ -43,8 +49,10 @@ struct ConfigOptions {
std::filesystem::path printf_output_file = "_kyty.txt";
ProfilerDirection profiler_direction = ProfilerDirection::None;
bool spirv_debug_printf_enabled = false;
bool gpu_assisted_validation_enabled = false;
bool renderdoc_enabled = false;
bool readback_linear_images = false;
bool playgo_hack_enabled = false;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool red_zone_protection_enabled = false;
#endif
@@ -77,8 +85,11 @@ ProfilerDirection GetProfilerDirection();
bool SpirvDebugPrintfEnabled();
bool GpuAssistedValidationEnabled();
bool RenderDocEnabled();
bool ReadbackLinearImagesEnabled();
bool PlayGoHackEnabled();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool RedZoneProtectionEnabled();
#endif
+10 -79
View File
@@ -2,7 +2,6 @@
#include <atomic>
#include <cstdio>
#include <cstdlib>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <windows.h> // IWYU pragma: keep
@@ -28,54 +27,14 @@ namespace Common::HostException {
static std::atomic<Handler> g_handler {nullptr};
static std::atomic_uint32_t g_install_state {0};
static thread_local bool g_in_exception_filter = false;
static_assert(decltype(g_handler)::is_always_lock_free);
static_assert(decltype(g_install_state)::is_always_lock_free);
[[noreturn]] static void FailFast(const char* reason) noexcept {
std::fputs("HostException fail-fast: ", stderr);
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
std::fputc('\n', stderr);
std::fflush(stderr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
#endif
std::_Exit(321);
}
class FilterScope final {
public:
FilterScope() noexcept {
if (g_in_exception_filter) {
FailFast("nested exception while resolving a host fault");
}
g_in_exception_filter = true;
}
~FilterScope() { g_in_exception_filter = false; }
KYTY_CLASS_NO_COPY(FilterScope);
};
static Handler LoadInstalledHandler() noexcept {
if (g_install_state.load(std::memory_order_acquire) == 0) {
FailFast("host exception handler is not installed");
}
const auto handler = g_handler.load(std::memory_order_acquire);
if (handler == nullptr) {
FailFast("host exception callback is null");
}
return handler;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
FilterScope filter_scope;
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) noexcept {
auto* exception_record = exception->ExceptionRecord;
if (exception_record->ExceptionCode == DBG_PRINTEXCEPTION_C ||
@@ -105,12 +64,6 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
} else if (exception_record->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) {
info.type = ExceptionType::IllegalInstruction;
} else {
printf("Unhandled win exception: code=0x%08" PRIx32 ", addr=0x%016" PRIx64
", rip=0x%016" PRIx64 ", rsp=0x%016" PRIx64 ", rbp=0x%016" PRIx64 "\n",
static_cast<uint32_t>(exception_record->ExceptionCode),
reinterpret_cast<uint64_t>(exception_record->ExceptionAddress),
exception->ContextRecord->Rip, exception->ContextRecord->Rsp,
exception->ContextRecord->Rbp);
return EXCEPTION_CONTINUE_SEARCH;
}
@@ -131,28 +84,21 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
info.r14 = exception->ContextRecord->R14;
info.r15 = exception->ContextRecord->R15;
const auto handler = LoadInstalledHandler();
return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
const auto handler = g_handler.load(std::memory_order_acquire);
if (handler != nullptr && handler(info)) {
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
#elif defined(__APPLE__)
static std::atomic<Handler> g_handler {nullptr};
static std::atomic_uint32_t g_install_state {0};
static thread_local bool g_in_exception_filter = false;
static_assert(decltype(g_handler)::is_always_lock_free);
static_assert(decltype(g_install_state)::is_always_lock_free);
[[noreturn]] static void FailFast(const char* reason) noexcept {
std::fputs("HostException fail-fast: ", stderr);
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
std::fputc('\n', stderr);
std::fflush(stderr);
std::_Exit(321);
}
// Translate the x86-64 page-fault error code (mcontext __es.__err) into an access type.
// bit 1 (0x2) = write, bit 4 (0x10) = instruction fetch, otherwise a read.
static AccessViolationType DecodeAccess(uint64_t err) {
@@ -170,11 +116,6 @@ static AccessViolationType DecodeAccess(uint64_t err) {
// re-executing the faulting instruction against the now-fixed protection. An unresolved
// fault restores the default disposition so the retry terminates the process.
static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
if (g_in_exception_filter) {
FailFast("nested exception while resolving a host fault");
}
g_in_exception_filter = true;
auto* uc = static_cast<ucontext_t*>(uctx);
const auto* mc = uc->uc_mcontext;
const auto& ss = mc->__ss;
@@ -210,14 +151,7 @@ static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
info.r15 = ss.__r15;
const auto handler = g_handler.load(std::memory_order_acquire);
if (handler == nullptr) {
FailFast("host exception callback is null");
}
const bool resolved = handler(info);
g_in_exception_filter = false;
if (resolved) {
if (handler != nullptr && handler(info)) {
return; // retry the faulting instruction against the fixed mapping
}
@@ -244,8 +178,6 @@ static void ChainToDefault(int signal_number) noexcept {
}
static void SignalHandler(int signal_number, siginfo_t* signal_info, void* native_context) {
FilterScope filter_scope;
auto* context = static_cast<ucontext_t*>(native_context);
auto* gregs = context->uc_mcontext.gregs;
@@ -289,9 +221,8 @@ static void SignalHandler(int signal_number, siginfo_t* signal_info, void* nativ
info.r14 = static_cast<uint64_t>(gregs[REG_R14]);
info.r15 = static_cast<uint64_t>(gregs[REG_R15]);
const auto handler = LoadInstalledHandler();
if (handler(info)) {
const auto handler = g_handler.load(std::memory_order_acquire);
if (handler != nullptr && handler(info)) {
return;
}
@@ -313,7 +244,7 @@ bool InstallHandler(Handler handler) {
g_handler.store(handler, std::memory_order_release);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (AddVectoredExceptionHandler(1, ExceptionFilter) == nullptr) {
if (AddVectoredExceptionHandler(0, ExceptionFilter) == nullptr) {
g_handler.store(nullptr, std::memory_order_release);
g_install_state.store(0, std::memory_order_release);
printf("AddVectoredExceptionHandler() failed\n");
+2 -8
View File
@@ -149,7 +149,7 @@ void WriteFatal(fmt::text_style style, std::string_view text) {
Flush();
}
KYTY_SUBSYSTEM_INIT(Log) {
void Initialize() {
g_initialized = true;
switch (Config::GetPrintfDirection()) {
case Config::OutputDirection::Silent: g_direction = Direction::Silent; break;
@@ -161,13 +161,7 @@ KYTY_SUBSYSTEM_INIT(Log) {
SetupLogger();
}
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Log) {
Flush();
std::lock_guard lock(g_logger_mutex);
g_logger.reset();
}
KYTY_SUBSYSTEM_DESTROY(Log) {
void Shutdown() {
Flush();
std::lock_guard lock(g_logger_mutex);
g_logger.reset();
+9 -2
View File
@@ -2,7 +2,6 @@
#define KYTY_COMMON_LOGGING_LOG_H_
#include "common/common.h"
#include "common/subsystems.h"
#include <fmt/color.h>
#include <fmt/printf.h>
@@ -10,7 +9,15 @@
namespace Log {
KYTY_SUBSYSTEM_DEFINE(Log);
void Initialize();
void Shutdown();
struct Lifecycle {
static constexpr const char* name = "Log";
static constexpr auto initialize = Log::Initialize;
static constexpr auto shutdown = Log::Shutdown;
static constexpr auto emergency_shutdown = Log::Shutdown;
};
enum class Direction { Silent, Console, File };
+1 -49
View File
@@ -15,56 +15,8 @@
#include "common/assert.h"
#include "common/platform/sysDbg.h"
struct FrameS {
struct FrameS* next;
void* ret_addr;
};
int WalkStack(int z_stack_depth, void** z_stack_trace) {
CONTEXT context;
// KNONVOLATILE_CONTEXT_POINTERS NvContext;
PRUNTIME_FUNCTION runtime_function = nullptr;
PVOID handler_data = nullptr;
ULONG64 establisher_frame = 0;
ULONG64 image_base = 0;
RtlCaptureContext(&context);
int frame = 0;
while (true) {
if (frame >= z_stack_depth) {
break;
}
z_stack_trace[frame] = reinterpret_cast<void*>(context.Rip);
frame++;
runtime_function = RtlLookupFunctionEntry(context.Rip, &image_base, nullptr);
if (runtime_function == nullptr) {
break;
}
// RtlZeroMemory(&NvContext, sizeof(KNONVOLATILE_CONTEXT_POINTERS));
RtlVirtualUnwind(0, image_base, context.Rip, runtime_function, &context, &handler_data,
&establisher_frame, nullptr /*&NvContext*/);
if (context.Rip == 0u) {
break;
}
}
return frame;
}
void SysStackWalk(void** stack, int* depth) {
int n = WalkStack(*depth, stack);
*depth = n;
// unwind_info_t info = {stack, 0, *depth};
// _Unwind_Backtrace(&trace_fcn, &info);
// *depth = info.depth;
*depth = static_cast<int>(CaptureStackBackTrace(0, static_cast<DWORD>(*depth), stack, nullptr));
}
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
+5 -14
View File
@@ -1,7 +1,6 @@
#include "common/profiler.h"
#include "common/emulatorConfig.h"
#include "common/subsystems.h"
#include <algorithm>
#include <common/TracyProtocol.hpp>
@@ -55,13 +54,7 @@ void SetThreadName(const char* name) {
}
}
void Close() {
if (tracy::ProfilerAvailable()) {
tracy::ShutdownProfiler();
}
}
KYTY_SUBSYSTEM_INIT(Profiler) {
void Initialize() {
switch (Config::GetProfilerDirection()) {
case Config::ProfilerDirection::Network:
if (!tracy::ProfilerAvailable()) {
@@ -78,12 +71,10 @@ KYTY_SUBSYSTEM_INIT(Profiler) {
}
}
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Profiler) {
Close();
}
KYTY_SUBSYSTEM_DESTROY(Profiler) {
Close();
void Shutdown() {
if (tracy::ProfilerAvailable()) {
tracy::ShutdownProfiler();
}
}
} // namespace Profiler
+9 -2
View File
@@ -2,7 +2,6 @@
#define KYTY_COMMON_PROFILER_H_
#include "common/common.h"
#include "common/subsystems.h"
#include <cstdint>
#include <optional>
@@ -42,7 +41,15 @@ private:
void EndBlock();
void SetThreadName(const char* name);
KYTY_SUBSYSTEM_DEFINE(Profiler);
void Initialize();
void Shutdown();
struct Lifecycle {
static constexpr const char* name = "Profiler";
static constexpr auto initialize = Profiler::Initialize;
static constexpr auto shutdown = Profiler::Shutdown;
static constexpr auto emergency_shutdown = Profiler::Shutdown;
};
} // namespace Profiler
+132
View File
@@ -0,0 +1,132 @@
#ifndef EMULATOR_SRC_COMMON_SLOTVECTOR_H_
#define EMULATOR_SRC_COMMON_SLOTVECTOR_H_
#include "common/assert.h"
#include "common/abi.h"
#include <compare>
#include <cstdint>
#include <deque>
#include <functional>
#include <limits>
#include <optional>
#include <utility>
#include <vector>
namespace Common {
struct SlotId {
static constexpr uint32_t INVALID_INDEX = std::numeric_limits<uint32_t>::max();
constexpr SlotId() noexcept = default;
constexpr SlotId(uint32_t value) noexcept: index(value), generation(1) {}
constexpr SlotId(uint32_t value, uint32_t slot_generation) noexcept
: index(value), generation(slot_generation) {}
[[nodiscard]] constexpr explicit operator bool() const noexcept { return index != INVALID_INDEX; }
constexpr auto operator<=>(const SlotId&) const noexcept = default;
uint32_t index = INVALID_INDEX;
uint32_t generation = 0;
};
// Stable-address slot storage for cache resources that are intentionally non-movable.
template <typename T>
class SlotVector {
public:
SlotVector() = default;
KYTY_CLASS_NO_COPY(SlotVector);
[[nodiscard]] T& operator[](SlotId id) noexcept {
EXIT_IF(!is_allocated(id));
return *m_values[id.index].value;
}
[[nodiscard]] const T& operator[](SlotId id) const noexcept {
EXIT_IF(!is_allocated(id));
return *m_values[id.index].value;
}
[[nodiscard]] T* try_get(SlotId id) noexcept {
return is_allocated(id) ? &*m_values[id.index].value : nullptr;
}
[[nodiscard]] const T* try_get(SlotId id) const noexcept {
return is_allocated(id) ? &*m_values[id.index].value : nullptr;
}
[[nodiscard]] bool is_allocated(SlotId id) const noexcept {
return id && id.index < m_values.size() &&
m_values[id.index].generation == id.generation && m_values[id.index].value.has_value();
}
template <typename... Args>
[[nodiscard]] SlotId insert(Args&&... args) {
uint32_t index = 0;
if (m_free_list.empty()) {
index = static_cast<uint32_t>(m_values.size());
m_values.emplace_back();
m_values.back().value.emplace(std::forward<Args>(args)...);
} else {
index = m_free_list.back();
m_free_list.pop_back();
EXIT_IF(m_values[index].value.has_value());
m_values[index].value.emplace(std::forward<Args>(args)...);
}
++m_size;
return SlotId {index, m_values[index].generation};
}
void erase(SlotId id) noexcept {
EXIT_IF(!is_allocated(id));
auto& slot = m_values[id.index];
slot.value.reset();
if (++slot.generation == 0) {
slot.generation = 1;
}
m_free_list.push_back(id.index);
--m_size;
}
[[nodiscard]] size_t size() const noexcept { return m_size; }
[[nodiscard]] size_t capacity() const noexcept { return m_values.size(); }
template <typename F>
void ForEach(F&& fn) {
for (uint32_t index = 0; index < m_values.size(); ++index) {
if (m_values[index].value) {
fn(SlotId {index, m_values[index].generation}, *m_values[index].value);
}
}
}
template <typename F>
void ForEach(F&& fn) const {
for (uint32_t index = 0; index < m_values.size(); ++index) {
if (m_values[index].value) {
fn(SlotId {index, m_values[index].generation}, *m_values[index].value);
}
}
}
private:
struct Slot {
std::optional<T> value;
uint32_t generation = 1;
};
std::deque<Slot> m_values;
std::vector<uint32_t> m_free_list;
size_t m_size = 0;
};
} // namespace Common
template <>
struct std::hash<Common::SlotId> {
[[nodiscard]] size_t operator()(Common::SlotId id) const noexcept {
return std::hash<uint64_t> {}((static_cast<uint64_t>(id.generation) << 32u) | id.index);
}
};
#endif // EMULATOR_SRC_COMMON_SLOTVECTOR_H_
+25 -214
View File
@@ -1,232 +1,43 @@
#include "common/subsystems.h"
#include "common/assert.h"
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <memory>
#include <string>
namespace Common {
class SubsystemPrivate {
public:
SubsystemPrivate() = default;
virtual ~SubsystemPrivate() = default;
static Subsystems* g_active_subsystems = nullptr;
KYTY_CLASS_NO_COPY(SubsystemPrivate);
Subsystems::Subsystems(bool print): m_print(print) {
m_active.reserve(16);
g_active_subsystems = this;
}
bool failed {false};
std::string fail_msg;
};
class SubsystemsListPrivate {
public:
explicit SubsystemsListPrivate(SubsystemsList& p): parent(p) {}
virtual ~SubsystemsListPrivate() = default;
void SetArgs(int argc, char** argv) {
this->m_argc = argc;
this->m_argv = argv;
Subsystems::~Subsystems() {
Destroy();
if (g_active_subsystems == this) {
g_active_subsystems = nullptr;
}
}
void Add(Subsystem* s, std::initializer_list<Subsystem*> deps) {
EXIT_IF(!s);
const char* name = s->Id();
EXIT_IF(FindByName(name) != nullptr);
auto nl = std::make_unique<SubsListStruct>();
nl->s = s;
nl->name = name;
nl->deps = nullptr;
nl->next = std::move(list);
nl->prev_init = nullptr;
auto* node = nl.get();
list = std::move(nl);
for (auto* dep: deps) {
const char* str = dep->Id();
auto l = std::make_unique<DepsListStruct>();
l->dep_name = str;
l->next = std::move(node->deps);
node->deps = std::move(l);
void Subsystems::Destroy() {
for (auto it = m_active.rbegin(); it != m_active.rend(); ++it) {
if (it->shutdown != nullptr) {
it->shutdown();
}
node->initialized = false;
}
m_active.clear();
}
bool InitAll(bool print_msg) {
while (SubsListStruct* n = FindNextToInitialize()) {
n->s->Init(&parent);
if (n->s->m_p->failed) {
fail_msg = n->s->m_p->fail_msg.c_str();
fail_name = n->name;
return false;
}
if (print_msg) {
printf("Initialized: %s\n", n->name);
}
n->initialized = true;
SubsListStruct* last = last_init;
last_init = n;
n->prev_init = last;
void Subsystems::EmergencyShutdown() {
for (auto it = m_active.rbegin(); it != m_active.rend(); ++it) {
if (it->emergency_shutdown != nullptr) {
it->emergency_shutdown();
}
return true;
}
m_active.clear();
}
void DestroyAll(bool print_msg) {
for (SubsListStruct* n = last_init; n != nullptr; n = n->prev_init) {
n->s->Destroy(&parent);
n->initialized = false;
if (print_msg) {
printf("Destroyed: %s\n", n->name);
}
}
last_init = nullptr;
void Subsystems::EmergencyShutdownActive() {
if (g_active_subsystems != nullptr) {
g_active_subsystems->EmergencyShutdown();
}
void ShutdownAll() {
for (SubsListStruct* n = last_init; n != nullptr; n = n->prev_init) {
n->s->UnexpectedShutdown(&parent);
n->initialized = false;
}
last_init = nullptr;
}
struct DepsListStruct {
const char* dep_name;
std::unique_ptr<DepsListStruct> next;
};
struct SubsListStruct {
Subsystem* s;
const char* name;
std::unique_ptr<DepsListStruct> deps;
std::unique_ptr<SubsListStruct> next;
SubsListStruct* prev_init;
bool initialized;
};
[[nodiscard]] SubsListStruct* FindByName(const char* name) const {
for (SubsListStruct* n = list.get(); n != nullptr; n = n->next.get()) {
if (std::strcmp(n->name, name) == 0) {
return n;
}
}
return nullptr;
}
[[nodiscard]] SubsListStruct* FindNextToInitialize() const {
for (SubsListStruct* n = list.get(); n != nullptr; n = n->next.get()) {
if (n->initialized) {
continue;
}
DepsListStruct* d = n->deps.get();
for (; d != nullptr; d = d->next.get()) {
SubsListStruct* s = FindByName(d->dep_name);
if ((s == nullptr) || !s->initialized) {
break;
}
}
if (d == nullptr) {
return n;
}
}
return nullptr;
}
KYTY_CLASS_NO_COPY(SubsystemsListPrivate);
std::unique_ptr<SubsListStruct> list;
SubsListStruct* last_init = nullptr;
int m_argc = 0;
char** m_argv = nullptr;
const char* fail_msg = nullptr;
const char* fail_name = nullptr;
SubsystemsList& parent;
};
SubsystemsList::SubsystemsList(): m_p(std::make_unique<SubsystemsListPrivate>(*this)) {}
SubsystemsList::~SubsystemsList() = default;
void SubsystemsList::Add(Subsystem* s, std::initializer_list<Subsystem*> deps) {
m_p->Add(s, deps);
}
bool SubsystemsList::InitAll(bool print_msg) {
return m_p->InitAll(print_msg);
}
void SubsystemsList::DestroyAll(bool print_msg) {
m_p->DestroyAll(print_msg);
}
int* SubsystemsList::GetArgc() {
return &m_p->m_argc;
}
char** SubsystemsList::GetArgv() {
return m_p->m_argv;
}
Subsystem::Subsystem(): m_p(std::make_unique<SubsystemPrivate>()) {}
Subsystem::~Subsystem() = default;
void Subsystem::Fail(const char* format, ...) {
va_list args {};
va_start(args, format);
va_list args_copy {};
va_copy(args_copy, args);
int len = std::vsnprintf(nullptr, 0, format, args_copy);
va_end(args_copy);
if (len > 0) {
std::string msg(static_cast<size_t>(len) + 1, '\0');
std::vsnprintf(msg.data(), msg.size(), format, args);
m_p->fail_msg = msg.c_str();
m_p->failed = true;
}
va_end(args);
}
const char* SubsystemsList::GetFailName() const {
return m_p->fail_name;
}
const char* SubsystemsList::GetFailMsg() const {
return m_p->fail_msg;
}
void SubsystemsList::SetArgs(int argc, char* argv[]) {
m_p->SetArgs(argc, argv);
}
void SubsystemsList::ShutdownAll() {
m_p->ShutdownAll();
}
} // namespace Common
+41 -70
View File
@@ -2,90 +2,61 @@
#define KYTY_COMMON_SUBSYSTEMS_H_
#include "common/common.h"
#include "common/singleton.h"
#include <initializer_list>
#include <memory>
#include <cstdio>
#include <vector>
namespace Common {
class Subsystem;
class SubsystemsListPrivate;
class SubsystemPrivate;
class SubsystemsList {
class Subsystems {
public:
SubsystemsList();
virtual ~SubsystemsList();
void SetArgs(int argc, char* argv[]);
using Callback = void (*)();
// void Add(Subsystem* s, const char* name, ...);
void Add(Subsystem* s, std::initializer_list<Subsystem*> deps);
explicit Subsystems(bool print = false);
~Subsystems();
bool InitAll(bool print_msg = false);
void DestroyAll(bool print_msg = false);
template <typename Lifecycle>
void Initialize() {
Lifecycle::initialize();
m_active.push_back({ShutdownCallback<Lifecycle>(), EmergencyCallback<Lifecycle>()});
if (m_print) {
std::printf("Initialized: %s\n", Lifecycle::name);
}
}
int* GetArgc();
char** GetArgv();
void Destroy();
void EmergencyShutdown();
[[nodiscard]] const char* GetFailName() const;
[[nodiscard]] const char* GetFailMsg() const;
static void EmergencyShutdownActive();
void ShutdownAll();
static SubsystemsList* Instance() { return Common::Singleton<SubsystemsList>::Instance(); }
KYTY_CLASS_NO_COPY(SubsystemsList);
KYTY_CLASS_NO_COPY(Subsystems);
private:
std::unique_ptr<SubsystemsListPrivate> m_p;
template <typename Lifecycle>
static consteval Callback ShutdownCallback() {
if constexpr (requires { Lifecycle::shutdown; }) {
return Lifecycle::shutdown;
}
return nullptr;
}
template <typename Lifecycle>
static consteval Callback EmergencyCallback() {
if constexpr (requires { Lifecycle::emergency_shutdown; }) {
return Lifecycle::emergency_shutdown;
}
return nullptr;
}
struct Entry {
Callback shutdown;
Callback emergency_shutdown;
};
std::vector<Entry> m_active;
bool m_print;
};
using SubsystemsListSingleton = Common::Singleton<SubsystemsList>;
class Subsystem {
public:
Subsystem();
virtual ~Subsystem();
virtual const char* Id() = 0;
virtual void Init(SubsystemsList* parent) = 0;
virtual void Destroy(SubsystemsList* parent) = 0;
virtual void UnexpectedShutdown(SubsystemsList* parent) = 0;
friend class SubsystemsListPrivate;
KYTY_CLASS_NO_COPY(Subsystem);
protected:
void Fail(const char* format, ...) KYTY_FORMAT_PRINTF(2, 3);
private:
std::unique_ptr<SubsystemPrivate> m_p;
};
#define KYTY_SUBSYSTEM_DEFINE(s) \
class s##Subsystem: public Common::Subsystem { \
public: \
static Subsystem* Instance() { return Common::Singleton<s##Subsystem>::Instance(); } \
const char* Id() { return #s; } \
void Init(Common::SubsystemsList* parent); \
void Destroy(Common::SubsystemsList* parent); \
void UnexpectedShutdown(Common::SubsystemsList* parent); \
}; \
typedef Common::Singleton<s##Subsystem> s##SubsystemSingleton;
#define KYTY_SUBSYSTEM_INIT(s) \
void s##Subsystem::Init([[maybe_unused]] Common::SubsystemsList* parent)
#define KYTY_SUBSYSTEM_DESTROY(s) \
void s##Subsystem::Destroy([[maybe_unused]] Common::SubsystemsList* parent)
#define KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(s) \
void s##Subsystem::UnexpectedShutdown([[maybe_unused]] Common::SubsystemsList* parent)
// #define KYTY_SUBSYSTEM_ADD(list, s, ...) list.push_back(s##SubsystemSingleton::Instance(), #s,
// __VA_ARGS__); #define KYTY_SUBSYSTEM_ADD2(list, ...) list.Add2(s##SubsystemSingleton::Instance(),
// __VA_ARGS__);
} // namespace Common
#endif /* KYTY_COMMON_SUBSYSTEMS_H_ */
+1 -5
View File
@@ -252,15 +252,11 @@ static thread_id_t g_main_thread;
static int g_main_thread_int;
static std::atomic<int> g_thread_counter = 0;
KYTY_SUBSYSTEM_INIT(Threads) {
void InitializeThreads() {
g_main_thread = std::this_thread::get_id();
g_main_thread_int = Thread::GetThreadIdUnique();
}
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Threads) {}
KYTY_SUBSYSTEM_DESTROY(Threads) {}
Thread::Thread(thread_func_t func, void* arg)
: m_thread(std::make_unique<ThreadPrivate>(func, arg)) {
while (!m_thread->started) {
+1 -2
View File
@@ -2,14 +2,13 @@
#define KYTY_COMMON_THREADS_H_
#include "common/common.h"
#include "common/subsystems.h"
#include <memory>
#include <string>
namespace Common {
KYTY_SUBSYSTEM_DEFINE(Threads);
void InitializeThreads();
using thread_func_t = void (*)(void*);
using wait_poll_func_t = void (*)();
+39 -38
View File
@@ -2,7 +2,6 @@
#include "common/abi.h"
#include "common/assert.h"
#include "common/commonSubsystem.h"
#include "common/emulatorConfig.h"
#include "common/file.h"
#include "common/logging/log.h"
@@ -16,6 +15,7 @@
#include "kernel/fileSystem.h"
#include "kernel/memory.h"
#include "kernel/pthread.h"
#include "kytyGitVersion.h"
#include "libs/agc.h"
#include "libs/audio.h"
#include "libs/controller.h"
@@ -27,13 +27,31 @@
#include <cstdlib>
#include <filesystem>
#include <thread>
namespace Emulator {
static void PrintSystemInfo() {
Common::SystemInfo info = Common::GetSystemInfo();
const Common::SystemInfo info = Common::GetSystemInfo();
LOGF("ProcessorName = %s\n", info.ProcessorName.c_str());
#if defined(__APPLE__)
static constexpr auto platform_name = "macOS";
#elif KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static constexpr auto platform_name = "Windows";
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
static constexpr auto platform_name = "Linux";
#else
static constexpr auto platform_name = "Unknown";
#endif
LOGF("Build\n"
" version: %s\n\n"
"Host\n"
" os: %s\n"
" cpu: %s\n"
" threads: %u\n\n",
KYTY_BUILD_LABEL, platform_name, info.ProcessorName.c_str(),
std::thread::hardware_concurrency());
}
static void KytyClose() {
@@ -43,7 +61,7 @@ static void KytyClose() {
LOGF("done!\n");
Common::SubsystemsListSingleton::Instance()->ShutdownAll();
Common::Subsystems::EmergencyShutdownActive();
}
static void MountOrCreateDir(const std::filesystem::path& dir, const std::string& point) {
@@ -105,30 +123,13 @@ static void ClearDebugTextureFolder() {
}
}
static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path& param_json) {
static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path& param_json,
Common::Subsystems& subsystems) {
EXIT_IF(!Common::Thread::IsMainThread());
auto* slist = Common::SubsystemsList::Instance();
auto* audio = Libs::Audio::AudioSubsystem::Instance();
auto* config = Config::ConfigSubsystem::Instance();
auto* controller = Libs::Controller::ControllerSubsystem::Instance();
auto* core = Common::CommonSubsystem::Instance();
auto* file_system = Libs::LibKernel::FileSystem::FileSystemSubsystem::Instance();
auto* graphics = Libs::Graphics::GraphicsSubsystem::Instance();
auto* log = Log::LogSubsystem::Instance();
auto* memory = Libs::LibKernel::Memory::MemorySubsystem::Instance();
auto* network = Libs::Network::NetworkSubsystem::Instance();
auto* profiler = Profiler::ProfilerSubsystem::Instance();
auto* pthread = Libs::LibKernel::PthreadSubsystem::Instance();
auto* timer = Loader::Timer::TimerSubsystem::Instance();
slist->Add(config, {core});
slist->InitAll(true);
subsystems.Initialize<Config::Lifecycle>();
Config::Load(cfg);
slist->Add(log, {core, config});
slist->InitAll(true);
subsystems.Initialize<Log::Lifecycle>();
if (Common::File::IsFileExisting(param_json)) {
Loader::SystemContentLoadParamSfo(param_json);
@@ -138,17 +139,16 @@ static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path&
}
}
slist->Add(audio, {core, log, pthread, memory});
slist->Add(controller, {core, log, config});
slist->Add(file_system, {core, log, pthread});
slist->Add(graphics, {core, log, pthread, memory, config, profiler, controller});
slist->Add(memory, {core, log});
slist->Add(network, {core, log, pthread});
slist->Add(profiler, {core, config});
slist->Add(pthread, {core, log, timer});
slist->Add(timer, {core, log});
slist->InitAll(true);
// Initialization order is explicit; destruction is automatic and reversed.
subsystems.Initialize<Loader::Timer::Lifecycle>();
subsystems.Initialize<Libs::LibKernel::PthreadLifecycle>();
subsystems.Initialize<Profiler::Lifecycle>();
subsystems.Initialize<Libs::Network::Lifecycle>();
subsystems.Initialize<Libs::LibKernel::Memory::Lifecycle>();
subsystems.Initialize<Libs::LibKernel::FileSystem::Lifecycle>();
subsystems.Initialize<Libs::Controller::Lifecycle>();
subsystems.Initialize<Libs::Audio::Lifecycle>();
subsystems.Initialize<Libs::Graphics::Lifecycle>();
}
static void LoadElf(const std::filesystem::path& elf, bool dbg_print_reloc = false,
@@ -189,8 +189,9 @@ void Run(const RunOptions& options) {
EXIT("ELF is required\n");
}
const auto param_json = options.app0_dir / "sce_sys" / "param.json";
Init(options.config, param_json);
const auto param_json = options.app0_dir / "sce_sys" / "param.json";
Common::Subsystems subsystems(true);
Init(options.config, param_json, subsystems);
ClearDebugTextureFolder();
@@ -7,6 +7,7 @@
#include "graphics/host_gpu/renderer/renderContext.h"
#include <cstdint>
#include <span>
#include <vector>
namespace Libs::Graphics {
@@ -30,10 +31,9 @@ private:
friend class CommandProcessor;
struct BufferCursor {
uint32_t* next_packet = nullptr;
uint32_t remaining_dw = 0;
uint32_t total_dw = 0;
uint32_t deferred_advance_dw = 0;
std::span<const uint32_t> commands;
uint32_t offset_dw = 0;
uint32_t deferred_advance_dw = 0;
};
std::vector<BufferCursor> m_buffer_stack;
@@ -50,7 +50,8 @@ public:
int64_t flip_arg = 0;
};
explicit CommandProcessor(RenderContext& renderer): m_renderer(renderer) {}
CommandProcessor(RenderContext& renderer, int interrupt_event_id)
: m_renderer(renderer), m_interrupt_event_id(interrupt_event_id) {}
~CommandProcessor() = default;
KYTY_CLASS_NO_COPY(CommandProcessor);
@@ -103,7 +104,7 @@ public:
uint32_t mode);
void DispatchIndirect(uint32_t data_offset, uint32_t mode);
void WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index);
void TriggerEvent(uint32_t event_type, uint32_t event_index);
void TriggerEvent(uint32_t event_type, uint32_t event_index, uint64_t event_address = 0);
void SetUserDataMarker(HW::UserSgprType type) { m_user_data_marker = type; }
[[nodiscard]] HW::UserSgprType GetUserDataMarker() const { return m_user_data_marker; }
@@ -112,6 +113,7 @@ public:
void SetCeComplete(bool complete) { m_ce_complete = complete; }
void WaitCe();
void WaitDeDiff(uint32_t diff);
void WaitForRewind(bool valid);
void IncrementDe();
void IncrementCe();
@@ -130,13 +132,14 @@ public:
const volatile void* address, uint32_t count_in_dwords);
[[nodiscard]] bool ShouldSkipPredicatedPackets() const { return m_predicate_skip; }
Pm4ProcessResult Process(Pm4Execution& execution, uint32_t* buffer, uint32_t size_dw);
void ProcessIndirectBuffer(uint32_t* buffer, uint32_t size_dw);
Pm4ProcessResult Process(Pm4Execution& execution, std::span<const uint32_t> commands);
void ProcessIndirectBuffer(std::span<const uint32_t> commands);
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
[[nodiscard]] uint64_t GetSubmitId() const { return m_submit_id; }
void SetSubmitId(uint64_t submit_id) { m_submit_id = submit_id; }
[[nodiscard]] bool IsAsyncComputeQueue() const { return m_interrupt_event_id >= 0x20; }
private:
template <typename T>
@@ -150,10 +153,10 @@ private:
uint32_t render_target_slice_offset, uint32_t first_vertex,
uint32_t first_instance);
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
RenderCommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
void CheckBuffer() const { GetScheduler().CheckActive(); }
GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
void CheckBuffer() const { GetScheduler().CheckActive(); }
GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
RenderContext& m_renderer;
HW::Context m_ctx;
@@ -176,9 +179,11 @@ private:
uint32_t m_const_ram[0x3000] = {0};
FlipInfo m_flip;
uint64_t m_submit_id = 0;
bool m_predicate_skip = false;
FlipInfo m_flip;
const int m_interrupt_event_id;
uint64_t m_submit_id = 0;
uint64_t m_synthetic_occlusion_counter = 0;
bool m_predicate_skip = false;
};
} // namespace Libs::Graphics
@@ -35,7 +35,6 @@ constexpr auto MakeContextDispatchTable() {
}
g_hw_ctx_func[Pm4::CB_TARGET_MASK] = HwCtxSetRenderTargetMask;
g_hw_ctx_func[Pm4::PA_SC_GENERIC_SCISSOR_TL] = HwCtxSetGenericScissor;
g_hw_ctx_func[Pm4::CB_BLEND_RED] = HwCtxSetBlendColor;
g_hw_ctx_func[Pm4::CB_DCC_CONTROL] = HwCtxSetCbDccControl;
g_hw_ctx_func[Pm4::DB_STENCIL_CONTROL] = HwCtxSetStencilControl;
g_hw_ctx_func[Pm4::DB_STENCILREFMASK] = HwCtxSetStencilMask;
@@ -82,7 +81,6 @@ constexpr auto MakeContextDispatchTable() {
g_hw_ctx_func[Pm4::VGT_PRIMITIVEID_RESET] = HwCtxSetPrimitiveIdReset;
g_hw_ctx_func[Pm4::PA_CL_OBJPRIM_ID_CNTL] = HwCtxSetObjprimIdControl;
g_hw_ctx_func[Pm4::VGT_SHADER_STAGES_EN] = HwCtxSetShaderStages;
g_hw_ctx_func[Pm4::PA_CL_GB_VERT_CLIP_ADJ] = HwCtxSetGuardBands;
for (uint32_t slot = 0; slot < 8; slot++) {
g_hw_ctx_func[Pm4::CB_COLOR0_BASE + slot * 15] = HwCtxSetRenderTarget;
@@ -217,6 +215,7 @@ constexpr auto MakeOpcodeDispatchTable() {
g_cp_op_func[Pm4::IT_COND_EXEC] = CpOpCondExec;
g_cp_op_func[Pm4::IT_SET_PREDICATION] = CpOpSetPredication;
g_cp_op_func[Pm4::IT_WRITE_DATA] = CpOpWriteData;
g_cp_op_func[Pm4::IT_WAIT_REG_MEM] = CpOpWaitRegMem32;
g_cp_op_func[Pm4::IT_INDIRECT_BUFFER] = CpOpIndirectBuffer;
g_cp_op_func[Pm4::IT_COPY_DATA] = CpOpCopyData;
g_cp_op_func[Pm4::IT_EVENT_WRITE] = CpOpEventWrite;
@@ -224,6 +223,7 @@ constexpr auto MakeOpcodeDispatchTable() {
g_cp_op_func[Pm4::IT_EVENT_WRITE_EOS] = CpOpEventWriteEos;
g_cp_op_func[Pm4::IT_DMA_DATA] = CpOpDmaData;
g_cp_op_func[Pm4::IT_ACQUIRE_MEM] = CpOpAcquireMem;
g_cp_op_func[Pm4::IT_REWIND] = CpOpRewind;
g_cp_op_func[Pm4::IT_SET_CONTEXT_REG] = CpOpSetContextReg;
g_cp_op_func[Pm4::IT_SET_SH_REG] = CpOpSetShaderReg;
g_cp_op_func[Pm4::IT_SET_UCONFIG_REG] = CpOpSetUconfigReg;
@@ -238,6 +238,7 @@ constexpr auto MakeOpcodeDispatchTable() {
g_cp_op_func[Pm4::IT_WAIT_ON_CE_COUNTER] = CpOpWaitOnCeCounter;
g_cp_op_func[Pm4::IT_WAIT_ON_DE_COUNTER_DIFF] = CpOpWaitOnDeCounterDiff;
g_cp_op_func[Pm4::IT_GET_LOD_STATS] = CpOpGetLodStats;
g_cp_op_func[Pm4::IT_WAIT_REG_MEM_64] = CpOpWaitRegMem64;
return g_cp_op_func;
}
@@ -246,12 +247,10 @@ constexpr auto MakeCustomOpcodeDispatchTable() {
std::array<cp_op_parser_func_t, Pm4::R_NUM> g_cp_op_custom_func {};
g_cp_op_custom_func[Pm4::R_DISPATCH_RESET] = CpOpDispatchReset;
g_cp_op_custom_func[Pm4::R_WAIT_MEM_32] = CpOpWaitRegMem32;
g_cp_op_custom_func[Pm4::R_WAIT_FLIP_DONE] = CpOpWaitFlipDone;
g_cp_op_custom_func[Pm4::R_PUSH_MARKER] = CpOpPushMarker;
g_cp_op_custom_func[Pm4::R_POP_MARKER] = CpOpPopMarker;
g_cp_op_custom_func[Pm4::R_ACQUIRE_MEM] = CpOpAcquireMem;
g_cp_op_custom_func[Pm4::R_WAIT_MEM_64] = CpOpWaitRegMem64;
g_cp_op_custom_func[Pm4::R_FLIP] = CpOpFlip;
g_cp_op_custom_func[Pm4::R_RELEASE_MEM] = CpOpReleaseMem;
g_cp_op_custom_func[Pm4::R_CONTEXT_STATE] = CpOpContextState;
@@ -56,7 +56,6 @@ uint32_t HwCtxSetWindowScissor(CommandProcessor&, uint32_t, uint32_t, const uint
uint32_t HwCtxSetClipRect(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetRenderTargetMask(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetGenericScissor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetBlendColor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetCbDccControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetStencilControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetStencilMask(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
@@ -90,7 +89,6 @@ uint32_t HwCtxSetDrawPayloadControl(CommandProcessor&, uint32_t, uint32_t, const
uint32_t HwCtxSetPrimitiveIdReset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetObjprimIdControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetShaderStages(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetGuardBands(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetRenderTarget(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetColorInfo(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetBlendControl(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
@@ -133,6 +131,7 @@ uint32_t CpOpDrawIndirectMulti(CommandProcessor&, uint32_t, const uint32_t*, uin
uint32_t CpOpDrawIndex(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDrawIndexOffset(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpPfpSyncMe(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpRewind(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpIndexType(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpNumInstances(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDrawIndexAuto(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
@@ -71,19 +71,6 @@ constexpr uint32_t NormalizeRegisterOffset(uint32_t raw_offset) {
return raw_offset & ~RegisterSelectorMask;
}
// Indirect Cx descriptors retain their selector. Selector 1 offsets 0..31 address the
// SPI_PS_INPUT_CNTL register bank; ordinary context-register offsets remain unchanged.
constexpr uint32_t DecodeIndirectCxRegisterOffset(uint32_t raw_offset) {
const auto offset = NormalizeRegisterOffset(raw_offset);
return (raw_offset & RegisterSelectorMask) == Pm4::CX_PS_SHADER_USAGE_BASE && offset < 32u
? Pm4::SPI_PS_INPUT_CNTL_0 + offset
: offset;
}
static_assert(DecodeIndirectCxRegisterOffset(Pm4::CX_PS_SHADER_USAGE_BASE + 2u) ==
Pm4::SPI_PS_INPUT_CNTL_0 + 2u);
static_assert(DecodeIndirectCxRegisterOffset(Pm4::DB_Z_INFO) == Pm4::DB_Z_INFO);
bool ReleaseMemGcrNeedsBarrier(uint32_t eop_event_type, uint32_t gcr_cntl) {
return eop_event_type != 0x28u ||
(gcr_cntl & (GcrGl2MetadataInvalidate | GcrGl0VectorInvalidate | GcrGl1Invalidate |
@@ -103,6 +90,128 @@ void LogUnknownReleaseMemGcr(uint32_t gcr_cntl) {
} // namespace
static HW::BlendControl DecodeBlendControl(uint32_t value) {
HW::BlendControl r {};
r.color_srcblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, COLOR_SRCBLEND);
r.color_comb_fcn = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, COLOR_COMB_FCN);
r.color_destblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, COLOR_DESTBLEND);
r.alpha_srcblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ALPHA_SRCBLEND);
r.alpha_comb_fcn = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ALPHA_COMB_FCN);
r.alpha_destblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ALPHA_DESTBLEND);
r.separate_alpha_blend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, SEPARATE_ALPHA_BLEND) != 0;
r.enable = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ENABLE) != 0;
return r;
}
static HW::ClipControl DecodeClipControl(uint32_t value) {
HW::ClipControl r {};
r.user_clip_planes = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, UCP_ENA);
r.user_clip_plane_mode = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, PS_UCP_MODE);
r.dx_clip_space = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, DX_CLIP_SPACE_DEF) != 0;
r.vertex_kill_any = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, VTX_KILL_OR) != 0;
r.min_z_clip_disable = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, ZCLIP_NEAR_DISABLE) != 0;
r.max_z_clip_disable = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, ZCLIP_FAR_DISABLE) != 0;
r.user_clip_plane_negate_y = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, PS_UCP_Y_SCALE_NEG) != 0;
r.clip_disable = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, CLIP_DISABLE) != 0;
r.user_clip_plane_cull_only = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, UCP_CULL_ONLY_ENA) != 0;
r.cull_on_clipping_error_disable =
KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, DIS_CLIP_ERR_DETECT) != 0;
r.linear_attribute_clip_enable =
KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, DX_LINEAR_ATTR_CLIP_ENA) != 0;
r.force_viewport_index_from_vs_enable =
KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, VTE_VPORT_PROVOKE_DISABLE) != 0;
return r;
}
static HW::ColorControl DecodeColorControl(uint32_t value) {
HW::ColorControl r {};
r.mode = KYTY_PM4_GET(value, CB_COLOR_CONTROL, MODE);
r.op = KYTY_PM4_GET(value, CB_COLOR_CONTROL, ROP3);
return r;
}
static HW::DepthControl DecodeDepthControl(uint32_t value) {
HW::DepthControl r {};
r.stencil_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, STENCIL_ENABLE) != 0;
r.z_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, Z_ENABLE) != 0;
r.z_write_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, Z_WRITE_ENABLE) != 0;
r.depth_bounds_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, DEPTH_BOUNDS_ENABLE) != 0;
r.zfunc = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, ZFUNC);
r.backface_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, BACKFACE_ENABLE) != 0;
r.stencilfunc = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, STENCILFUNC);
r.stencilfunc_bf = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, STENCILFUNC_BF);
r.color_writes_on_depth_fail_enable =
KYTY_PM4_GET(value, DB_DEPTH_CONTROL, ENABLE_COLOR_WRITES_ON_DEPTH_FAIL) != 0;
r.color_writes_on_depth_pass_disable =
KYTY_PM4_GET(value, DB_DEPTH_CONTROL, DISABLE_COLOR_WRITES_ON_DEPTH_PASS) != 0;
return r;
}
static HW::ModeControl DecodeModeControl(uint32_t value) {
HW::ModeControl r {};
r.cull_front = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, CULL_FRONT) != 0;
r.cull_back = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, CULL_BACK) != 0;
r.face = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, FACE) != 0;
r.poly_mode = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLY_MODE);
r.polymode_front_ptype = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLYMODE_FRONT_PTYPE);
r.polymode_back_ptype = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLYMODE_BACK_PTYPE);
r.poly_offset_front_enable =
KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLY_OFFSET_FRONT_ENABLE) != 0;
r.poly_offset_back_enable =
KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLY_OFFSET_BACK_ENABLE) != 0;
r.vtx_window_offset_enable =
KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, VTX_WINDOW_OFFSET_ENABLE) != 0;
r.provoking_vtx_last = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, PROVOKING_VTX_LAST) != 0;
r.persp_corr_dis = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, PERSP_CORR_DIS) != 0;
return r;
}
static HW::RenderControl DecodeRenderControl(uint32_t value) {
HW::RenderControl r {};
r.depth_clear_enable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, DEPTH_CLEAR_ENABLE) != 0;
r.stencil_clear_enable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, STENCIL_CLEAR_ENABLE) != 0;
r.resummarize_enable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, RESUMMARIZE_ENABLE) != 0;
r.stencil_compress_disable =
KYTY_PM4_GET(value, DB_RENDER_CONTROL, STENCIL_COMPRESS_DISABLE) != 0;
r.depth_compress_disable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, DEPTH_COMPRESS_DISABLE) != 0;
r.copy_centroid = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_CENTROID) != 0;
r.copy_sample = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_SAMPLE);
return r;
}
static HW::ScanModeControl DecodeScanModeControl(uint32_t value) {
HW::ScanModeControl r {};
r.msaa_enable = KYTY_PM4_GET(value, PA_SC_MODE_CNTL_0, MSAA_ENABLE) != 0;
r.vport_scissor_enable = KYTY_PM4_GET(value, PA_SC_MODE_CNTL_0, VPORT_SCISSOR_ENABLE) != 0;
r.line_stipple_enable = KYTY_PM4_GET(value, PA_SC_MODE_CNTL_0, LINE_STIPPLE_ENABLE) != 0;
return r;
}
static HW::StencilControl DecodeStencilControl(uint32_t value) {
HW::StencilControl r {};
r.stencil_fail = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILFAIL);
r.stencil_zpass = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZPASS);
r.stencil_zfail = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZFAIL);
r.stencil_fail_bf = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILFAIL_BF);
r.stencil_zpass_bf = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZPASS_BF);
r.stencil_zfail_bf = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZFAIL_BF);
return r;
}
static HW::AaConfig ParseAaConfig(uint32_t value) {
HW::AaConfig r;
@@ -183,6 +292,10 @@ static void HwCtxIgnoreAaMaskRegister([[maybe_unused]] uint32_t cmd_offset,
static void HwCtxIgnoreAlphaToMaskRegister([[maybe_unused]] uint32_t value) {}
static void HwCtxIgnoreDisabledUserClipPlane(CommandProcessor& cp, uint32_t value) {
EXIT_NOT_IMPLEMENTED(value != 0 || cp.GetCtx().GetClipControl().user_clip_planes != 0);
}
static void HwCtxIgnoreDrawPayloadControl([[maybe_unused]] uint32_t value) {}
static void HwCtxIgnoreObjprimIdControl([[maybe_unused]] uint32_t value) {}
@@ -404,47 +517,12 @@ KYTY_HW_CTX_PARSER(HwCtxSetAaMask) {
return num_values;
}
KYTY_HW_CTX_PARSER(HwCtxSetBlendColor) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0046900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::CB_BLEND_RED);
HW::BlendColor r;
r.red = *reinterpret_cast<const float*>(&buffer[0]);
r.green = *reinterpret_cast<const float*>(&buffer[1]);
r.blue = *reinterpret_cast<const float*>(&buffer[2]);
r.alpha = *reinterpret_cast<const float*>(&buffer[3]);
cp.GetCtx().SetBlendColor(r);
return 4;
}
KYTY_HW_CTX_PARSER(HwCtxSetBlendControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
uint32_t param = (cmd_offset - Pm4::CB_BLEND0_CONTROL) / 1;
HW::BlendControl r;
r.color_srcblend = (buffer[0] >> Pm4::CB_BLEND0_CONTROL_COLOR_SRCBLEND_SHIFT) &
Pm4::CB_BLEND0_CONTROL_COLOR_SRCBLEND_MASK;
r.color_comb_fcn = (buffer[0] >> Pm4::CB_BLEND0_CONTROL_COLOR_COMB_FCN_SHIFT) &
Pm4::CB_BLEND0_CONTROL_COLOR_COMB_FCN_MASK;
r.color_destblend = (buffer[0] >> Pm4::CB_BLEND0_CONTROL_COLOR_DESTBLEND_SHIFT) &
Pm4::CB_BLEND0_CONTROL_COLOR_DESTBLEND_MASK;
r.alpha_srcblend = (buffer[0] >> Pm4::CB_BLEND0_CONTROL_ALPHA_SRCBLEND_SHIFT) &
Pm4::CB_BLEND0_CONTROL_ALPHA_SRCBLEND_MASK;
r.alpha_comb_fcn = (buffer[0] >> Pm4::CB_BLEND0_CONTROL_ALPHA_COMB_FCN_SHIFT) &
Pm4::CB_BLEND0_CONTROL_ALPHA_COMB_FCN_MASK;
r.alpha_destblend = (buffer[0] >> Pm4::CB_BLEND0_CONTROL_ALPHA_DESTBLEND_SHIFT) &
Pm4::CB_BLEND0_CONTROL_ALPHA_DESTBLEND_MASK;
r.separate_alpha_blend = ((buffer[0] >> Pm4::CB_BLEND0_CONTROL_SEPARATE_ALPHA_BLEND_SHIFT) &
Pm4::CB_BLEND0_CONTROL_SEPARATE_ALPHA_BLEND_MASK) != 0;
r.enable = ((buffer[0] >> Pm4::CB_BLEND0_CONTROL_ENABLE_SHIFT) &
Pm4::CB_BLEND0_CONTROL_ENABLE_MASK) != 0;
cp.GetCtx().SetBlendControl(param, r);
cp.GetCtx().SetBlendControl(param, DecodeBlendControl(buffer[0]));
return 1;
}
@@ -453,38 +531,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetClipControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::PA_CL_CLIP_CNTL);
HW::ClipControl r;
// r.user_clip_planes = buffer[0] & 0x3fu;
// r.user_clip_plane_mode = (buffer[0] >> 14u) & 0x3u;
// r.clip_space = (buffer[0] >> 19u) & 0x1u;
// r.vertex_kill_mode = (buffer[0] >> 21u) & 0x1u;
// r.min_z_clip_enable = (buffer[0] >> 26u) & 0x1u;
// r.max_z_clip_enable = (buffer[0] >> 27u) & 0x1u;
// r.user_clip_plane_negate_y = (buffer[0] & 0x00002000u) != 0;
// r.clip_enable = (buffer[0] & 0x00010000u) != 0;
// r.user_clip_plane_cull_only = (buffer[0] & 0x00020000u) != 0;
// r.cull_on_clipping_error_disable = (buffer[0] & 0x00100000u) != 0;
// r.linear_attribute_clip_enable = (buffer[0] & 0x01000000u) != 0;
// r.force_viewport_index_from_vs_enable = (buffer[0] & 0x02000000u) != 0;
r.user_clip_planes = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, UCP_ENA);
r.user_clip_plane_mode = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, PS_UCP_MODE);
r.dx_clip_space = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, DX_CLIP_SPACE_DEF) != 0;
r.vertex_kill_any = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, VTX_KILL_OR) != 0;
r.min_z_clip_disable = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, ZCLIP_NEAR_DISABLE) != 0;
r.max_z_clip_disable = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, ZCLIP_FAR_DISABLE) != 0;
r.user_clip_plane_negate_y = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, PS_UCP_Y_SCALE_NEG) != 0;
r.clip_disable = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, CLIP_DISABLE) != 0;
r.user_clip_plane_cull_only = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, UCP_CULL_ONLY_ENA) != 0;
r.cull_on_clipping_error_disable =
KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, DIS_CLIP_ERR_DETECT) != 0;
r.linear_attribute_clip_enable =
KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, DX_LINEAR_ATTR_CLIP_ENA) != 0;
r.force_viewport_index_from_vs_enable =
KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, VTE_VPORT_PROVOKE_DISABLE) != 0;
cp.GetCtx().SetClipControl(r);
cp.GetCtx().SetClipControl(DecodeClipControl(buffer[0]));
return 1;
}
@@ -493,12 +540,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetColorControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::CB_COLOR_CONTROL);
HW::ColorControl r;
r.mode = KYTY_PM4_GET(buffer[0], CB_COLOR_CONTROL, MODE);
r.op = KYTY_PM4_GET(buffer[0], CB_COLOR_CONTROL, ROP3);
cp.GetCtx().SetColorControl(r);
cp.GetCtx().SetColorControl(DecodeColorControl(buffer[0]));
return 1;
}
@@ -522,9 +564,12 @@ KYTY_HW_CTX_PARSER(HwCtxSetColorInfo) {
// r.format = (buffer[4] >> 2u) & 0x1fu;
// r.channel_type = (buffer[4] >> 8u) & 0x7u;
// r.channel_order = (buffer[4] >> 11u) & 0x3u;
r.format = KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, FORMAT);
r.channel_type = KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, NUMBER_TYPE);
r.channel_order = KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, COMP_SWAP);
r.format =
static_cast<Prospero::ChannelLayout>(KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, FORMAT));
r.channel_type =
static_cast<Prospero::ChannelType>(KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, NUMBER_TYPE));
r.channel_order =
static_cast<Prospero::ChannelOrder>(KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, COMP_SWAP));
r.cmask_fast_clear_enable = KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, FAST_CLEAR) != 0;
r.fmask_compression_enable = KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, COMPRESSION) != 0;
r.blend_clamp = KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, BLEND_CLAMP) != 0;
@@ -556,22 +601,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetDepthControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::DB_DEPTH_CONTROL);
HW::DepthControl r;
r.stencil_enable = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, STENCIL_ENABLE) != 0;
r.z_enable = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, Z_ENABLE) != 0;
r.z_write_enable = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, Z_WRITE_ENABLE) != 0;
r.depth_bounds_enable = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, DEPTH_BOUNDS_ENABLE) != 0;
r.zfunc = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, ZFUNC);
r.backface_enable = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, BACKFACE_ENABLE) != 0;
r.stencilfunc = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, STENCILFUNC);
r.stencilfunc_bf = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, STENCILFUNC_BF);
r.color_writes_on_depth_fail_enable =
KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, ENABLE_COLOR_WRITES_ON_DEPTH_FAIL) != 0;
r.color_writes_on_depth_pass_disable =
KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, DISABLE_COLOR_WRITES_ON_DEPTH_PASS) != 0;
cp.GetCtx().SetDepthControl(r);
cp.GetCtx().SetDepthControl(DecodeDepthControl(buffer[0]));
return 1;
}
@@ -800,20 +830,6 @@ KYTY_HW_CTX_PARSER(HwCtxSetClipRect) {
return value_count;
}
KYTY_HW_CTX_PARSER(HwCtxSetGuardBands) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0046900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::PA_CL_GB_VERT_CLIP_ADJ);
auto vert_clip = *reinterpret_cast<const float*>(&buffer[0]); // PA_CL_GB_VERT_CLIP_ADJ
auto vert_discard = *reinterpret_cast<const float*>(&buffer[1]); // PA_CL_GB_VERT_DISC_ADJ
auto horz_clip = *reinterpret_cast<const float*>(&buffer[2]); // PA_CL_GB_HORZ_CLIP_ADJ
auto horz_discard = *reinterpret_cast<const float*>(&buffer[3]); // PA_CL_GB_HORZ_DISC_ADJ
cp.GetCtx().SetGuardBands(horz_clip, vert_clip, horz_discard, vert_discard);
return 4;
}
KYTY_HW_CTX_PARSER(HwCtxSetHardwareScreenOffset) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::PA_SU_HARDWARE_SCREEN_OFFSET);
@@ -875,24 +891,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetModeControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::PA_SU_SC_MODE_CNTL);
HW::ModeControl r;
r.cull_front = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, CULL_FRONT) != 0;
r.cull_back = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, CULL_BACK) != 0;
r.face = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, FACE) != 0;
r.poly_mode = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, POLY_MODE);
r.polymode_front_ptype = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, POLYMODE_FRONT_PTYPE);
r.polymode_back_ptype = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, POLYMODE_BACK_PTYPE);
r.poly_offset_front_enable =
KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, POLY_OFFSET_FRONT_ENABLE) != 0;
r.poly_offset_back_enable =
KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, POLY_OFFSET_BACK_ENABLE) != 0;
r.vtx_window_offset_enable =
KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, VTX_WINDOW_OFFSET_ENABLE) != 0;
r.provoking_vtx_last = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, PROVOKING_VTX_LAST) != 0;
r.persp_corr_dis = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, PERSP_CORR_DIS) != 0;
cp.GetCtx().SetModeControl(r);
cp.GetCtx().SetModeControl(DecodeModeControl(buffer[0]));
return 1;
}
@@ -958,19 +957,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetRenderControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::DB_RENDER_CONTROL);
HW::RenderControl r;
r.depth_clear_enable = KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, DEPTH_CLEAR_ENABLE) != 0;
r.stencil_clear_enable = KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, STENCIL_CLEAR_ENABLE) != 0;
r.resummarize_enable = KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, RESUMMARIZE_ENABLE) != 0;
r.stencil_compress_disable =
KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, STENCIL_COMPRESS_DISABLE) != 0;
r.depth_compress_disable =
KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, DEPTH_COMPRESS_DISABLE) != 0;
r.copy_centroid = KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, COPY_CENTROID) != 0;
r.copy_sample = KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, COPY_SAMPLE);
cp.GetCtx().SetRenderControl(r);
cp.GetCtx().SetRenderControl(DecodeRenderControl(buffer[0]));
return 1;
}
@@ -1038,9 +1025,12 @@ KYTY_HW_CTX_PARSER(HwCtxSetRenderTarget) {
// info.format = (buffer[4] >> 2u) & 0x1fu;
// info.channel_type = (buffer[4] >> 8u) & 0x7u;
// info.channel_order = (buffer[4] >> 11u) & 0x3u;
info.format = KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, FORMAT);
info.channel_type = KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, NUMBER_TYPE);
info.channel_order = KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, COMP_SWAP);
info.format =
static_cast<Prospero::ChannelLayout>(KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, FORMAT));
info.channel_type =
static_cast<Prospero::ChannelType>(KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, NUMBER_TYPE));
info.channel_order =
static_cast<Prospero::ChannelOrder>(KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, COMP_SWAP));
info.cmask_fast_clear_enable = KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, FAST_CLEAR) != 0;
info.fmask_compression_enable = KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, COMPRESSION) != 0;
info.blend_clamp = KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, BLEND_CLAMP) != 0;
@@ -1062,10 +1052,12 @@ KYTY_HW_CTX_PARSER(HwCtxSetRenderTarget) {
// attrib.num_fragments = (buffer[5] >> 15u) & 0x3u;
attrib.force_dest_alpha_to_one =
KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, FORCE_DST_ALPHA_1) != 0;
attrib.tile_mode = KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, TILE_MODE_INDEX);
attrib.fmask_tile_mode = KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, FMASK_TILE_MODE_INDEX);
attrib.num_samples = KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, NUM_SAMPLES);
attrib.num_fragments = KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, NUM_FRAGMENTS);
attrib.tile_mode =
static_cast<Prospero::TileMode>(KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, TILE_MODE_INDEX));
attrib.fmask_tile_mode = static_cast<Prospero::TileMode>(
KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, FMASK_TILE_MODE_INDEX));
attrib.num_samples = KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, NUM_SAMPLES);
attrib.num_fragments = KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, NUM_FRAGMENTS);
// dcc.max_uncompressed_block_size = (buffer[6] >> 2u) & 0x3u;
// dcc.max_compressed_block_size = (buffer[6] >> 5u) & 0x3u;
@@ -1158,13 +1150,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetScanModeControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::PA_SC_MODE_CNTL_0);
HW::ScanModeControl r;
r.msaa_enable = KYTY_PM4_GET(buffer[0], PA_SC_MODE_CNTL_0, MSAA_ENABLE) != 0;
r.vport_scissor_enable = KYTY_PM4_GET(buffer[0], PA_SC_MODE_CNTL_0, VPORT_SCISSOR_ENABLE) != 0;
r.line_stipple_enable = KYTY_PM4_GET(buffer[0], PA_SC_MODE_CNTL_0, LINE_STIPPLE_ENABLE) != 0;
cp.GetCtx().SetScanModeControl(r);
cp.GetCtx().SetScanModeControl(DecodeScanModeControl(buffer[0]));
return 1;
}
@@ -1204,16 +1190,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetStencilControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::DB_STENCIL_CONTROL);
HW::StencilControl r;
r.stencil_fail = KYTY_PM4_GET(buffer[0], DB_STENCIL_CONTROL, STENCILFAIL);
r.stencil_zpass = KYTY_PM4_GET(buffer[0], DB_STENCIL_CONTROL, STENCILZPASS);
r.stencil_zfail = KYTY_PM4_GET(buffer[0], DB_STENCIL_CONTROL, STENCILZFAIL);
r.stencil_fail_bf = KYTY_PM4_GET(buffer[0], DB_STENCIL_CONTROL, STENCILFAIL_BF);
r.stencil_zpass_bf = KYTY_PM4_GET(buffer[0], DB_STENCIL_CONTROL, STENCILZPASS_BF);
r.stencil_zfail_bf = KYTY_PM4_GET(buffer[0], DB_STENCIL_CONTROL, STENCILZFAIL_BF);
cp.GetCtx().SetStencilControl(r);
cp.GetCtx().SetStencilControl(DecodeStencilControl(buffer[0]));
return 1;
}
@@ -1228,23 +1205,16 @@ KYTY_HW_CTX_PARSER(HwCtxSetStencilInfo) {
}
KYTY_HW_CTX_PARSER(HwCtxSetStencilMask) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0026900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::DB_STENCILREFMASK);
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
EXIT_NOT_IMPLEMENTED(num_values == 0);
EXIT_NOT_IMPLEMENTED(cmd_offset < Pm4::DB_STENCILREFMASK);
EXIT_NOT_IMPLEMENTED(cmd_offset + num_values - 1u > Pm4::DB_STENCILREFMASK_BF);
HW::StencilMask r;
for (uint32_t i = 0; i < num_values; i++) {
g_hw_ctx_indirect_func[(cmd_offset + i) & (Pm4::CX_NUM - 1)](cp, cmd_offset + i, buffer[i]);
}
r.stencil_testval = KYTY_PM4_GET(buffer[0], DB_STENCILREFMASK, STENCILTESTVAL);
r.stencil_mask = KYTY_PM4_GET(buffer[0], DB_STENCILREFMASK, STENCILMASK);
r.stencil_writemask = KYTY_PM4_GET(buffer[0], DB_STENCILREFMASK, STENCILWRITEMASK);
r.stencil_opval = KYTY_PM4_GET(buffer[0], DB_STENCILREFMASK, STENCILOPVAL);
r.stencil_testval_bf = KYTY_PM4_GET(buffer[1], DB_STENCILREFMASK_BF, STENCILTESTVAL_BF);
r.stencil_mask_bf = KYTY_PM4_GET(buffer[1], DB_STENCILREFMASK_BF, STENCILMASK_BF);
r.stencil_writemask_bf = KYTY_PM4_GET(buffer[1], DB_STENCILREFMASK_BF, STENCILWRITEMASK_BF);
r.stencil_opval_bf = KYTY_PM4_GET(buffer[1], DB_STENCILREFMASK_BF, STENCILOPVAL_BF);
cp.GetCtx().SetStencilMask(r);
return 2;
return num_values;
}
KYTY_HW_CTX_PARSER(HwCtxSetViewportScaleOffset) {
@@ -1347,10 +1317,6 @@ static void HwShSetCsRegister(CommandProcessor& cp, uint32_t cmd_offset, uint32_
Pm4::COMPUTE_PGM_RSRC1_BULKY_MASK) != 0u;
cs_regs.threadgroup_configuration = ((value >> Pm4::COMPUTE_PGM_RSRC1_WGP_MODE_SHIFT) &
Pm4::COMPUTE_PGM_RSRC1_WGP_MODE_MASK) != 0u;
cs_regs.wave_size = (((value >> Pm4::COMPUTE_PGM_RSRC1_W32_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC1_W32_EN_MASK) != 0u)
? 32u
: 64u;
break;
case Pm4::COMPUTE_PGM_RSRC2:
cs_regs.scratch_en = ((value >> Pm4::COMPUTE_PGM_RSRC2_SCRATCH_EN_SHIFT) &
@@ -1564,7 +1530,7 @@ KYTY_HW_UC_PARSER(HwUcSetPrimitiveType) {
uint32_t prim_type = KYTY_PM4_GET(buffer[0], VGT_PRIMITIVE_TYPE, PRIM_TYPE);
cp.GetUcfg().SetPrimitiveType(prim_type);
cp.GetUcfg().SetPrimitiveType(static_cast<Prospero::PrimitiveType>(prim_type));
return 1;
}
@@ -1607,16 +1573,10 @@ KYTY_HW_UC_PARSER(HwUcSetIaMultiVgtParam) {
return num_values;
}
static void HwIgnoreMultiPrimIbReset([[maybe_unused]] uint32_t value) {}
KYTY_HW_UC_PARSER(HwUcSetMultiPrimIbReset) {
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
for (uint32_t i = 0; i < num_values; i++) {
HwIgnoreMultiPrimIbReset(buffer[i]);
}
return num_values;
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::GE_MULTI_PRIM_IB_RESET_EN);
cp.GetUcfg().SetPrimitiveResetControl(buffer[0]);
return 1;
}
static void HwUcIgnoreBorderColorTableAddr([[maybe_unused]] uint32_t cmd_offset,
@@ -1816,6 +1776,17 @@ KYTY_CP_OP_PARSER(CpOpPfpSyncMe) {
return 1;
}
KYTY_CP_OP_PARSER(CpOpRewind) {
KYTY_PROFILER_FUNCTION();
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0005900);
EXIT_NOT_IMPLEMENTED((buffer[0] & ~0x81000000u) != 0);
cp.WaitForRewind((buffer[0] & 0x80000000u) != 0);
return 1;
}
KYTY_CP_OP_PARSER(CpOpSetPredication) {
KYTY_PROFILER_FUNCTION();
@@ -1888,11 +1859,11 @@ KYTY_CP_OP_PARSER(CpOpBranch) {
uint64_t reference = buffer[5] | (static_cast<uint64_t>(buffer[6]) << 32u);
uint32_t mode = buffer[0] & 0x3u;
uint32_t function = (buffer[0] >> 8u) & 0x7u;
auto* then_buffer = reinterpret_cast<uint32_t*>((buffer[7] & 0xfffffffcu) |
(static_cast<uint64_t>(buffer[8]) << 32u));
auto* then_buffer = reinterpret_cast<const uint32_t*>(
(buffer[7] & 0xfffffffcu) | (static_cast<uint64_t>(buffer[8]) << 32u));
uint32_t then_num_dw = buffer[9] & 0xfffffu;
auto* else_buffer = reinterpret_cast<uint32_t*>((buffer[10] & 0xfffffffcu) |
(static_cast<uint64_t>(buffer[11]) << 32u));
auto* else_buffer = reinterpret_cast<const uint32_t*>(
(buffer[10] & 0xfffffffcu) | (static_cast<uint64_t>(buffer[11]) << 32u));
uint32_t else_num_dw = buffer[12] & 0xfffffu;
EXIT_NOT_IMPLEMENTED(compare_addr == nullptr);
@@ -1906,10 +1877,10 @@ KYTY_CP_OP_PARSER(CpOpBranch) {
reinterpret_cast<uint64_t>(else_buffer), else_num_dw);
if (take_then) {
cp.ProcessIndirectBuffer(then_buffer, then_num_dw);
cp.ProcessIndirectBuffer({then_buffer, then_num_dw});
} else if (mode == 2 && else_num_dw != 0) {
EXIT_NOT_IMPLEMENTED(else_buffer == nullptr);
cp.ProcessIndirectBuffer(else_buffer, else_num_dw);
cp.ProcessIndirectBuffer({else_buffer, else_num_dw});
}
return payload_dw;
@@ -2176,12 +2147,19 @@ KYTY_CP_OP_PARSER(CpOpEventWrite) {
EXIT_NOT_IMPLEMENTED(((cmd_id >> 8u) & 0xffu) != Pm4::IT_EVENT_WRITE);
uint32_t event_index = (buffer[0] >> 8u) & 0x7u;
uint32_t event_type = (buffer[0]) & 0x3fu;
const auto packet_size_dw = KYTY_PM4_LEN(cmd_id);
uint32_t event_index = (buffer[0] >> 8u) & 0x7u;
uint32_t event_type = (buffer[0]) & 0x3fu;
uint64_t event_address = 0;
cp.TriggerEvent(event_type, event_index);
if (event_type == 0x39u) {
EXIT_NOT_IMPLEMENTED(packet_size_dw != 4u);
event_address = buffer[1] | (static_cast<uint64_t>(buffer[2]) << 32u);
}
return KYTY_PM4_LEN(cmd_id) - 1u;
cp.TriggerEvent(event_type, event_index, event_address);
return packet_size_dw - 1u;
}
KYTY_CP_OP_PARSER(CpOpEventWriteEop) {
@@ -2343,7 +2321,7 @@ KYTY_CP_OP_PARSER(CpOpIndirectBuffer) {
}
auto* indirect_buffer =
reinterpret_cast<uint32_t*>(buffer[0] | (static_cast<uint64_t>(buffer[1]) << 32u));
reinterpret_cast<const uint32_t*>(buffer[0] | (static_cast<uint64_t>(buffer[1]) << 32u));
uint32_t indirect_num_dw = control & 0xfffffu;
static std::atomic<uint32_t> indirect_log_count {0};
if (indirect_log_count.fetch_add(1) < 128) {
@@ -2363,7 +2341,7 @@ KYTY_CP_OP_PARSER(CpOpIndirectBuffer) {
GraphicsDbgDumpDcb("ci", indirect_num_dw, indirect_buffer);
cp.ProcessIndirectBuffer(indirect_buffer, indirect_num_dw);
cp.ProcessIndirectBuffer({indirect_buffer, indirect_num_dw});
return 3;
}
@@ -2386,10 +2364,10 @@ KYTY_CP_OP_PARSER(CpOpIndirectCxRegs) {
EXIT("indirect CX registers have null address, num_regs = %" PRIu32 "\n", indirect_num_dw);
}
for (uint32_t i = 0; i < indirect_num_dw; i++, indirect_buffer += 2) {
// Keep the encoded offset for packet control values, and use the decoded offset only
// for register dispatch.
// Keep the encoded offset for packet control values, and use the normalized offset
// only for register dispatch.
auto raw_cmd_offset = indirect_buffer[0];
auto cmd_offset = DecodeIndirectCxRegisterOffset(raw_cmd_offset);
auto cmd_offset = NormalizeRegisterOffset(raw_cmd_offset);
auto value = indirect_buffer[1];
if (HwCtxTrySetFakeRegister(cmd_offset, value)) {
@@ -2839,6 +2817,20 @@ KYTY_CP_OP_PARSER(CpOpSetShaderReg) {
auto pfunc = g_hw_sh_func[cmd_offset];
if (pfunc == nullptr) {
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
bool handled = num_values != 0;
for (uint32_t i = 0; i < num_values; i++) {
if (cmd_offset + i >= Pm4::SH_NUM || g_hw_sh_indirect_func[cmd_offset + i] == nullptr) {
handled = false;
break;
}
}
if (handled) {
for (uint32_t i = 0; i < num_values; i++) {
g_hw_sh_indirect_func[cmd_offset + i](cp, cmd_offset + i, buffer[1 + i]);
}
return num_values + 1u;
}
EXIT("unknown shader register\n\t%05" PRIx32 ":\n\tcmd_id = %08" PRIx32
"\n\tcmd_offset = %08" PRIx32 "\n",
num_dw - dw, cmd_id, cmd_offset);
@@ -2852,6 +2844,10 @@ KYTY_CP_OP_PARSER(CpOpSetShaderReg) {
KYTY_CP_OP_PARSER(CpOpSetUconfigReg) {
KYTY_PROFILER_FUNCTION();
if (Gen5::AgcIsInternalDataPacket(cmd_id, buffer)) {
return KYTY_PM4_LEN(cmd_id) - 1u;
}
auto raw_cmd_offset = buffer[0];
auto cmd_offset = NormalizeRegisterOffset(raw_cmd_offset);
@@ -2942,19 +2938,21 @@ template <typename T>
static uint32_t CpOpWaitRegMemSized(CommandProcessor& cp, uint32_t cmd_id, const uint32_t* buffer) {
static_assert(sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t));
constexpr auto value_dw = static_cast<uint32_t>(sizeof(T) / sizeof(uint32_t));
constexpr auto value_dw = static_cast<uint32_t>(sizeof(T) / sizeof(uint32_t));
constexpr auto payload_dw = 4u + value_dw * 2u;
constexpr auto packet_dw = payload_dw + 1u;
constexpr auto register_id =
(sizeof(T) == sizeof(uint32_t) ? Pm4::R_WAIT_MEM_32 : Pm4::R_WAIT_MEM_64);
constexpr auto opcode =
(sizeof(T) == sizeof(uint32_t) ? Pm4::IT_WAIT_REG_MEM : Pm4::IT_WAIT_REG_MEM_64);
EXIT_NOT_IMPLEMENTED(KYTY_PM4_R(cmd_id) != register_id);
EXIT_NOT_IMPLEMENTED(((cmd_id >> 8u) & 0xffu) != opcode || KYTY_PM4_R(cmd_id) != 0u);
EXIT_NOT_IMPLEMENTED(KYTY_PM4_LEN(cmd_id) != packet_dw);
auto* addr = reinterpret_cast<const T*>(buffer[0] | (static_cast<uint64_t>(buffer[1]) << 32u));
auto mask = CpOpWaitRegMemReadValue<T>(buffer + 2u);
auto ref = CpOpWaitRegMemReadValue<T>(buffer + 2u + value_dw);
auto ctrl = buffer[2u + value_dw * 2u];
constexpr auto address_align_mask = (sizeof(T) == sizeof(uint32_t) ? 0x3u : 0x7u);
auto ctrl = buffer[0];
auto* addr = reinterpret_cast<const T*>((buffer[1] & ~address_align_mask) |
(static_cast<uint64_t>(buffer[2] & 0x3ffffu) << 32u));
auto ref = CpOpWaitRegMemReadValue<T>(buffer + 3u);
auto mask = CpOpWaitRegMemReadValue<T>(buffer + 3u + value_dw);
auto poll = buffer[3u + value_dw * 2u];
EXIT_NOT_IMPLEMENTED((ctrl & 0x10u) == 0);
@@ -3102,9 +3100,12 @@ void GraphicsInitJmpTablesCxIndirect() {
g_hw_ctx_indirect_func[cmd_offset] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
uint32_t slot = (cmd_offset - Pm4::CB_COLOR0_INFO) / 15;
HW::ColorInfo info;
info.format = KYTY_PM4_GET(value, CB_COLOR0_INFO, FORMAT);
info.channel_type = KYTY_PM4_GET(value, CB_COLOR0_INFO, NUMBER_TYPE);
info.channel_order = KYTY_PM4_GET(value, CB_COLOR0_INFO, COMP_SWAP);
info.format =
static_cast<Prospero::ChannelLayout>(KYTY_PM4_GET(value, CB_COLOR0_INFO, FORMAT));
info.channel_type = static_cast<Prospero::ChannelType>(
KYTY_PM4_GET(value, CB_COLOR0_INFO, NUMBER_TYPE));
info.channel_order =
static_cast<Prospero::ChannelOrder>(KYTY_PM4_GET(value, CB_COLOR0_INFO, COMP_SWAP));
info.cmask_fast_clear_enable = KYTY_PM4_GET(value, CB_COLOR0_INFO, FAST_CLEAR) != 0;
info.fmask_compression_enable = KYTY_PM4_GET(value, CB_COLOR0_INFO, COMPRESSION) != 0;
info.blend_clamp = KYTY_PM4_GET(value, CB_COLOR0_INFO, BLEND_CLAMP) != 0;
@@ -3129,10 +3130,12 @@ void GraphicsInitJmpTablesCxIndirect() {
HW::ColorAttrib attrib;
attrib.force_dest_alpha_to_one =
KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, FORCE_DST_ALPHA_1) != 0;
attrib.tile_mode = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, TILE_MODE_INDEX);
attrib.fmask_tile_mode = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, FMASK_TILE_MODE_INDEX);
attrib.num_samples = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, NUM_SAMPLES);
attrib.num_fragments = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, NUM_FRAGMENTS);
attrib.tile_mode = static_cast<Prospero::TileMode>(
KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, TILE_MODE_INDEX));
attrib.fmask_tile_mode = static_cast<Prospero::TileMode>(
KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, FMASK_TILE_MODE_INDEX));
attrib.num_samples = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, NUM_SAMPLES);
attrib.num_fragments = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, NUM_FRAGMENTS);
cp.GetCtx().SetColorAttrib(slot, attrib);
};
}
@@ -3286,8 +3289,9 @@ void GraphicsInitJmpTablesCxIndirect() {
g_hw_ctx_indirect_func[cmd_offset] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
uint32_t slot = (cmd_offset - Pm4::CB_COLOR0_ATTRIB3);
HW::ColorAttrib3 attrib3;
attrib3.depth = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, MIP0_DEPTH);
attrib3.tile_mode = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, COLOR_SW_MODE);
attrib3.depth = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, MIP0_DEPTH);
attrib3.tile_mode = static_cast<Prospero::TileMode>(
KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, COLOR_SW_MODE));
attrib3.dimension = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, RESOURCE_TYPE);
attrib3.cmask_pipe_aligned = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, CMASK_PIPE_ALIGNED);
attrib3.dcc_pipe_aligned = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, DCC_PIPE_ALIGNED);
@@ -3343,6 +3347,12 @@ void GraphicsInitJmpTablesCxIndirect() {
};
}
for (auto cmd_offset = Pm4::PA_CL_UCP_0_X; cmd_offset <= Pm4::PA_CL_UCP_5_W; cmd_offset++) {
g_hw_ctx_indirect_func[cmd_offset] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDisabledUserClipPlane(cp, value);
};
}
for (auto cmd_offset = Pm4::PA_SC_VPORT_SCISSOR_0_TL;
cmd_offset <= Pm4::PA_SC_VPORT_SCISSOR_15_TL; cmd_offset += 2) {
g_hw_ctx_indirect_func[cmd_offset] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
@@ -3514,7 +3524,7 @@ void GraphicsInitJmpTablesCxIndirect() {
cp.GetCtx().SetReuseOff(value);
};
g_hw_ctx_indirect_func[Pm4::VGT_MULTI_PRIM_IB_RESET_INDX] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwIgnoreMultiPrimIbReset(value);
cp.GetCtx().SetPrimitiveResetIndex(value);
};
g_hw_ctx_indirect_func[Pm4::VGT_TESS_DISTRIBUTION] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
cp.GetCtx().SetTessDistribution(value);
@@ -3681,39 +3691,12 @@ void GraphicsInitJmpTablesCxIndirect() {
for (uint32_t slot = 0; slot < 8; slot++) {
g_hw_ctx_indirect_func[Pm4::CB_BLEND0_CONTROL + slot] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
uint32_t param = cmd_offset - Pm4::CB_BLEND0_CONTROL;
HW::BlendControl r;
r.color_srcblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, COLOR_SRCBLEND);
r.color_comb_fcn = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, COLOR_COMB_FCN);
r.color_destblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, COLOR_DESTBLEND);
r.alpha_srcblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ALPHA_SRCBLEND);
r.alpha_comb_fcn = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ALPHA_COMB_FCN);
r.alpha_destblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ALPHA_DESTBLEND);
r.separate_alpha_blend =
KYTY_PM4_GET(value, CB_BLEND0_CONTROL, SEPARATE_ALPHA_BLEND) != 0;
r.enable = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ENABLE) != 0;
cp.GetCtx().SetBlendControl(param, r);
cp.GetCtx().SetBlendControl(param, DecodeBlendControl(value));
};
}
g_hw_ctx_indirect_func[Pm4::PA_SU_SC_MODE_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::ModeControl r;
r.cull_front = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, CULL_FRONT) != 0;
r.cull_back = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, CULL_BACK) != 0;
r.face = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, FACE) != 0;
r.poly_mode = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLY_MODE);
r.polymode_front_ptype = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLYMODE_FRONT_PTYPE);
r.polymode_back_ptype = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLYMODE_BACK_PTYPE);
r.poly_offset_front_enable =
KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLY_OFFSET_FRONT_ENABLE) != 0;
r.poly_offset_back_enable =
KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLY_OFFSET_BACK_ENABLE) != 0;
r.vtx_window_offset_enable =
KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, VTX_WINDOW_OFFSET_ENABLE) != 0;
r.provoking_vtx_last = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, PROVOKING_VTX_LAST) != 0;
r.persp_corr_dis = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, PERSP_CORR_DIS) != 0;
cp.GetCtx().SetModeControl(r);
cp.GetCtx().SetModeControl(DecodeModeControl(value));
};
g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_DB_FMT_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
@@ -3881,28 +3864,11 @@ void GraphicsInitJmpTablesCxIndirect() {
};
g_hw_ctx_indirect_func[Pm4::DB_STENCIL_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::StencilControl r;
r.stencil_fail = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILFAIL);
r.stencil_zpass = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZPASS);
r.stencil_zfail = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZFAIL);
r.stencil_fail_bf = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILFAIL_BF);
r.stencil_zpass_bf = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZPASS_BF);
r.stencil_zfail_bf = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZFAIL_BF);
cp.GetCtx().SetStencilControl(r);
cp.GetCtx().SetStencilControl(DecodeStencilControl(value));
};
g_hw_ctx_indirect_func[Pm4::DB_RENDER_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::RenderControl r;
r.depth_clear_enable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, DEPTH_CLEAR_ENABLE) != 0;
r.stencil_clear_enable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, STENCIL_CLEAR_ENABLE) != 0;
r.resummarize_enable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, RESUMMARIZE_ENABLE) != 0;
r.stencil_compress_disable =
KYTY_PM4_GET(value, DB_RENDER_CONTROL, STENCIL_COMPRESS_DISABLE) != 0;
r.depth_compress_disable =
KYTY_PM4_GET(value, DB_RENDER_CONTROL, DEPTH_COMPRESS_DISABLE) != 0;
r.copy_centroid = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_CENTROID) != 0;
r.copy_sample = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_SAMPLE);
cp.GetCtx().SetRenderControl(r);
cp.GetCtx().SetRenderControl(DecodeRenderControl(value));
};
g_hw_ctx_indirect_func[Pm4::DB_STENCILREFMASK] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
@@ -3924,23 +3890,7 @@ void GraphicsInitJmpTablesCxIndirect() {
};
g_hw_ctx_indirect_func[Pm4::PA_CL_CLIP_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::ClipControl r;
r.user_clip_planes = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, UCP_ENA);
r.user_clip_plane_mode = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, PS_UCP_MODE);
r.dx_clip_space = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, DX_CLIP_SPACE_DEF) != 0;
r.vertex_kill_any = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, VTX_KILL_OR) != 0;
r.min_z_clip_disable = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, ZCLIP_NEAR_DISABLE) != 0;
r.max_z_clip_disable = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, ZCLIP_FAR_DISABLE) != 0;
r.user_clip_plane_negate_y = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, PS_UCP_Y_SCALE_NEG) != 0;
r.clip_disable = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, CLIP_DISABLE) != 0;
r.user_clip_plane_cull_only = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, UCP_CULL_ONLY_ENA) != 0;
r.cull_on_clipping_error_disable =
KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, DIS_CLIP_ERR_DETECT) != 0;
r.linear_attribute_clip_enable =
KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, DX_LINEAR_ATTR_CLIP_ENA) != 0;
r.force_viewport_index_from_vs_enable =
KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, VTE_VPORT_PROVOKE_DISABLE) != 0;
cp.GetCtx().SetClipControl(r);
cp.GetCtx().SetClipControl(DecodeClipControl(value));
};
g_hw_ctx_indirect_func[Pm4::PA_SU_LINE_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
@@ -3968,11 +3918,7 @@ void GraphicsInitJmpTablesCxIndirect() {
};
g_hw_ctx_indirect_func[Pm4::PA_SC_MODE_CNTL_0] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::ScanModeControl r;
r.msaa_enable = KYTY_PM4_GET(value, PA_SC_MODE_CNTL_0, MSAA_ENABLE) != 0;
r.vport_scissor_enable = KYTY_PM4_GET(value, PA_SC_MODE_CNTL_0, VPORT_SCISSOR_ENABLE) != 0;
r.line_stipple_enable = KYTY_PM4_GET(value, PA_SC_MODE_CNTL_0, LINE_STIPPLE_ENABLE) != 0;
cp.GetCtx().SetScanModeControl(r);
cp.GetCtx().SetScanModeControl(DecodeScanModeControl(value));
};
g_hw_ctx_indirect_func[Pm4::PA_SC_MODE_CNTL_1] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreScanModeControl1(value);
@@ -4031,27 +3977,11 @@ void GraphicsInitJmpTablesCxIndirect() {
HwCtxIgnoreAlphaToMaskRegister(value);
};
g_hw_ctx_indirect_func[Pm4::CB_COLOR_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::ColorControl r;
r.mode = KYTY_PM4_GET(value, CB_COLOR_CONTROL, MODE);
r.op = KYTY_PM4_GET(value, CB_COLOR_CONTROL, ROP3);
cp.GetCtx().SetColorControl(r);
cp.GetCtx().SetColorControl(DecodeColorControl(value));
};
g_hw_ctx_indirect_func[Pm4::DB_DEPTH_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::DepthControl r;
r.stencil_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, STENCIL_ENABLE) != 0;
r.z_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, Z_ENABLE) != 0;
r.z_write_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, Z_WRITE_ENABLE) != 0;
r.depth_bounds_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, DEPTH_BOUNDS_ENABLE) != 0;
r.zfunc = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, ZFUNC);
r.backface_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, BACKFACE_ENABLE) != 0;
r.stencilfunc = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, STENCILFUNC);
r.stencilfunc_bf = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, STENCILFUNC_BF);
r.color_writes_on_depth_fail_enable =
KYTY_PM4_GET(value, DB_DEPTH_CONTROL, ENABLE_COLOR_WRITES_ON_DEPTH_FAIL) != 0;
r.color_writes_on_depth_pass_disable =
KYTY_PM4_GET(value, DB_DEPTH_CONTROL, DISABLE_COLOR_WRITES_ON_DEPTH_PASS) != 0;
cp.GetCtx().SetDepthControl(r);
cp.GetCtx().SetDepthControl(DecodeDepthControl(value));
};
g_hw_ctx_indirect_func[Pm4::DB_EQAA] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
@@ -4441,6 +4371,11 @@ void GraphicsInitJmpTablesUcIndirect() {
r.vertex_group_size = KYTY_PM4_GET(value, GE_CNTL, VERT_GRP_SIZE);
cp.GetUcfg().SetGeControl(r);
};
g_hw_uc_indirect_func[Pm4::GE_PC_ALLOC] = [](KYTY_HW_UC_INDIRECT_ARGS) {
(void)cp;
(void)cmd_offset;
(void)value;
};
g_hw_uc_indirect_func[Pm4::GE_USER_VGPR_EN] = [](KYTY_HW_UC_INDIRECT_ARGS) {
HW::GeUserVgprEn r;
@@ -4452,7 +4387,7 @@ void GraphicsInitJmpTablesUcIndirect() {
g_hw_uc_indirect_func[Pm4::VGT_PRIMITIVE_TYPE] = [](KYTY_HW_UC_INDIRECT_ARGS) {
uint32_t prim_type = KYTY_PM4_GET(value, VGT_PRIMITIVE_TYPE, PRIM_TYPE);
cp.GetUcfg().SetPrimitiveType(prim_type);
cp.GetUcfg().SetPrimitiveType(static_cast<Prospero::PrimitiveType>(prim_type));
};
g_hw_uc_indirect_func[Pm4::VGT_INDEX_TYPE] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.SetIndexType(value & 0x3u);
@@ -4467,7 +4402,7 @@ void GraphicsInitJmpTablesUcIndirect() {
HwUcIgnoreIaMultiVgtParam(value);
};
g_hw_uc_indirect_func[Pm4::GE_MULTI_PRIM_IB_RESET_EN] = [](KYTY_HW_UC_INDIRECT_ARGS) {
HwIgnoreMultiPrimIbReset(value);
cp.GetUcfg().SetPrimitiveResetControl(value);
};
g_hw_uc_indirect_func[Pm4::TA_CS_BC_BASE_ADDR] = [](KYTY_HW_UC_INDIRECT_ARGS) {
HwUcIgnoreBorderColorTableAddr(cmd_offset, value);
+30 -15
View File
@@ -3,15 +3,8 @@
#include "common/common.h"
#include <type_traits>
namespace Libs::Graphics::Prospero {
template <typename T>
constexpr auto GpuEnumValue(T value) {
return static_cast<std::underlying_type_t<T>>(value);
}
enum class PrimitiveType : uint32_t {
kNone = 0,
kPointList = 1,
@@ -115,32 +108,52 @@ enum class ChannelOrder : uint32_t {
kAltReversed = 3,
};
// Maps a shader's logical RGBA export to the host color-attachment components. Two bits per
// component keep this cheap to carry in shader and pipeline keys.
// Selects the logical shader component written to each physical color-attachment component.
// Two bits per component keep this cheap to carry in shader and pipeline keys.
struct ColorComponentMapping {
uint8_t packed = 0xe4u; // RGBA: 0, 1, 2, 3
static constexpr uint8_t kIdentity = 0xe4u;
uint8_t packed = kIdentity;
[[nodiscard]] constexpr uint32_t Map(uint32_t component) const {
return component < 4u ? (packed >> (component * 2u)) & 0x3u : component;
}
[[nodiscard]] constexpr uint32_t ApplyMask(uint32_t mask) const {
[[nodiscard]] constexpr uint32_t ApplyMask(uint32_t logical_mask) const {
uint32_t mapped = 0;
for (uint32_t component = 0; component < 4u; component++) {
mapped |= ((mask >> component) & 1u) << Map(component);
for (uint32_t physical_component = 0; physical_component < 4u; physical_component++) {
mapped |= ((logical_mask >> Map(physical_component)) & 1u) << physical_component;
}
return mapped;
}
[[nodiscard]] constexpr bool IsIdentity() const { return packed == 0xe4u; }
// This mapping selects an intermediate component; next selects the final logical component.
[[nodiscard]] constexpr ColorComponentMapping Then(ColorComponentMapping next) const {
ColorComponentMapping result {0u};
for (uint32_t physical_component = 0; physical_component < 4u; physical_component++) {
result.packed |= static_cast<uint8_t>(next.Map(Map(physical_component))
<< (physical_component * 2u));
}
return result;
}
[[nodiscard]] constexpr bool IsIdentity() const { return packed == kIdentity; }
bool operator==(const ColorComponentMapping&) const = default;
};
static_assert(sizeof(ColorComponentMapping) == sizeof(uint8_t));
inline constexpr ColorComponentMapping ColorMappingAbgr {0x1bu}; // 3, 2, 1, 0
inline constexpr ColorComponentMapping ColorMappingRgba {ColorComponentMapping::kIdentity};
inline constexpr ColorComponentMapping ColorMappingGr {0xe1u}; // 1, 0, 2, 3
inline constexpr ColorComponentMapping ColorMappingRabg {0x6cu}; // 0, 3, 2, 1
inline constexpr ColorComponentMapping ColorMappingRgab {0xb4u}; // 0, 1, 3, 2
inline constexpr ColorComponentMapping ColorMappingBgra {0xc6u}; // 2, 1, 0, 3
inline constexpr ColorComponentMapping ColorMappingAbgr {0x1bu}; // 3, 2, 1, 0
inline constexpr ColorComponentMapping ColorMappingAgba {0x27u}; // 3, 1, 2, 0
inline constexpr ColorComponentMapping ColorMappingArbg {0x63u}; // 3, 0, 2, 1
inline constexpr ColorComponentMapping ColorMappingAgbr {0x87u}; // 3, 1, 0, 2
inline constexpr ColorComponentMapping ColorMappingArgb {0x93u}; // 3, 0, 1, 2
enum class DepthFormat : uint32_t {
kInvalid = 0,
@@ -388,9 +401,11 @@ enum class BufferFormat : uint32_t {
k8Srgb = 128,
k8_8Srgb = 129,
k8_8_8_8Srgb = 130,
k10_10_10_2Float = 131,
k9_9_9_5Float = 132,
k5_6_5UNorm = 133,
k5_5_5_1UNorm = 134,
k1_5_5_5UNorm = 135,
k4_4_4_4UNorm = 136,
kFmask8_S4_F4 = 161,
kBc1UNorm = 169,
+175 -83
View File
@@ -3,129 +3,221 @@
#include "graphics/guest_gpu/gpu_defs.h"
#include <array>
#include <bit>
namespace Libs::Graphics::Prospero {
namespace {
struct FormatInfo {
uint32_t format;
uint32_t bytes_per_element;
uint32_t block_compressed_bytes_per_block;
uint32_t render_target_bytes_per_element;
bool supported_sampled_texture_format;
bool unsigned_integer_texture_format;
BufferFormat format;
uint32_t bytes_per_element;
uint32_t block_compressed_bytes_per_block;
uint32_t render_target_bytes_per_element;
bool sampled_texture;
bool uint_texture;
};
constexpr FormatInfo kFormatInfo[] = {
{GpuEnumValue(BufferFormat::k8UNorm), 1, 0, 1, true, false},
{GpuEnumValue(BufferFormat::k8SNorm), 0, 0, 1, false, false},
{GpuEnumValue(BufferFormat::k8UInt), 1, 0, 1, true, true},
{GpuEnumValue(BufferFormat::k16UNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k16SNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k16UInt), 2, 0, 2, true, true},
{GpuEnumValue(BufferFormat::k16SInt), 2, 0, 2, false, false},
{GpuEnumValue(BufferFormat::k16Float), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k8_8UNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k8_8SNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k8_8UInt), 2, 0, 2, true, true},
{GpuEnumValue(BufferFormat::k8_8SInt), 2, 0, 2, false, false},
{GpuEnumValue(BufferFormat::k32UInt), 4, 0, 4, true, true},
{GpuEnumValue(BufferFormat::k32SInt), 4, 0, 4, false, false},
{GpuEnumValue(BufferFormat::k32Float), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k16_16UNorm), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k16_16SNorm), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k16_16UInt), 4, 0, 4, true, true},
{GpuEnumValue(BufferFormat::k16_16SInt), 4, 0, 4, false, false},
{GpuEnumValue(BufferFormat::k16_16Float), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k11_11_10Float), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k10_10_10_2UNorm), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k10_10_10_2UInt), 4, 0, 4, true, true},
{GpuEnumValue(BufferFormat::k8_8_8_8UNorm), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k8_8_8_8SNorm), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k8_8_8_8UInt), 4, 0, 4, true, true},
{GpuEnumValue(BufferFormat::k8_8_8_8SInt), 4, 0, 4, false, false},
{GpuEnumValue(BufferFormat::k32_32UInt), 8, 0, 8, true, true},
{GpuEnumValue(BufferFormat::k32_32SInt), 8, 0, 8, false, false},
{GpuEnumValue(BufferFormat::k32_32Float), 8, 0, 8, true, false},
{GpuEnumValue(BufferFormat::k16_16_16_16UNorm), 8, 0, 8, true, false},
{GpuEnumValue(BufferFormat::k16_16_16_16SNorm), 8, 0, 8, true, false},
{GpuEnumValue(BufferFormat::k16_16_16_16UInt), 8, 0, 8, true, true},
{GpuEnumValue(BufferFormat::k16_16_16_16SInt), 8, 0, 8, false, false},
{GpuEnumValue(BufferFormat::k16_16_16_16Float), 8, 0, 8, true, false},
{GpuEnumValue(BufferFormat::k32_32_32UInt), 12, 0, 12, true, true},
{GpuEnumValue(BufferFormat::k32_32_32SInt), 12, 0, 12, false, false},
{GpuEnumValue(BufferFormat::k32_32_32Float), 12, 0, 12, true, false},
{GpuEnumValue(BufferFormat::k32_32_32_32UInt), 16, 0, 16, true, true},
{GpuEnumValue(BufferFormat::k32_32_32_32SInt), 16, 0, 16, false, false},
{GpuEnumValue(BufferFormat::k32_32_32_32Float), 16, 0, 16, true, false},
{GpuEnumValue(BufferFormat::k8Srgb), 1, 0, 0, true, false},
{GpuEnumValue(BufferFormat::k8_8Srgb), 2, 0, 0, true, false},
{GpuEnumValue(BufferFormat::k8_8_8_8Srgb), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k9_9_9_5Float), 4, 0, 0, true, false},
{GpuEnumValue(BufferFormat::k5_6_5UNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k5_5_5_1UNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k4_4_4_4UNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::kFmask8_S4_F4), 1, 0, 1, true, false},
{GpuEnumValue(BufferFormat::kBc1UNorm), 0, 8, 0, true, false},
{GpuEnumValue(BufferFormat::kBc1Srgb), 0, 8, 0, true, false},
{GpuEnumValue(BufferFormat::kBc2UNorm), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc2Srgb), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc3UNorm), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc3Srgb), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc4UNorm), 0, 8, 0, true, false},
{GpuEnumValue(BufferFormat::kBc4SNorm), 0, 8, 0, true, false},
{GpuEnumValue(BufferFormat::kBc5UNorm), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc5SNorm), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc6UFloat), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc6SFloat), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc7UNorm), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc7Srgb), 0, 16, 0, true, false},
{BufferFormat::k8UNorm, 1, 0, 1, true, false},
{BufferFormat::k8SNorm, 0, 0, 1, false, false},
{BufferFormat::k8UInt, 1, 0, 1, true, true},
{BufferFormat::k16UNorm, 2, 0, 2, true, false},
{BufferFormat::k16SNorm, 2, 0, 2, true, false},
{BufferFormat::k16UInt, 2, 0, 2, true, true},
{BufferFormat::k16SInt, 2, 0, 2, false, false},
{BufferFormat::k16Float, 2, 0, 2, true, false},
{BufferFormat::k8_8UNorm, 2, 0, 2, true, false},
{BufferFormat::k8_8SNorm, 2, 0, 2, true, false},
{BufferFormat::k8_8UInt, 2, 0, 2, true, true},
{BufferFormat::k8_8SInt, 2, 0, 2, false, false},
{BufferFormat::k32UInt, 4, 0, 4, true, true},
{BufferFormat::k32SInt, 4, 0, 4, false, false},
{BufferFormat::k32Float, 4, 0, 4, true, false},
{BufferFormat::k16_16UNorm, 4, 0, 4, true, false},
{BufferFormat::k16_16SNorm, 4, 0, 4, true, false},
{BufferFormat::k16_16UInt, 4, 0, 4, true, true},
{BufferFormat::k16_16SInt, 4, 0, 4, false, false},
{BufferFormat::k16_16Float, 4, 0, 4, true, false},
{BufferFormat::k11_11_10UInt, 4, 0, 4, true, true},
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
{BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false},
{BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true},
{BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false},
{BufferFormat::k8_8_8_8SNorm, 4, 0, 4, true, false},
{BufferFormat::k8_8_8_8UInt, 4, 0, 4, true, true},
{BufferFormat::k8_8_8_8SInt, 4, 0, 4, false, false},
{BufferFormat::k32_32UInt, 8, 0, 8, true, true},
{BufferFormat::k32_32SInt, 8, 0, 8, false, false},
{BufferFormat::k32_32Float, 8, 0, 8, true, false},
{BufferFormat::k16_16_16_16UNorm, 8, 0, 8, true, false},
{BufferFormat::k16_16_16_16SNorm, 8, 0, 8, true, false},
{BufferFormat::k16_16_16_16UInt, 8, 0, 8, true, true},
{BufferFormat::k16_16_16_16SInt, 8, 0, 8, false, false},
{BufferFormat::k16_16_16_16Float, 8, 0, 8, true, false},
{BufferFormat::k32_32_32UInt, 12, 0, 12, true, true},
{BufferFormat::k32_32_32SInt, 12, 0, 12, false, false},
{BufferFormat::k32_32_32Float, 12, 0, 12, true, false},
{BufferFormat::k32_32_32_32UInt, 16, 0, 16, true, true},
{BufferFormat::k32_32_32_32SInt, 16, 0, 16, false, false},
{BufferFormat::k32_32_32_32Float, 16, 0, 16, true, false},
{BufferFormat::k8Srgb, 1, 0, 0, true, false},
{BufferFormat::k8_8Srgb, 2, 0, 0, true, false},
{BufferFormat::k8_8_8_8Srgb, 4, 0, 4, true, false},
{BufferFormat::k9_9_9_5Float, 4, 0, 0, true, false},
{BufferFormat::k5_6_5UNorm, 2, 0, 2, true, false},
{BufferFormat::k5_5_5_1UNorm, 2, 0, 2, true, false},
{BufferFormat::k1_5_5_5UNorm, 0, 0, 2, false, false},
{BufferFormat::k4_4_4_4UNorm, 2, 0, 2, true, false},
{BufferFormat::kFmask8_S4_F4, 1, 0, 1, true, false},
{BufferFormat::kBc1UNorm, 0, 8, 0, true, false},
{BufferFormat::kBc1Srgb, 0, 8, 0, true, false},
{BufferFormat::kBc2UNorm, 0, 16, 0, true, false},
{BufferFormat::kBc2Srgb, 0, 16, 0, true, false},
{BufferFormat::kBc3UNorm, 0, 16, 0, true, false},
{BufferFormat::kBc3Srgb, 0, 16, 0, true, false},
{BufferFormat::kBc4UNorm, 0, 8, 0, true, false},
{BufferFormat::kBc4SNorm, 0, 8, 0, true, false},
{BufferFormat::kBc5UNorm, 0, 16, 0, true, false},
{BufferFormat::kBc5SNorm, 0, 16, 0, true, false},
{BufferFormat::kBc6UFloat, 0, 16, 0, true, false},
{BufferFormat::kBc6SFloat, 0, 16, 0, true, false},
{BufferFormat::kBc7UNorm, 0, 16, 0, true, false},
{BufferFormat::kBc7Srgb, 0, 16, 0, true, false},
};
constexpr uint8_t RenderTargetTypeBit(ChannelType type) {
return static_cast<uint8_t>(1u << static_cast<uint32_t>(type));
}
constexpr auto kUNormType = RenderTargetTypeBit(ChannelType::kUNorm);
constexpr auto kIntegerTypes =
RenderTargetTypeBit(ChannelType::kUInt) | RenderTargetTypeBit(ChannelType::kSInt);
constexpr auto kNormalizedIntegerTypes =
kUNormType | RenderTargetTypeBit(ChannelType::kSNorm) | kIntegerTypes;
struct RenderTargetLayoutInfo {
ChannelLayout layout;
BufferFormat first_linear_format;
uint8_t linear_type_mask;
uint8_t components;
BufferFormat extra_format = BufferFormat::kInvalid;
ChannelType extra_type = ChannelType::kUNorm;
ChannelOrderSupport order_support = ChannelOrderSupport::kAll;
};
constexpr RenderTargetLayoutInfo kRenderTargetLayouts[] = {
{ChannelLayout::k8, BufferFormat::k8UNorm, kNormalizedIntegerTypes, 1, BufferFormat::k8Srgb,
ChannelType::kSrgb},
{ChannelLayout::k16, BufferFormat::k16UNorm, kNormalizedIntegerTypes, 1, BufferFormat::k16Float,
ChannelType::kFloat},
{ChannelLayout::k8_8, BufferFormat::k8_8UNorm, kNormalizedIntegerTypes, 2,
BufferFormat::k8_8Srgb, ChannelType::kSrgb},
{ChannelLayout::k32, BufferFormat::k32UInt, kIntegerTypes, 1, BufferFormat::k32Float,
ChannelType::kFloat},
{ChannelLayout::k16_16, BufferFormat::k16_16UNorm, kNormalizedIntegerTypes, 2,
BufferFormat::k16_16Float, ChannelType::kFloat},
{ChannelLayout::k11_11_10, BufferFormat::k11_11_10UNorm, kNormalizedIntegerTypes, 3,
BufferFormat::k11_11_10Float, ChannelType::kFloat},
{ChannelLayout::k10_11_11, BufferFormat::k10_11_11UNorm, kNormalizedIntegerTypes, 3,
BufferFormat::k10_11_11Float, ChannelType::kFloat},
{ChannelLayout::k2_10_10_10, BufferFormat::k2_10_10_10UNorm, kNormalizedIntegerTypes, 4},
{ChannelLayout::k10_10_10_2, BufferFormat::k10_10_10_2UNorm, kNormalizedIntegerTypes, 4},
{ChannelLayout::k8_8_8_8, BufferFormat::k8_8_8_8UNorm, kNormalizedIntegerTypes, 4,
BufferFormat::k8_8_8_8Srgb, ChannelType::kSrgb},
{ChannelLayout::k32_32, BufferFormat::k32_32UInt, kIntegerTypes, 2, BufferFormat::k32_32Float,
ChannelType::kFloat},
{ChannelLayout::k16_16_16_16, BufferFormat::k16_16_16_16UNorm, kNormalizedIntegerTypes, 4,
BufferFormat::k16_16_16_16Float, ChannelType::kFloat},
{ChannelLayout::k32_32_32_32, BufferFormat::k32_32_32_32UInt, kIntegerTypes, 4,
BufferFormat::k32_32_32_32Float, ChannelType::kFloat},
{ChannelLayout::k5_6_5, BufferFormat::k5_6_5UNorm, kUNormType, 3},
{ChannelLayout::k5_5_5_1, BufferFormat::k5_5_5_1UNorm, kUNormType, 4},
{ChannelLayout::k1_5_5_5, BufferFormat::k1_5_5_5UNorm, kUNormType, 4},
{ChannelLayout::k4_4_4_4, BufferFormat::k4_4_4_4UNorm, kUNormType, 4},
{ChannelLayout::k10_10_10_2Float, BufferFormat::kInvalid, 0, 4, BufferFormat::k10_10_10_2Float,
ChannelType::kFloat, ChannelOrderSupport::kStandardOnly},
};
const RenderTargetLayoutInfo* FindRenderTargetLayout(ChannelLayout layout) {
for (const auto& info: kRenderTargetLayouts) {
if (info.layout == layout) {
return &info;
}
}
return nullptr;
}
constexpr auto MakeFormatInfoLookup() {
constexpr uint32_t kMaxFormat = GpuEnumValue(BufferFormat::kBc7Srgb);
constexpr size_t kMaxFormat = static_cast<size_t>(BufferFormat::kBc7Srgb);
std::array<const FormatInfo*, kMaxFormat + 1> lookup {};
for (const auto& info: kFormatInfo) {
lookup[info.format] = &info;
lookup[static_cast<size_t>(info.format)] = &info;
}
return lookup;
}
constexpr auto kFormatInfoLookup = MakeFormatInfoLookup();
const FormatInfo* FindFormatInfo(uint32_t format) {
return format < kFormatInfoLookup.size() ? kFormatInfoLookup[format] : nullptr;
const FormatInfo* FindFormatInfo(BufferFormat format) {
const auto index = static_cast<size_t>(format);
return index < kFormatInfoLookup.size() ? kFormatInfoLookup[index] : nullptr;
}
} // namespace
uint32_t NumBytesPerElement(uint32_t format) {
RenderTargetFormatEncoding ResolveRenderTargetFormat(ChannelLayout layout, ChannelType type) {
const auto* layout_info = FindRenderTargetLayout(layout);
const auto raw_type = static_cast<uint32_t>(type);
if (layout_info == nullptr || raw_type >= 8u) {
return {};
}
auto format = BufferFormat::kInvalid;
if (layout_info->extra_format != BufferFormat::kInvalid && type == layout_info->extra_type) {
format = layout_info->extra_format;
} else if ((layout_info->linear_type_mask & (1u << raw_type)) != 0) {
const auto first_type =
std::countr_zero(static_cast<uint32_t>(layout_info->linear_type_mask));
format = static_cast<BufferFormat>(static_cast<uint32_t>(layout_info->first_linear_format) +
raw_type - first_type);
}
if (format == BufferFormat::kInvalid) {
return {};
}
return {format, layout_info->components, layout_info->order_support};
}
uint32_t NumBytesPerElement(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr ? info->bytes_per_element : 0;
}
uint32_t BlockCompressedBytesPerBlock(uint32_t format) {
uint32_t BlockCompressedBytesPerBlock(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr ? info->block_compressed_bytes_per_block : 0;
}
uint32_t RenderTargetBytesPerElement(uint32_t format) {
uint32_t RenderTargetBytesPerElement(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr ? info->render_target_bytes_per_element : 0;
}
bool IsSupportedTextureFormat(uint32_t format) {
const auto* info = FindFormatInfo(format);
return info != nullptr && info->supported_sampled_texture_format;
bool IsFmaskTextureFormat(BufferFormat format) {
return format == BufferFormat::kFmask8_S4_F4;
}
bool IsUintTextureFormat(uint32_t format) {
bool IsSampledTextureFormat(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr && info->unsigned_integer_texture_format;
return info != nullptr && info->sampled_texture;
}
bool IsFmaskTextureFormat(uint32_t format) {
return format == GpuEnumValue(BufferFormat::kFmask8_S4_F4);
bool IsUintTextureFormat(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr && info->uint_texture;
}
BufferFormat RemapTextureFormat(BufferFormat format) {
return format == BufferFormat::k11_11_10UInt ? BufferFormat::k32UInt : format;
}
} // namespace Libs::Graphics::Prospero
+41 -7
View File
@@ -1,16 +1,50 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GUEST_GPU_GPU_FORMAT_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GUEST_GPU_GPU_FORMAT_H_
#include "common/common.h"
#include "graphics/guest_gpu/gpu_defs.h"
namespace Libs::Graphics::Prospero {
uint32_t NumBytesPerElement(uint32_t format);
uint32_t BlockCompressedBytesPerBlock(uint32_t format);
uint32_t RenderTargetBytesPerElement(uint32_t format);
bool IsSupportedTextureFormat(uint32_t format);
bool IsUintTextureFormat(uint32_t format);
bool IsFmaskTextureFormat(uint32_t format);
enum class ChannelOrderSupport : uint8_t {
kNone,
kStandardOnly,
kAll,
};
struct RenderTargetFormatEncoding {
BufferFormat buffer_format = BufferFormat::kInvalid;
uint8_t components = 0;
ChannelOrderSupport order_support = ChannelOrderSupport::kNone;
[[nodiscard]] constexpr bool IsValid() const {
return buffer_format != BufferFormat::kInvalid && components >= 1u && components <= 4u &&
order_support != ChannelOrderSupport::kNone;
}
[[nodiscard]] constexpr bool SupportsOrder(ChannelOrder order) const {
switch (order_support) {
case ChannelOrderSupport::kStandardOnly: return order == ChannelOrder::kStandard;
case ChannelOrderSupport::kAll:
switch (order) {
case ChannelOrder::kStandard:
case ChannelOrder::kAlt:
case ChannelOrder::kReversed:
case ChannelOrder::kAltReversed: return true;
}
return false;
default: return false;
}
}
};
RenderTargetFormatEncoding ResolveRenderTargetFormat(ChannelLayout layout, ChannelType type);
uint32_t NumBytesPerElement(BufferFormat format);
uint32_t BlockCompressedBytesPerBlock(BufferFormat format);
uint32_t RenderTargetBytesPerElement(BufferFormat format);
bool IsFmaskTextureFormat(BufferFormat format);
bool IsSampledTextureFormat(BufferFormat format);
bool IsUintTextureFormat(BufferFormat format);
BufferFormat RemapTextureFormat(BufferFormat format);
} // namespace Libs::Graphics::Prospero
+184 -267
View File
@@ -13,6 +13,7 @@
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/sync.h"
#include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/videoOut.h"
#include "graphics/presentation/window.h"
#include "graphics/shader/shader.h"
@@ -26,6 +27,7 @@
#include <cstdio>
#include <deque>
#include <memory>
#include <mutex>
#include <semaphore>
#include <thread>
#include <vector>
@@ -36,7 +38,7 @@ static thread_local CommandProcessor* g_current_processor = nullptr;
static thread_local Pm4Execution* g_current_execution = nullptr;
static thread_local bool g_gpu_mutex_owned = false;
static thread_local bool g_gpu_thread = false;
static thread_local GpuState* g_gpu_state = nullptr;
static thread_local GuestGpu* g_gpu_state = nullptr;
class GpuMutexLock final {
public:
@@ -59,115 +61,23 @@ private:
Common::Mutex& m_mutex;
};
struct OwnedCmdBuffer {
OwnedCmdBuffer() = default;
explicit OwnedCmdBuffer(const uint32_t* data, uint32_t count) {
EXIT_IF(data == nullptr && count != 0);
if (count != 0) {
m_words.assign(data, data + count);
}
}
[[nodiscard]] bool Empty() const noexcept { return m_words.empty(); }
[[nodiscard]] uint32_t Size() const noexcept { return static_cast<uint32_t>(m_words.size()); }
[[nodiscard]] uint32_t* Data() noexcept { return m_words.data(); }
private:
std::vector<uint32_t> m_words;
};
class GpuState {
public:
static constexpr uint32_t ComputePipeCount = 7;
static constexpr uint32_t QueuesPerComputePipe = 8;
static constexpr uint32_t ComputeQueueCount = ComputePipeCount * QueuesPerComputePipe;
static constexpr uint32_t QueueCount = 1 + ComputeQueueCount;
explicit GpuState(RenderContext& renderer): m_renderer(renderer) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
m_gfx_cp = std::make_unique<CommandProcessor>(renderer);
m_thread = std::jthread(ThreadRun, this);
}
~GpuState();
KYTY_CLASS_NO_COPY(GpuState);
void Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done);
void SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
bool trigger_agc_interrupt_on_done);
void SubmitFlipPreparation(uint64_t request_id);
void Done();
void Shutdown();
[[nodiscard]] bool IsStopping();
void SendCommand(Common::UniqueFunction<void>&& command);
void SendCommandSync(Common::UniqueFunction<void>&& command);
int GetFrameNum();
[[nodiscard]] static bool IsGpuThread() noexcept { return g_gpu_thread; }
private:
enum class SubmissionType { Graphics, Compute, FlipPreparation };
struct Submission {
SubmissionType type = SubmissionType::Graphics;
uint32_t queue_id = 0;
OwnedCmdBuffer commands;
OwnedCmdBuffer constant_commands;
Pm4Execution command_execution;
Pm4Execution constant_execution;
bool trigger_agc_interrupt_on_done = false;
bool reset_processor = false;
bool started = false;
bool command_complete = false;
bool constant_complete = false;
bool blocked = false;
uint64_t flip_request_id = 0;
};
void Enqueue(Submission submission);
void WaitForIdle();
void ProcessCommands();
bool Process(Submission& submission);
static void ThreadRun(void* data);
CommandProcessor& GetProcessor(uint32_t queue_id);
RenderContext& m_renderer;
Common::Mutex m_submission_mutex;
Common::Mutex m_queue_mutex;
std::mutex m_shutdown_mutex;
Common::CondVar m_work_available;
Common::CondVar m_idle;
std::array<std::deque<Submission>, QueueCount> m_queues;
std::deque<Common::UniqueFunction<void>> m_commands;
std::atomic_uint32_t m_pending_commands {0};
uint32_t m_next_queue = 0;
uint32_t m_submission_count = 0;
bool m_processing = false;
bool m_graphics_done = true;
bool m_accepting = true;
bool m_stopping = false;
bool m_shutdown_complete = false;
std::unique_ptr<CommandProcessor> m_gfx_cp;
std::array<std::unique_ptr<CommandProcessor>, ComputeQueueCount> m_compute_cp;
uint64_t m_submit_id = 0;
std::atomic_int m_done_num = 0;
std::jthread m_thread;
friend class CommandProcessor;
};
static bool GraphicsRunDebugDumpEnabled() {
return Config::GraphicsDebugDumpEnabled() &&
Config::GetPrintfDirection() != Config::OutputDirection::Silent;
}
GpuState::~GpuState() {
GuestGpu::GuestGpu(RenderContext& renderer): m_renderer(renderer) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
GraphicsInitJmpTables();
m_gfx_cp = std::make_unique<CommandProcessor>(renderer, 0);
m_thread = std::jthread(ThreadRun, this);
}
GuestGpu::~GuestGpu() {
Shutdown();
}
void GpuState::Shutdown() {
void GuestGpu::Shutdown() {
std::lock_guard shutdown_lock(m_shutdown_mutex);
if (m_shutdown_complete) {
return;
@@ -184,12 +94,12 @@ void GpuState::Shutdown() {
m_shutdown_complete = true;
}
bool GpuState::IsStopping() {
bool GuestGpu::IsStopping() {
Common::LockGuard lock(m_queue_mutex);
return m_stopping;
}
void GpuState::SendCommand(Common::UniqueFunction<void>&& command) {
void GuestGpu::SendCommand(Common::UniqueFunction<void>&& command) {
EXIT_IF(!command);
if (IsGpuThread()) {
command();
@@ -202,7 +112,7 @@ void GpuState::SendCommand(Common::UniqueFunction<void>&& command) {
m_work_available.Signal();
}
void GpuState::ProcessCommands() {
void GuestGpu::ProcessCommands() {
EXIT_IF(!IsGpuThread());
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
Common::UniqueFunction<void> command;
@@ -217,7 +127,7 @@ void GpuState::ProcessCommands() {
}
}
void GpuState::SendCommandSync(Common::UniqueFunction<void>&& command) {
void GuestGpu::SendCommandSync(Common::UniqueFunction<void>&& command) {
EXIT_IF(!command);
if (IsGpuThread()) {
command();
@@ -231,38 +141,37 @@ void GpuState::SendCommandSync(Common::UniqueFunction<void>&& command) {
done.acquire();
}
void GpuState::Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done) {
void GuestGpu::Submit(std::span<const uint32_t> draw_commands,
std::span<const uint32_t> constant_commands) {
if (draw_commands.empty()) {
return;
}
GpuMutexLock lock(m_submission_mutex);
Submission submission;
submission.type = SubmissionType::Graphics;
submission.queue_id = 0;
submission.commands = OwnedCmdBuffer(cmd_draw_buffer, num_draw_dw);
submission.constant_commands = OwnedCmdBuffer(cmd_const_buffer, num_const_dw);
submission.trigger_agc_interrupt_on_done = trigger_agc_interrupt_on_done;
submission.reset_processor = m_graphics_done;
m_graphics_done = false;
submission.type = SubmissionType::Graphics;
submission.queue_id = 0;
submission.commands = draw_commands;
submission.constant_commands = constant_commands;
submission.reset_processor = m_graphics_done;
m_graphics_done = false;
Enqueue(std::move(submission));
}
void GpuState::SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
bool trigger_agc_interrupt_on_done) {
void GuestGpu::SubmitCompute(uint32_t queue, std::span<const uint32_t> commands) {
EXIT_IF(commands.empty());
GpuMutexLock lock(m_submission_mutex);
constexpr uint32_t compute_queue_base = 0x20u;
EXIT_NOT_IMPLEMENTED(queue < compute_queue_base ||
queue >= compute_queue_base + ComputeQueueCount);
EXIT_NOT_IMPLEMENTED(queue < ComputeQueueBase || queue >= ComputeQueueBase + ComputeQueueCount);
const auto compute_queue = queue - compute_queue_base;
const auto compute_queue = queue - ComputeQueueBase;
Submission submission;
submission.type = SubmissionType::Compute;
submission.queue_id = 1 + compute_queue;
submission.commands = OwnedCmdBuffer(cmd_buffer, num_dw);
submission.trigger_agc_interrupt_on_done = trigger_agc_interrupt_on_done;
submission.type = SubmissionType::Compute;
submission.queue_id = 1 + compute_queue;
submission.commands = commands;
Enqueue(std::move(submission));
}
void GpuState::SubmitFlipPreparation(uint64_t request_id) {
void GuestGpu::SubmitFlipPreparation(uint64_t request_id) {
GpuMutexLock lock(m_submission_mutex);
Submission submission;
submission.type = SubmissionType::FlipPreparation;
@@ -273,7 +182,7 @@ void GpuState::SubmitFlipPreparation(uint64_t request_id) {
Enqueue(std::move(submission));
}
void GpuState::Done() {
void GuestGpu::Done() {
GpuMutexLock lock(m_submission_mutex);
if (!IsGpuThread()) {
WaitForIdle();
@@ -282,18 +191,18 @@ void GpuState::Done() {
m_done_num++;
}
int GpuState::GetFrameNum() {
int GuestGpu::GetFrameNum() const {
return m_done_num;
}
CommandProcessor& GpuState::GetProcessor(uint32_t queue_id) {
CommandProcessor& GuestGpu::GetProcessor(uint32_t queue_id) {
EXIT_IF(queue_id >= QueueCount);
if (queue_id == 0) {
return *m_gfx_cp;
}
auto& processor = m_compute_cp[queue_id - 1];
if (processor == nullptr) {
processor = std::make_unique<CommandProcessor>(m_renderer);
processor = std::make_unique<CommandProcessor>(m_renderer, ComputeQueueBase + queue_id - 1);
}
return *processor;
}
@@ -346,8 +255,7 @@ void CommandProcessor::BufferFlush() {
}
void CommandProcessor::BufferFlushAndWait() {
auto& submitted = GetScheduler().FlushAndGetSubmitted();
submitted.WaitForFenceOnly();
GetScheduler().FlushAndWait();
}
void CommandProcessor::BufferWait() {
@@ -374,6 +282,12 @@ void CommandProcessor::WaitDeDiff(uint32_t diff) {
}
}
void CommandProcessor::WaitForRewind(bool valid) {
if (!valid) {
SuspendPm4();
}
}
void CommandProcessor::IncrementDe() {
m_de_count++;
}
@@ -426,10 +340,8 @@ template void CommandProcessor::WaitRegMem<uint64_t>(uint32_t, const uint64_t*,
void CommandProcessor::WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw_num,
uint32_t write_control) {
const uint32_t dst_sel = ((write_control >> 30u) & 0x1u) | ((write_control >> 7u) & 0x1eu);
const uint32_t cache_policy = (write_control >> 25u) & 0x3u;
const uint32_t increment = (write_control >> 16u) & 0x1u;
const uint32_t write_confirm = (write_control >> 20u) & 0x1u;
const uint32_t dst_sel = ((write_control >> 30u) & 0x1u) | ((write_control >> 7u) & 0x1eu);
const bool write_one_address = ((write_control >> 16u) & 0x1u) != 0;
switch (dst_sel) {
case 0:
@@ -439,16 +351,17 @@ void CommandProcessor::WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw
case 6: break;
default: EXIT("unsupported writeData destination selector 0x%02" PRIx32 "\n", dst_sel);
}
EXIT_NOT_IMPLEMENTED(increment != 0);
if (cache_policy > 3 || write_confirm > 1) {
LOGF("\t warning: unexpected write_data control 0x%08" PRIx32 "\n", write_control);
}
if (dw_num == 0) {
return;
}
memcpy(dst, src, static_cast<size_t>(dw_num) * sizeof(uint32_t));
if (write_one_address) {
for (uint32_t i = 0; i < dw_num; i++) {
dst[0] = src[i];
}
} else {
memcpy(dst, src, static_cast<size_t>(dw_num) * sizeof(uint32_t));
}
}
void CommandProcessor::WriteReferenceClock(uint64_t dst_address, uint32_t num_bytes) {
@@ -473,7 +386,6 @@ void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cach
if (num_bytes == 0) {
return;
}
EXIT_NOT_IMPLEMENTED((num_bytes & 3u) != 0);
EXIT_NOT_IMPLEMENTED(dst_cache_policy > 3);
EXIT_NOT_IMPLEMENTED(src_cache_policy > 3);
EXIT_NOT_IMPLEMENTED(wait_for_previous > 1);
@@ -490,6 +402,13 @@ void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cach
default: return false;
}
};
if (dst_sel == 2) {
// kNowhere discards the GL2 prefetch destination without a guest-visible write.
if (src_sel != 3) {
EXIT("unsupported dmaData nowhere source selector 0x%02" PRIx8 "\n", src_sel);
}
return;
}
bool dst_gds = false;
if (!decode_gds(dst_sel, dst_gds)) {
EXIT("unsupported dmaData destination selector 0x%02" PRIx8 "\n", dst_sel);
@@ -512,7 +431,7 @@ void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cach
dst_gds, src_gds);
}
void GpuState::Enqueue(Submission submission) {
void GuestGpu::Enqueue(Submission submission) {
EXIT_IF(submission.queue_id >= QueueCount);
Common::LockGuard lock(m_queue_mutex);
EXIT_IF(!m_accepting);
@@ -521,15 +440,15 @@ void GpuState::Enqueue(Submission submission) {
m_work_available.Signal();
}
void GpuState::WaitForIdle() {
void GuestGpu::WaitForIdle() {
Common::LockGuard lock(m_queue_mutex);
while (m_processing || !m_commands.empty() || m_submission_count != 0) {
m_idle.Wait(&m_queue_mutex);
}
}
void GpuState::ThreadRun(void* data) {
auto* gpu = static_cast<GpuState*>(data);
void GuestGpu::ThreadRun(void* data) {
auto* gpu = static_cast<GuestGpu*>(data);
EXIT_IF(gpu == nullptr);
KYTY_PROFILER_THREAD("Thread_Gpu");
g_gpu_thread = true;
@@ -625,9 +544,13 @@ void GpuState::ThreadRun(void* data) {
}
}
bool GpuState::Process(Submission& submission) {
auto& cp = GetProcessor(submission.queue_id);
bool GuestGpu::Process(Submission& submission) {
const bool first_slice = !submission.started;
if (first_slice && RenderDocCaptureRequested()) {
Common::LockGuard render_lock(m_renderer.GetMutex());
RenderDocStartCapture();
}
auto& cp = GetProcessor(submission.queue_id);
if (first_slice && submission.reset_processor) {
cp.Reset();
@@ -646,21 +569,20 @@ bool GpuState::Process(Submission& submission) {
switch (submission.type) {
case SubmissionType::Graphics: {
bool progressed = false;
submission.constant_complete |= submission.constant_commands.Empty();
submission.constant_complete |= submission.constant_commands.empty();
for (;;) {
bool round_progress = false;
if (!submission.constant_complete) {
submission.constant_complete =
cp.Process(
submission.constant_execution, submission.constant_commands.Data(),
submission.constant_commands.Size()) == Pm4ProcessResult::Complete;
cp.Process(submission.constant_execution, submission.constant_commands) ==
Pm4ProcessResult::Complete;
round_progress |= submission.constant_execution.MadeProgress();
}
cp.SetCeComplete(submission.constant_complete);
if (!submission.command_complete) {
submission.command_complete =
cp.Process(submission.command_execution, submission.commands.Data(),
submission.commands.Size()) == Pm4ProcessResult::Complete;
cp.Process(submission.command_execution, submission.commands) ==
Pm4ProcessResult::Complete;
round_progress |= submission.command_execution.MadeProgress();
}
progressed |= round_progress;
@@ -677,14 +599,11 @@ bool GpuState::Process(Submission& submission) {
} else if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
}
if (complete && submission.trigger_agc_interrupt_on_done) {
m_renderer.TriggerEopEvent(0);
}
break;
}
case SubmissionType::Compute: {
const auto num_dw = submission.commands.Size();
auto* buffer = submission.commands.Data();
const auto num_dw = static_cast<uint32_t>(submission.commands.size());
const auto* buffer = submission.commands.data();
static uint32_t compute_batch_log_count = 0;
if (first_slice && num_dw <= 128 && compute_batch_log_count++ < 32) {
LOGF("compute direct batch: data=0x%016" PRIx64 ", num_dw=%" PRIu32 "\n",
@@ -696,7 +615,7 @@ bool GpuState::Process(Submission& submission) {
if (first_slice) {
GraphicsDbgDumpDcb("cc", num_dw, buffer);
}
complete = cp.Process(submission.command_execution, buffer, num_dw) ==
complete = cp.Process(submission.command_execution, submission.commands) ==
Pm4ProcessResult::Complete;
if (submission.command_execution.MadeProgress()) {
if (complete) {
@@ -706,9 +625,6 @@ bool GpuState::Process(Submission& submission) {
} else if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
}
if (complete && submission.trigger_agc_interrupt_on_done) {
Sync::TriggerAgcUserInterrupt();
}
break;
}
case SubmissionType::FlipPreparation:
@@ -720,12 +636,13 @@ bool GpuState::Process(Submission& submission) {
return complete;
}
Pm4ProcessResult CommandProcessor::Process(Pm4Execution& execution, uint32_t* buffer,
uint32_t size_dw) {
Pm4ProcessResult CommandProcessor::Process(Pm4Execution& execution,
std::span<const uint32_t> commands) {
KYTY_PROFILER_BLOCK("CommandProcessor::Process");
EXIT_IF(g_current_execution != nullptr);
if (execution.m_buffer_stack.empty() && size_dw != 0) {
execution.m_buffer_stack.push_back({buffer, size_dw, size_dw, 0});
EXIT_IF(commands.size() > UINT32_MAX);
if (execution.m_buffer_stack.empty() && !commands.empty()) {
execution.m_buffer_stack.push_back({commands});
}
execution.m_suspended = false;
execution.m_made_progress = false;
@@ -750,14 +667,14 @@ Pm4ProcessResult CommandProcessor::Process(Pm4Execution& execution, uint32_t* bu
: Pm4ProcessResult::Blocked;
}
void CommandProcessor::ProcessIndirectBuffer(uint32_t* buffer, uint32_t size_dw) {
void CommandProcessor::ProcessIndirectBuffer(std::span<const uint32_t> commands) {
EXIT_IF(g_current_execution == nullptr);
if (size_dw == 0) {
if (commands.empty()) {
return;
}
auto& execution = *g_current_execution;
const auto stop_depth = execution.m_buffer_stack.size();
execution.m_buffer_stack.push_back({buffer, size_dw, size_dw, 0});
execution.m_buffer_stack.push_back({commands});
ProcessPm4(execution, stop_depth);
}
@@ -773,29 +690,28 @@ void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
}
const auto buffer_index = execution.m_buffer_stack.size() - 1;
auto& cursor = execution.m_buffer_stack[buffer_index];
EXIT_IF(cursor.offset_dw > cursor.commands.size());
if (cursor.deferred_advance_dw != 0) {
EXIT_IF(cursor.deferred_advance_dw > cursor.remaining_dw);
cursor.next_packet += cursor.deferred_advance_dw;
cursor.remaining_dw -= cursor.deferred_advance_dw;
EXIT_IF(cursor.deferred_advance_dw > cursor.commands.size() - cursor.offset_dw);
cursor.offset_dw += cursor.deferred_advance_dw;
cursor.deferred_advance_dw = 0;
execution.m_made_progress = true;
continue;
}
if (cursor.remaining_dw == 0) {
if (cursor.offset_dw == cursor.commands.size()) {
execution.m_buffer_stack.pop_back();
continue;
}
auto* const packet = cursor.next_packet;
const auto remaining_dw = cursor.remaining_dw;
const auto total_dw = cursor.total_dw;
const auto packet_header = packet[0];
const auto opcode = (packet_header >> 8u) & 0xffu;
const auto* const packet = cursor.commands.data() + cursor.offset_dw;
const auto total_dw = static_cast<uint32_t>(cursor.commands.size());
const auto remaining_dw = total_dw - cursor.offset_dw;
const auto packet_header = packet[0];
const auto opcode = (packet_header >> 8u) & 0xffu;
EXIT_NOT_IMPLEMENTED(remaining_dw > total_dw);
if (packet_header == 0x80000000u) {
cursor.next_packet++;
cursor.remaining_dw--;
cursor.offset_dw++;
execution.m_made_progress = true;
continue;
}
@@ -830,8 +746,7 @@ void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
dst, val, packet[1], packet[2]);
}
}
cursor.next_packet += packet_dw;
cursor.remaining_dw -= packet_dw;
cursor.offset_dw += packet_dw;
execution.m_made_progress = true;
continue;
}
@@ -864,8 +779,7 @@ void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
return;
}
EXIT_IF(execution.m_buffer_stack.size() != buffer_index + 1);
execution.m_buffer_stack[buffer_index].next_packet += packet_dw;
execution.m_buffer_stack[buffer_index].remaining_dw -= packet_dw;
execution.m_buffer_stack[buffer_index].offset_dw += packet_dw;
execution.m_made_progress = true;
}
}
@@ -1164,6 +1078,8 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_group_y,
uint32_t thread_group_z, uint32_t mode) {
m_sh_ctx.SetCsWaveSize(Pm4::ComputeWaveSize(mode));
uint32_t frame_num = 0;
// uint32_t local_x = 1;
// uint32_t local_y = 1;
@@ -1306,7 +1222,14 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
switch (interrupt_selector) {
case 0x00:
case 0x03: with_interrupt = false; break;
case 0x01: Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), interrupt_context_id); return;
case 0x01:
if (!IsAsyncComputeQueue()) {
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), m_interrupt_event_id,
interrupt_context_id);
return;
}
with_interrupt = true;
break;
case 0x02: with_interrupt = true; break;
default: EXIT("unknown interrupt_selector\n");
}
@@ -1319,10 +1242,11 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
if (with_interrupt) {
if (with_writeback) {
Sync::WriteAtEndOfPipeWithInterruptWriteBack32(m_submit_id, CurrentBuffer(), dst,
data, interrupt_context_id);
data, m_interrupt_event_id,
interrupt_context_id);
} else {
Sync::WriteAtEndOfPipeWithInterrupt32(m_submit_id, CurrentBuffer(), dst, data,
interrupt_context_id);
m_interrupt_event_id, interrupt_context_id);
}
} else if (with_writeback) {
Sync::WriteAtEndOfPipeWithWriteBack32(m_submit_id, CurrentBuffer(), dst, data);
@@ -1337,12 +1261,12 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
if (eop_event_type == 0x2f && cache_action == 0x00 && event_index == 0x06) {
auto* dst = static_cast<uint32_t*>(dst_gpu_addr);
SynchronizeGpu();
Sync::ReadGds(m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu,
Sync::ReadGds(*m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu,
value >> 16u);
Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu,
value >> 16u);
if (with_interrupt) {
m_renderer.TriggerEopEvent(interrupt_context_id);
m_renderer.TriggerInterrupt(m_interrupt_event_id, interrupt_context_id);
}
return;
}
@@ -1368,10 +1292,12 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
if (with_interrupt) {
if (with_writeback) {
Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
m_submit_id, CurrentBuffer(), dst, value, interrupt_context_id);
m_submit_id, CurrentBuffer(), dst, value, m_interrupt_event_id,
interrupt_context_id);
} else {
Sync::WriteAtEndOfPipeWithInterrupt64(m_submit_id, CurrentBuffer(), dst,
value, interrupt_context_id);
value, m_interrupt_event_id,
interrupt_context_id);
}
} else if (with_writeback) {
Sync::WriteAtEndOfPipeWithWriteBack64(m_submit_id, CurrentBuffer(), dst,
@@ -1385,9 +1311,14 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
case 0x00:
switch (eop_event_type) {
case 0x04:
if (event_index == 0x05) {
write64(false);
return;
}
break;
case 0x14:
case 0x28:
if ((eop_event_type == 0x04 && event_index == 0x05) ||
(eop_event_type == 0x28 && event_index == 0x00)) {
if (event_index == 0x00) {
write64(false);
return;
}
@@ -1445,26 +1376,35 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
case 0x04:
if constexpr (sizeof(T) == sizeof(uint64_t)) {
const auto clock = Sync::ReadReferenceClock();
std::memcpy(dst_gpu_addr, &clock, sizeof(clock));
auto* dst = static_cast<uint64_t*>(dst_gpu_addr);
std::memcpy(dst, &clock, sizeof(clock));
switch (cache_action) {
case 0x00:
if (((eop_event_type == 0x04 && event_index == 0x05) ||
(eop_event_type == 0x28 && event_index == 0x00)) &&
!with_interrupt) {
Sync::WriteAtEndOfPipeClockCounter(m_submit_id, CurrentBuffer(),
static_cast<uint64_t*>(dst_gpu_addr),
clock);
if ((eop_event_type == 0x04 && event_index == 0x05) ||
(eop_event_type == 0x28 && event_index == 0x00)) {
if (with_interrupt) {
Sync::WriteAtEndOfPipeWithInterrupt64(
m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id,
interrupt_context_id);
} else {
Sync::WriteAtEndOfPipeClockCounter(m_submit_id, CurrentBuffer(),
dst, clock);
}
return;
}
break;
case 0x38:
if (((eop_event_type == 0x04 &&
(event_index == 0x00 || event_index == 0x05)) ||
(eop_event_type == 0x28 && event_index == 0x00)) &&
!with_interrupt) {
Sync::WriteAtEndOfPipeClockCounterWithWriteBack(
m_submit_id, CurrentBuffer(), static_cast<uint64_t*>(dst_gpu_addr),
clock);
if ((eop_event_type == 0x04 &&
(event_index == 0x00 || event_index == 0x05)) ||
(eop_event_type == 0x28 && event_index == 0x00)) {
if (with_interrupt) {
Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
m_submit_id, CurrentBuffer(), dst, clock, m_interrupt_event_id,
interrupt_context_id);
} else {
Sync::WriteAtEndOfPipeClockCounterWithWriteBack(
m_submit_id, CurrentBuffer(), dst, clock);
}
return;
}
break;
@@ -1521,15 +1461,17 @@ void CommandProcessor::EmitGlobalBarrier() {
void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id) {
CheckBuffer();
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), interrupt_context_id);
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), m_interrupt_event_id, interrupt_context_id);
}
void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) {
void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index,
uint64_t event_address) {
if (GraphicsRunDebugDumpEnabled()) {
LOGF("CommandProcessor::TriggerEvent()\n"
"\t event_type = 0x%08" PRIx32 "\n"
"\t event_index = 0x%08" PRIx32 "\n",
event_type, event_index);
"\t event_index = 0x%08" PRIx32 "\n"
"\t address = 0x%016" PRIx64 "\n",
event_type, event_index, event_address);
}
const auto valid_cache_event_index = event_index == 0x00000000 || event_index == 0x00000007;
@@ -1566,12 +1508,36 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) {
case 0x0000001a:
case 0x0000001b:
case 0x00000038:
case 0x00000039:
case 0x0000003a:
LOGF("\t temporary: ignoring unsupported event_write type 0x%08" PRIx32
", index 0x%08" PRIx32 "\n",
event_type, event_index);
break;
case 0x00000039: {
if (event_index != 0x00000001 || event_address == 0 || (event_address & 0x7u) != 0) {
EXIT("invalid occlusion-counter dump: index=0x%08" PRIx32 ", address=0x%016" PRIx64
"\n",
event_index, event_address);
}
static std::once_flag warning_once;
std::call_once(warning_once, [] {
std::printf("Warning: game uses occlusion queries, which are currently treated as "
"always visible; GPU usage may be higher and FPS may be lower.\n");
});
// Until host occlusion queries are implemented, publish an always-visible result. The
// PS5 layout contains one interleaved begin/end pair per DB, and bit 63 marks a result
// ready.
constexpr uint64_t ready_bit = 1ull << 63u;
constexpr uint64_t counter_mask = ready_bit - 1u;
auto* results = reinterpret_cast<volatile uint64_t*>(event_address);
const auto value = ready_bit | m_synthetic_occlusion_counter;
for (uint32_t db = 0; db < 16u; db++) {
results[db * 2u] = value;
}
m_synthetic_occlusion_counter = (m_synthetic_occlusion_counter + 1u) & counter_mask;
break;
}
default:
EXIT("unknown event type: 0x%08" PRIx32 ", 0x%08" PRIx32 "\n", event_type, event_index);
}
@@ -1634,7 +1600,7 @@ void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache
m_flip.flip_arg);
Sync::WriteAtEndOfPipeWithInterruptWriteBackFlip32(
m_submit_id, command, static_cast<uint32_t*>(dst_gpu_addr), value, m_flip.handle,
m_flip.index, m_flip.flip_mode, m_flip.flip_arg, request);
m_flip.index, m_flip.flip_mode, m_flip.flip_arg, request, m_interrupt_event_id);
GetScheduler().Flush();
}
@@ -1655,60 +1621,11 @@ void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
}
void CommandProcessor::SynchronizeGpu() {
GetScheduler().FinishCurrent();
GetScheduler().Finish();
}
Gpu::Gpu(RenderContext& renderer) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
GraphicsInitJmpTables();
m_state = std::make_unique<GpuState>(renderer);
}
Gpu::~Gpu() = default;
void Gpu::Shutdown() {
m_state->Shutdown();
}
bool Gpu::IsStopping() {
return m_state->IsStopping();
}
void Gpu::SendCommand(Common::UniqueFunction<void>&& command) {
m_state->SendCommand(std::move(command));
}
void Gpu::SendCommandSync(Common::UniqueFunction<void>&& command) {
m_state->SendCommandSync(std::move(command));
}
void Gpu::Submit(uint32_t* draw_commands, uint32_t draw_size_dw, uint32_t* constant_commands,
uint32_t constant_size_dw, bool trigger_agc_interrupt_on_done) {
EXIT_IF(draw_commands == nullptr || draw_size_dw == 0);
m_state->Submit(draw_commands, draw_size_dw, constant_commands, constant_size_dw,
trigger_agc_interrupt_on_done);
}
void Gpu::SubmitCompute(uint32_t queue, uint32_t* commands, uint32_t size_dw,
bool trigger_agc_interrupt_on_done) {
EXIT_IF(commands == nullptr || size_dw == 0);
m_state->SubmitCompute(queue, commands, size_dw, trigger_agc_interrupt_on_done);
}
void Gpu::SubmitFlipPreparation(uint64_t request_id) {
m_state->SubmitFlipPreparation(request_id);
}
void Gpu::Done() {
m_state->Done();
}
int Gpu::GetFrameNum() const {
return m_state->GetFrameNum();
}
bool Gpu::IsGpuThread() noexcept {
return GpuState::IsGpuThread();
bool GuestGpu::IsGpuThread() noexcept {
return g_gpu_thread;
}
} // namespace Libs::Graphics
+74 -12
View File
@@ -3,38 +3,100 @@
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "common/uniqueFunction.h"
#include "graphics/guest_gpu/command_processor/commandProcessor.h"
#include <array>
#include <atomic>
#include <cstdint>
#include <deque>
#include <memory>
#include <mutex>
#include <span>
#include <thread>
namespace Libs::Graphics {
class GpuState;
class RenderContext;
class Gpu final {
class GuestGpu final {
public:
explicit Gpu(RenderContext& renderer);
~Gpu();
KYTY_CLASS_NO_COPY(Gpu);
explicit GuestGpu(RenderContext& renderer);
~GuestGpu();
KYTY_CLASS_NO_COPY(GuestGpu);
void Shutdown();
[[nodiscard]] bool IsStopping();
void SendCommand(Common::UniqueFunction<void>&& command);
void SendCommandSync(Common::UniqueFunction<void>&& command);
void Submit(uint32_t* draw_commands, uint32_t draw_size_dw, uint32_t* constant_commands,
uint32_t constant_size_dw, bool trigger_agc_interrupt_on_done = false);
void SubmitCompute(uint32_t queue, uint32_t* commands, uint32_t size_dw,
bool trigger_agc_interrupt_on_done = false);
void SubmitFlipPreparation(uint64_t request_id);
void Done();
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
void Submit(std::span<const uint32_t> draw_commands,
std::span<const uint32_t> constant_commands);
void SubmitCompute(uint32_t queue, std::span<const uint32_t> commands);
void SubmitFlipPreparation(uint64_t request_id);
void Done();
[[nodiscard]] int GetFrameNum() const;
[[nodiscard]] static bool IsGpuThread() noexcept;
private:
std::unique_ptr<GpuState> m_state;
static constexpr uint32_t ComputePipeCount = 7;
static constexpr uint32_t QueuesPerComputePipe = 8;
static constexpr uint32_t ComputeQueueCount = ComputePipeCount * QueuesPerComputePipe;
static constexpr uint32_t ComputeQueueBase = 0x20;
static constexpr uint32_t QueueCount = 1 + ComputeQueueCount;
enum class SubmissionType { Graphics, Compute, FlipPreparation };
struct Submission {
SubmissionType type = SubmissionType::Graphics;
uint32_t queue_id = 0;
std::span<const uint32_t> commands;
std::span<const uint32_t> constant_commands;
Pm4Execution command_execution;
Pm4Execution constant_execution;
bool reset_processor = false;
bool started = false;
bool command_complete = false;
bool constant_complete = false;
bool blocked = false;
uint64_t flip_request_id = 0;
};
void Enqueue(Submission submission);
void WaitForIdle();
void ProcessCommands();
bool Process(Submission& submission);
static void ThreadRun(void* data);
CommandProcessor& GetProcessor(uint32_t queue_id);
RenderContext& m_renderer;
Common::Mutex m_submission_mutex;
Common::Mutex m_queue_mutex;
std::mutex m_shutdown_mutex;
Common::CondVar m_work_available;
Common::CondVar m_idle;
std::array<std::deque<Submission>, QueueCount> m_queues;
std::deque<Common::UniqueFunction<void>> m_commands;
std::atomic_uint32_t m_pending_commands {0};
uint32_t m_next_queue = 0;
uint32_t m_submission_count = 0;
bool m_processing = false;
bool m_graphics_done = true;
bool m_accepting = true;
bool m_stopping = false;
bool m_shutdown_complete = false;
std::unique_ptr<CommandProcessor> m_gfx_cp;
std::array<std::unique_ptr<CommandProcessor>, ComputeQueueCount> m_compute_cp;
uint64_t m_submit_id = 0;
std::atomic_int m_done_num = 0;
std::jthread m_thread;
friend class CommandProcessor;
};
} // namespace Libs::Graphics
+46 -42
View File
@@ -29,27 +29,27 @@ struct ColorView {
struct ColorInfo {
bool fmask_compression_enable = false;
// uint32_t fmask_compression_mode = 0;
bool fmask_data_compression_disable = false;
bool fmask_one_frag_mode = false;
bool cmask_fast_clear_enable = false;
bool dcc_compression_enable = false;
bool blend_clamp = false;
bool blend_bypass = false;
bool round_mode = false;
uint32_t cmask_is_linear = 0;
uint32_t cmask_addr_type = 0;
bool alt_tile_mode = false;
uint32_t format = 0;
uint32_t channel_type = 0;
uint32_t channel_order = 0;
bool fmask_data_compression_disable = false;
bool fmask_one_frag_mode = false;
bool cmask_fast_clear_enable = false;
bool dcc_compression_enable = false;
bool blend_clamp = false;
bool blend_bypass = false;
bool round_mode = false;
uint32_t cmask_is_linear = 0;
uint32_t cmask_addr_type = 0;
bool alt_tile_mode = false;
Prospero::ChannelLayout format = Prospero::ChannelLayout::kInvalid;
Prospero::ChannelType channel_type = Prospero::ChannelType::kUNorm;
Prospero::ChannelOrder channel_order = Prospero::ChannelOrder::kStandard;
};
struct ColorAttrib {
bool force_dest_alpha_to_one = false;
uint32_t tile_mode = 0;
uint32_t fmask_tile_mode = 0;
uint32_t num_samples = 0;
uint32_t num_fragments = 0;
bool force_dest_alpha_to_one = false;
Prospero::TileMode tile_mode = Prospero::TileMode::kLinear;
Prospero::TileMode fmask_tile_mode = Prospero::TileMode::kLinear;
uint32_t num_samples = 0;
uint32_t num_fragments = 0;
};
struct ColorAttrib2 {
@@ -59,11 +59,11 @@ struct ColorAttrib2 {
};
struct ColorAttrib3 {
uint32_t depth = 0;
uint32_t tile_mode = 0;
uint32_t dimension = 0;
bool cmask_pipe_aligned = false;
bool dcc_pipe_aligned = false;
uint32_t depth = 0;
Prospero::TileMode tile_mode = Prospero::TileMode::kLinear;
uint32_t dimension = 0;
bool cmask_pipe_aligned = false;
bool dcc_pipe_aligned = false;
};
struct ColorDccControl {
@@ -132,7 +132,7 @@ struct RenderTarget {
};
struct DepthZInfo {
uint32_t format = 0;
Prospero::DepthFormat format = Prospero::DepthFormat::kInvalid;
uint32_t num_samples = 0;
Prospero::TextureCompatiblePlaneCompression texture_compatibility =
Prospero::TextureCompatiblePlaneCompression::kDisable;
@@ -144,16 +144,16 @@ struct DepthZInfo {
[[nodiscard]] static DepthZInfo Decode(uint32_t value) {
DepthZInfo info;
info.format = value & 0x3u;
info.format = static_cast<Prospero::DepthFormat>(value & 0x3u);
info.num_samples = (value >> 2u) & 0x3u;
info.texture_compatibility = static_cast<Prospero::TextureCompatiblePlaneCompression>(
value & Prospero::GpuEnumValue(Prospero::TextureCompatiblePlaneCompression::kBitMask));
value & static_cast<uint32_t>(Prospero::TextureCompatiblePlaneCompression::kBitMask));
info.partially_resident = (value & 0x00001000u) != 0;
info.max_mip_level = static_cast<uint8_t>((value >> 16u) & 0x0fu);
info.expclear_enabled = (value & 0x08000000u) != 0;
info.htile_acceleration = (value & 0x20000000u) != 0;
info.z_compare_base = static_cast<Prospero::ZCompareBase>(
value & Prospero::GpuEnumValue(Prospero::ZCompareBase::kBitMask));
value & static_cast<uint32_t>(Prospero::ZCompareBase::kBitMask));
return info;
}
@@ -167,7 +167,7 @@ struct DepthZInfo {
};
struct DepthStencilInfo {
uint32_t format = 0;
Prospero::StencilFormat format = Prospero::StencilFormat::kInvalid;
Prospero::TextureCompatibleStencil texture_compatibility =
Prospero::TextureCompatibleStencil::kDisable;
bool expclear_enabled = false;
@@ -176,9 +176,9 @@ struct DepthStencilInfo {
[[nodiscard]] static DepthStencilInfo Decode(uint32_t value) {
DepthStencilInfo info;
info.format = value & 0x1u;
info.format = static_cast<Prospero::StencilFormat>(value & 0x1u);
info.texture_compatibility = static_cast<Prospero::TextureCompatibleStencil>(
value & Prospero::GpuEnumValue(Prospero::TextureCompatibleStencil::kBitMask));
value & static_cast<uint32_t>(Prospero::TextureCompatibleStencil::kBitMask));
info.partially_resident = (value & 0x00001000u) != 0;
info.expclear_enabled = (value & 0x08000000u) != 0;
info.htile_stencil_disabled = (value & 0x20000000u) != 0;
@@ -295,10 +295,7 @@ struct ClipControl {
bool linear_attribute_clip_enable = false;
bool force_viewport_index_from_vs_enable = false;
[[nodiscard]] bool IsZClipModeRepresentable() const {
return min_z_clip_disable == max_z_clip_disable;
}
[[nodiscard]] bool IsZClipEnabled() const { return !min_z_clip_disable; }
[[nodiscard]] bool IsZClipEnabled() const { return !min_z_clip_disable && !max_z_clip_disable; }
};
struct DepthControl {
@@ -941,8 +938,10 @@ public:
[[nodiscard]] uint8_t GetStencilClearValue() const { return m_stencil_clear_value; }
void SetStencilClearValue(uint8_t clear_value) { m_stencil_clear_value = clear_value; }
[[nodiscard]] float GetLineWidth() const { return m_line_width; }
void SetLineWidth(float width) { m_line_width = width; }
[[nodiscard]] float GetLineWidth() const { return m_line_width; }
void SetLineWidth(float width) { m_line_width = width; }
[[nodiscard]] uint32_t GetPrimitiveResetIndex() const { return m_primitive_reset_index; }
void SetPrimitiveResetIndex(uint32_t index) { m_primitive_reset_index = index; }
[[nodiscard]] const ShaderRegisters& GetShaderRegisters() const { return m_sh_regs; }
@@ -987,7 +986,8 @@ public:
void SetScShaderControl(uint32_t value) { m_sh_regs.m_paScShaderControl = value; }
private:
float m_line_width = 1.0f;
float m_line_width = 1.0f;
uint32_t m_primitive_reset_index = 0xffffffffu;
BlendControl m_blend_control[8];
BlendColor m_blend_color;
@@ -1029,12 +1029,14 @@ public:
void Reset() { *this = UserConfig(); }
void SetPrimitiveType(uint32_t prim_type) { m_prim_type = prim_type; }
[[nodiscard]] uint32_t GetPrimType() const { return m_prim_type; }
void SetPrimitiveType(Prospero::PrimitiveType prim_type) { m_prim_type = prim_type; }
[[nodiscard]] Prospero::PrimitiveType GetPrimType() const { return m_prim_type; }
void SetIndexOffset(uint32_t index_offset) { m_index_offset = index_offset; }
[[nodiscard]] uint32_t GetIndexOffset() const { return m_index_offset; }
void SetObjectId(uint32_t object_id) { m_object_id = object_id; }
[[nodiscard]] uint32_t GetObjectId() const { return m_object_id; }
void SetPrimitiveResetControl(uint32_t control) { m_primitive_reset_control = control; }
[[nodiscard]] uint32_t GetPrimitiveResetControl() const { return m_primitive_reset_control; }
[[nodiscard]] const GeControl& GetGeControl() const { return m_ge_cntl; }
void SetGeControl(const GeControl& control) { m_ge_cntl = control; }
@@ -1049,9 +1051,10 @@ public:
void SetGdsOaAddress(uint32_t value) { m_gds_oa.counters[m_gds_oa.GetIndex()].address = value; }
private:
uint32_t m_prim_type = 0;
uint32_t m_index_offset = 0;
uint32_t m_object_id = 0;
Prospero::PrimitiveType m_prim_type = Prospero::PrimitiveType::kNone;
uint32_t m_index_offset = 0;
uint32_t m_object_id = 0;
uint32_t m_primitive_reset_control = 0;
GeControl m_ge_cntl;
GeUserVgprEn m_ge_user_vgpr_en;
@@ -1086,6 +1089,7 @@ public:
void SetPsShaderChksum(uint32_t value) { m_ps.ps_regs.chksum = value; }
void SetCsShader(const CsStageRegisters& cs_regs) { m_cs.cs_regs = cs_regs; }
void SetCsWaveSize(uint8_t wave_size) { m_cs.cs_regs.wave_size = wave_size; }
void SetPsUserSgpr(uint32_t id, uint32_t value, UserSgprType type) {
m_ps.ps_user_sgpr.value[id] = value;
+6 -6
View File
@@ -21,12 +21,10 @@ constexpr auto MakeRegisterNames() {
names[R_DRAW_RESET] = "R_DRAW_RESET";
names[R_WAIT_FLIP_DONE] = "R_WAIT_FLIP_DONE";
names[R_DISPATCH_RESET] = "R_DISPATCH_RESET";
names[R_WAIT_MEM_32] = "R_WAIT_MEM_32";
names[R_PUSH_MARKER] = "R_PUSH_MARKER";
names[R_POP_MARKER] = "R_POP_MARKER";
names[R_ACQUIRE_MEM] = "R_ACQUIRE_MEM";
names[R_WRITE_DATA] = "R_WRITE_DATA";
names[R_WAIT_MEM_64] = "R_WAIT_MEM_64";
names[R_FLIP] = "R_FLIP";
names[R_RELEASE_MEM] = "R_RELEASE_MEM";
names[R_DMA_DATA] = "R_DMA_DATA";
@@ -62,6 +60,7 @@ constexpr auto MakeOpcodeNames() {
names[IT_WRITE_DATA] = "IT_WRITE_DATA";
names[IT_MEM_SEMAPHORE] = "IT_MEM_SEMAPHORE";
names[IT_DRAW_INDEX_INDIRECT_MULTI] = "IT_DRAW_INDEX_INDIRECT_MULTI";
names[IT_WAIT_REG_MEM] = "IT_WAIT_REG_MEM";
names[IT_INDIRECT_BUFFER] = "IT_INDIRECT_BUFFER";
names[IT_COPY_DATA] = "IT_COPY_DATA";
names[IT_CP_DMA] = "IT_CP_DMA";
@@ -91,6 +90,7 @@ constexpr auto MakeOpcodeNames() {
names[IT_DISPATCH_DRAW_PREAMBLE] = "IT_DISPATCH_DRAW_PREAMBLE";
names[IT_DISPATCH_DRAW] = "IT_DISPATCH_DRAW";
names[IT_GET_LOD_STATS] = "IT_GET_LOD_STATS";
names[IT_WAIT_REG_MEM_64] = "IT_WAIT_REG_MEM_64";
names[IT_SET_CONTEXT_REG_INDIRECT] = "IT_SET_CONTEXT_REG_INDIRECT";
return names;
}
@@ -100,7 +100,7 @@ constexpr auto g_opcode_names = MakeOpcodeNames();
} // namespace
void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t start_dw,
void DumpPm4PacketStream(Common::File* file, const uint32_t* cmd_buffer, uint32_t start_dw,
uint32_t num_dw) {
// db_dump();
@@ -143,9 +143,9 @@ void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t star
if ((op == IT_SET_CONTEXT_REG_INDIRECT || op == IT_SET_SH_REG_INDIRECT ||
op == IT_SET_UCONFIG_REG_INDIRECT) &&
len == 4) {
auto* indirect_buffer =
reinterpret_cast<uint32_t*>((static_cast<uint64_t>(cmd[0]) & 0xfffffffcu) |
(static_cast<uint64_t>(cmd[1]) << 32u));
auto* indirect_buffer = reinterpret_cast<const uint32_t*>(
(static_cast<uint64_t>(cmd[0]) & 0xfffffffcu) |
(static_cast<uint64_t>(cmd[1]) << 32u));
const uint32_t indirect_num_regs = cmd[3] & 0x3fffu;
for (uint32_t i = 0; indirect_buffer != nullptr && i < indirect_num_regs;
+17 -10
View File
@@ -42,6 +42,7 @@ constexpr uint32_t IT_WRITE_DATA = 0x37;
constexpr uint32_t IT_MEM_SEMAPHORE = 0x39;
constexpr uint32_t IT_DRAW_INDEX_INDIRECT_MULTI = 0x38;
constexpr uint32_t IT_DISPATCH_DRAW_PREAMBLE = 0x3A;
constexpr uint32_t IT_WAIT_REG_MEM = 0x3C;
constexpr uint32_t IT_INDIRECT_BUFFER = 0x3F;
constexpr uint32_t IT_COPY_DATA = 0x40;
constexpr uint32_t IT_CP_DMA = 0x41;
@@ -70,6 +71,7 @@ constexpr uint32_t IT_WAIT_ON_CE_COUNTER = 0x86;
constexpr uint32_t IT_WAIT_ON_DE_COUNTER_DIFF = 0x88;
constexpr uint32_t IT_DISPATCH_DRAW = 0x8D;
constexpr uint32_t IT_GET_LOD_STATS = 0x8E;
constexpr uint32_t IT_WAIT_REG_MEM_64 = 0x93;
constexpr uint32_t IT_SET_CONTEXT_REG_INDIRECT = 0x9F;
/* Custom codes. Implemented via IT_NOP */
@@ -78,12 +80,10 @@ constexpr uint32_t R_ZERO = 0x00;
constexpr uint32_t R_DRAW_RESET = 0x05;
constexpr uint32_t R_WAIT_FLIP_DONE = 0x06;
constexpr uint32_t R_DISPATCH_RESET = 0x09;
constexpr uint32_t R_WAIT_MEM_32 = 0x0A;
constexpr uint32_t R_PUSH_MARKER = 0x0B;
constexpr uint32_t R_POP_MARKER = 0x0C;
constexpr uint32_t R_ACQUIRE_MEM = 0x14;
constexpr uint32_t R_WRITE_DATA = 0x15;
constexpr uint32_t R_WAIT_MEM_64 = 0x16;
constexpr uint32_t R_FLIP = 0x17;
constexpr uint32_t R_RELEASE_MEM = 0x18;
constexpr uint32_t R_DMA_DATA = 0x19;
@@ -317,8 +317,8 @@ constexpr uint32_t FSR_RECURSIONS1 = 0xF2;
constexpr uint32_t VGT_MULTI_PRIM_IB_RESET_INDX = 0x103;
constexpr uint32_t CB_RMI_GL2_CACHE_CONTROL = 0x104;
constexpr uint32_t CB_BLEND_RED = 0x105;
constexpr uint32_t CB_BLEND_GREEN = 0x106;
constexpr uint32_t CB_BLEND_BLUE = 0x107;
constexpr uint32_t CB_BLEND_BLUE = 0x106;
constexpr uint32_t CB_BLEND_GREEN = 0x107;
constexpr uint32_t CB_BLEND_ALPHA = 0x108;
constexpr uint32_t CB_DCC_CONTROL = 0x109;
@@ -372,6 +372,7 @@ constexpr uint32_t PA_CL_UCP_0_X = 0x16F;
constexpr uint32_t PA_CL_UCP_0_Y = 0x170;
constexpr uint32_t PA_CL_UCP_0_Z = 0x171;
constexpr uint32_t PA_CL_UCP_0_W = 0x172;
constexpr uint32_t PA_CL_UCP_5_W = 0x186;
constexpr uint32_t SPI_PS_INPUT_CNTL_0 = 0x191;
constexpr uint32_t SPI_PS_INPUT_CNTL_31 = 0x1B0;
constexpr uint32_t SPI_VS_OUT_CONFIG = 0x1B1;
@@ -386,9 +387,6 @@ constexpr uint32_t SPI_SHADER_POS_FORMAT = 0x1C3;
constexpr uint32_t SPI_SHADER_Z_FORMAT = 0x1C4;
constexpr uint32_t SPI_SHADER_COL_FORMAT = 0x1C5;
// Indirect Cx descriptor selector for the 32-entry PS input-control register bank.
constexpr uint32_t CX_PS_SHADER_USAGE_BASE = 0x10000000u;
constexpr uint32_t CB_BLEND0_CONTROL = 0x1E0;
constexpr uint32_t CB_BLEND0_CONTROL_COLOR_SRCBLEND_SHIFT = 0;
constexpr uint32_t CB_BLEND0_CONTROL_COLOR_SRCBLEND_MASK = 0x1F;
@@ -983,8 +981,16 @@ constexpr uint32_t COMPUTE_PGM_RSRC1_FP16_OVFL_SHIFT = 26;
constexpr uint32_t COMPUTE_PGM_RSRC1_FP16_OVFL_MASK = 0x1;
constexpr uint32_t COMPUTE_PGM_RSRC1_WGP_MODE_SHIFT = 29;
constexpr uint32_t COMPUTE_PGM_RSRC1_WGP_MODE_MASK = 0x1;
constexpr uint32_t COMPUTE_PGM_RSRC1_W32_EN_SHIFT = 30;
constexpr uint32_t COMPUTE_PGM_RSRC1_W32_EN_MASK = 0x1;
constexpr uint32_t COMPUTE_DISPATCH_INITIATOR_CS_W32_EN_SHIFT = 15;
constexpr uint32_t COMPUTE_DISPATCH_INITIATOR_CS_W32_EN_MASK = 0x1;
[[nodiscard]] constexpr uint8_t ComputeWaveSize(uint32_t dispatch_initiator) {
return ((dispatch_initiator >> COMPUTE_DISPATCH_INITIATOR_CS_W32_EN_SHIFT) &
COMPUTE_DISPATCH_INITIATOR_CS_W32_EN_MASK) != 0u
? 32u
: 64u;
}
constexpr uint32_t COMPUTE_PGM_RSRC2 = 0x213;
constexpr uint32_t COMPUTE_PGM_RSRC2_SCRATCH_EN_SHIFT = 0;
@@ -1047,6 +1053,7 @@ constexpr uint32_t GE_USER_VGPR1 = 0x25C;
constexpr uint32_t GE_USER_VGPR2 = 0x25D;
constexpr uint32_t GE_USER_VGPR3 = 0x25E;
constexpr uint32_t GE_STEREO_CNTL = 0x25F;
constexpr uint32_t GE_PC_ALLOC = 0x260;
constexpr uint32_t GE_USER_VGPR_EN = 0x262;
constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR1_SHIFT = 0;
@@ -1081,7 +1088,7 @@ constexpr uint32_t UC_NOP = 0x80003FFF;
constexpr uint32_t UC_NUM = 0x3FFF + 1;
void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t start_dw,
void DumpPm4PacketStream(Common::File* file, const uint32_t* cmd_buffer, uint32_t start_dw,
uint32_t num_dw);
} // namespace Libs::Graphics::Pm4
+39 -35
View File
@@ -265,7 +265,7 @@ static bool GetMipTailLayout(const TileBlockLayout& block, MipTailLayout& out) {
return false;
}
bool TileGetTextureElementLayout(uint32_t format, TileTextureElementLayout& out) {
bool TileGetTextureElementLayout(Prospero::BufferFormat format, TileTextureElementLayout& out) {
if (const auto bytes = Prospero::NumBytesPerElement(format); std::has_single_bit(bytes)) {
out = {bytes, 1, 1};
return true;
@@ -277,10 +277,10 @@ bool TileGetTextureElementLayout(uint32_t format, TileTextureElementLayout& out)
return false;
}
bool TileGetTextureBlockLayout(uint32_t format, uint32_t tile, bool volume,
bool TileGetTextureBlockLayout(Prospero::BufferFormat format, Prospero::TileMode tile, bool volume,
TileTextureBlockLayout& out) {
TileBlockFamily family = TileBlockFamily::Count;
switch (static_cast<Prospero::TileMode>(tile)) {
switch (tile) {
case Prospero::TileMode::kStandard256B:
if (!volume) family = TileBlockFamily::Standard256B;
break;
@@ -1130,15 +1130,18 @@ static bool TileGetHtileSize(uint32_t width, uint32_t height, TileSizeAlign& hti
return true;
}
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t z_format,
uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size,
TileSizeAlign& htile_size, TileSizeAlign& depth_size,
uint32_t num_fragments_log2) {
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch,
Prospero::DepthFormat z_format, Prospero::StencilFormat stencil_format,
bool htile, TileSizeAlign& stencil_size, TileSizeAlign& htile_size,
TileSizeAlign& depth_size, uint32_t num_fragments_log2) {
EXIT_IF(pitch != 0);
// Prospero derives uncompressed depth/stencil as independent 64 KiB block surfaces.
if (width > 0 && width <= 16384 && height > 0 && height <= 16384 &&
(z_format == 1 || z_format == 3) && stencil_format <= 1 && num_fragments_log2 <= 3) {
const uint32_t depth_bytes = z_format == 1 ? 2u : 4u;
(z_format == Prospero::DepthFormat::kZ16 || z_format == Prospero::DepthFormat::kZ32F) &&
(stencil_format == Prospero::StencilFormat::kInvalid ||
stencil_format == Prospero::StencilFormat::k8UInt) &&
num_fragments_log2 <= 3) {
const uint32_t depth_bytes = z_format == Prospero::DepthFormat::kZ16 ? 2u : 4u;
uint32_t depth_block_width = 0;
uint32_t depth_block_height = 0;
uint32_t stencil_block_width = 0;
@@ -1146,7 +1149,7 @@ bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t
const bool valid_blocks =
Gen5Msaa64KBBlockSizeFromElementBytes(depth_bytes, num_fragments_log2,
&depth_block_width, &depth_block_height) &&
(stencil_format == 0 ||
(stencil_format == Prospero::StencilFormat::kInvalid ||
Gen5Msaa64KBBlockSizeFromElementBytes(1, num_fragments_log2, &stencil_block_width,
&stencil_block_height));
const uint32_t fragments = 1u << num_fragments_log2;
@@ -1155,7 +1158,7 @@ bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t
AlignUp(height, depth_block_height) * depth_bytes * fragments
: 0;
const uint64_t stencil_bytes_total =
stencil_format == 1 && valid_blocks
stencil_format == Prospero::StencilFormat::k8UInt && valid_blocks
? static_cast<uint64_t>(AlignUp(width, stencil_block_width)) *
AlignUp(height, stencil_block_height) * fragments
: 0;
@@ -1163,7 +1166,7 @@ bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t
const bool htile_valid = !htile || TileGetHtileSize(width, height, calculated_htile);
if (depth_bytes_total <= UINT32_MAX && stencil_bytes_total <= UINT32_MAX && htile_valid) {
depth_size = {static_cast<uint32_t>(depth_bytes_total), 65536};
stencil_size = stencil_format == 1
stencil_size = stencil_format == Prospero::StencilFormat::k8UInt
? TileSizeAlign {static_cast<uint32_t>(stencil_bytes_total), 65536}
: TileSizeAlign {};
htile_size = calculated_htile;
@@ -1236,31 +1239,29 @@ bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitc
if (levels > max_levels) {
return false;
}
uint32_t format = 0;
auto format = Prospero::BufferFormat::kInvalid;
switch (bytes_per_element) {
case 1: format = Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm); break;
case 2: format = Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm); break;
case 4: format = Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float); break;
case 8: format = Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float); break;
case 16: format = Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32Float); break;
case 1: format = Prospero::BufferFormat::k8UNorm; break;
case 2: format = Prospero::BufferFormat::k16UNorm; break;
case 4: format = Prospero::BufferFormat::k32Float; break;
case 8: format = Prospero::BufferFormat::k16_16_16_16Float; break;
case 16: format = Prospero::BufferFormat::k32_32_32_32Float; break;
default: return false;
}
TileGetTextureSize(format, width, height, levels,
Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget), &total_size,
level_sizes, padded_size);
TileGetTextureSize(format, width, height, levels, Prospero::TileMode::kRenderTarget,
&total_size, level_sizes, padded_size);
return total_size.size != 0 && total_size.align == 65536;
}
void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32_t levels,
uint32_t tile, TileSizeAlign* total_size, TileSizeOffset* level_sizes,
TilePaddedSize* padded_size) {
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
uint32_t levels, Prospero::TileMode tile, TileSizeAlign* total_size,
TileSizeOffset* level_sizes, TilePaddedSize* padded_size) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(levels == 0 || levels > 16);
TileTextureElementLayout element {};
if (static_cast<Prospero::TileMode>(tile) == Prospero::TileMode::kLinear &&
TileGetTextureElementLayout(format, element)) {
if (tile == Prospero::TileMode::kLinear && TileGetTextureElementLayout(format, element)) {
uint32_t mip_pitch[16] {};
uint32_t mip_height[16] {};
uint32_t mip_size[16] {};
@@ -1302,26 +1303,27 @@ void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32
}
if (total_size != nullptr && total_size->size == 0) {
std::vector<std::string> list;
list.push_back(fmt::format("format = {}", format));
list.push_back(fmt::format("format = {}", static_cast<uint32_t>(format)));
list.push_back(fmt::format("width = {}", width));
list.push_back(fmt::format("height = {}", height));
list.push_back(fmt::format("levels = {}", levels));
list.push_back(fmt::format("tile = {}", tile));
list.push_back(fmt::format("tile = {}", static_cast<uint32_t>(tile)));
EXIT("unknown format:\n%s\n", Common::Concat(list, '\n').c_str());
}
}
void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
uint32_t levels, uint32_t tile, bool volume_texture,
TileSizeAlign& total_size) {
void TileGetTextureTotalSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
uint32_t depth, uint32_t levels, Prospero::TileMode tile,
bool volume_texture, TileSizeAlign& total_size) {
EXIT_NOT_IMPLEMENTED(depth == 0);
if (volume_texture && static_cast<Prospero::TileMode>(tile) != Prospero::TileMode::kLinear) {
if (volume_texture && tile != Prospero::TileMode::kLinear) {
TileSurfaceLayout layout {};
const TileSurfaceDescription description {
format, tile, TileSurfaceDimension::Dim3D, width, height, depth, levels, 1};
if (!TileGetTiledTextureLayout(description, layout)) {
EXIT("unsupported 3D texture layout: format=%u tile=%u extent=%ux%ux%u levels=%u\n",
format, tile, width, height, depth, levels);
static_cast<uint32_t>(format), static_cast<uint32_t>(tile), width, height, depth,
levels);
}
EXIT_NOT_IMPLEMENTED(layout.total_size > UINT32_MAX);
total_size.size = static_cast<uint32_t>(layout.total_size);
@@ -1337,9 +1339,10 @@ void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, u
total_size.size = static_cast<uint32_t>(total);
}
uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t tile) {
uint32_t TileGetTexturePitch(Prospero::BufferFormat format, uint32_t width,
Prospero::TileMode tile) {
uint32_t pitch = width;
switch (static_cast<Prospero::TileMode>(tile)) {
switch (tile) {
case Prospero::TileMode::kLinear: {
TileTextureElementLayout element {};
if (TileGetTextureElementLayout(format, element) && element.texel_width == 1 &&
@@ -1348,6 +1351,7 @@ uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t tile) {
}
break;
}
case Prospero::TileMode::kStandard4KB:
case Prospero::TileMode::kStandard64KB:
case Prospero::TileMode::kPrt:
case Prospero::TileMode::kDepth:
+23 -21
View File
@@ -3,6 +3,7 @@
#include "common/abi.h"
#include "common/common.h"
#include "graphics/guest_gpu/gpu_defs.h"
namespace Libs::Graphics {
@@ -65,14 +66,14 @@ enum class TileSurfaceDimension : uint32_t {
};
struct TileSurfaceDescription {
uint32_t format = 0;
uint32_t tile_mode = 0;
TileSurfaceDimension dimension = TileSurfaceDimension::Dim2D;
uint32_t width = 0;
uint32_t height = 0;
uint32_t depth = 1;
uint32_t levels = 1;
uint32_t layers = 1;
Prospero::BufferFormat format = Prospero::BufferFormat::kInvalid;
Prospero::TileMode tile_mode = Prospero::TileMode::kLinear;
TileSurfaceDimension dimension = TileSurfaceDimension::Dim2D;
uint32_t width = 0;
uint32_t height = 0;
uint32_t depth = 1;
uint32_t levels = 1;
uint32_t layers = 1;
};
// Width/height fields are measured in addressable elements, not texture texels.
@@ -98,8 +99,8 @@ struct TileSurfaceLayout {
bool TileGetBlockLayout(TileBlockFamily family, uint32_t bytes_per_element,
TileBlockLayout& layout);
bool TileGetTextureElementLayout(uint32_t format, TileTextureElementLayout& layout);
bool TileGetTextureBlockLayout(uint32_t format, uint32_t tile, bool volume,
bool TileGetTextureElementLayout(Prospero::BufferFormat format, TileTextureElementLayout& layout);
bool TileGetTextureBlockLayout(Prospero::BufferFormat format, Prospero::TileMode tile, bool volume,
TileTextureBlockLayout& layout);
bool TileGetTiledTextureLayout(const TileSurfaceDescription& description,
TileSurfaceLayout& layout);
@@ -110,10 +111,10 @@ bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t b
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
uint32_t block_z, uint32_t& byte_offset);
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t z_format,
uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size,
TileSizeAlign& htile_size, TileSizeAlign& depth_size,
uint32_t num_fragments_log2 = 0);
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch,
Prospero::DepthFormat z_format, Prospero::StencilFormat stencil_format,
bool htile, TileSizeAlign& stencil_size, TileSizeAlign& htile_size,
TileSizeAlign& depth_size, uint32_t num_fragments_log2 = 0);
uint32_t TileGetRenderTargetPitch(uint32_t width, uint32_t bytes_per_element,
uint32_t num_fragments_log2 = 0);
uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element,
@@ -125,13 +126,14 @@ bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t
uint32_t bytes_per_element, uint32_t levels,
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
TilePaddedSize* padded_size);
void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32_t levels,
uint32_t tile, TileSizeAlign* total_size, TileSizeOffset* level_sizes,
TilePaddedSize* padded_size);
void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
uint32_t levels, uint32_t tile, bool volume_texture,
TileSizeAlign& total_size);
uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t tile);
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
uint32_t levels, Prospero::TileMode tile, TileSizeAlign* total_size,
TileSizeOffset* level_sizes, TilePaddedSize* padded_size);
void TileGetTextureTotalSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
uint32_t depth, uint32_t levels, Prospero::TileMode tile,
bool volume_texture, TileSizeAlign& total_size);
uint32_t TileGetTexturePitch(Prospero::BufferFormat format, uint32_t width,
Prospero::TileMode tile);
} // namespace Libs::Graphics
+80 -4
View File
@@ -3,20 +3,88 @@
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/vulkanCommon.h" // IWYU pragma: export
#include "graphics/host_gpu/vulkanInstance.h"
#include <map>
#include <mutex>
#include <tuple>
#include <vector>
#include <vk_mem_alloc.h>
namespace Libs::Graphics {
class CommandBuffer;
struct VulkanBuffer;
struct VulkanImage;
struct VulkanMemory;
struct GraphicContext: public VulkanInstance {
inline constexpr uint32_t VULKAN_TARGET_API_VERSION = VK_API_VERSION_1_3;
struct GraphicContext {
vk::Instance instance = nullptr;
vk::DebugUtilsMessengerEXT debug_messenger = nullptr;
vk::PhysicalDevice physical_device = nullptr;
vk::PhysicalDeviceProperties physical_device_properties = {};
vk::PhysicalDeviceMemoryProperties physical_device_memory_properties = {};
vk::Device device = nullptr;
VmaAllocator allocator = nullptr;
bool memory_budget_ext_enabled = false;
bool rt_extensions_enabled = false;
bool compute_subgroup_size_control_enabled = false;
bool compute_wave64_supported = false;
bool sample_rate_shading_enabled = false;
uint32_t subgroup_size = 0;
uint32_t min_subgroup_size = 0;
uint32_t max_subgroup_size = 0;
uint32_t max_push_descriptors = 0;
vk::ShaderStageFlags required_subgroup_size_stages = {};
Common::Mutex queue_mutex;
uint32_t queue_family = static_cast<uint32_t>(-1);
vk::Queue queue = nullptr;
[[nodiscard]] const vk::PhysicalDeviceProperties& GetPhysicalDeviceProperties() const {
return physical_device_properties;
}
[[nodiscard]] const vk::PhysicalDeviceMemoryProperties&
GetPhysicalDeviceMemoryProperties() const {
return physical_device_memory_properties;
}
[[nodiscard]] vk::FormatProperties GetFormatProperties(vk::Format format) const {
std::scoped_lock lock(m_format_properties_mutex);
auto [it, inserted] = m_format_properties.try_emplace(format);
if (inserted) {
physical_device.getFormatProperties(format, &it->second);
}
return it->second;
}
[[nodiscard]] vk::Result GetImageFormatProperties(vk::Format format, vk::ImageType type,
vk::ImageTiling tiling,
vk::ImageUsageFlags usage,
vk::ImageCreateFlags flags,
vk::ImageFormatProperties* properties) const {
using Key = std::tuple<vk::Format, vk::ImageType, vk::ImageTiling, vk::ImageUsageFlags,
vk::ImageCreateFlags>;
std::scoped_lock lock(m_image_format_properties_mutex);
auto [it, inserted] =
m_image_format_properties.try_emplace(Key {format, type, tiling, usage, flags});
if (inserted) {
it->second.first = physical_device.getImageFormatProperties(format, type, tiling, usage,
flags, &it->second.second);
}
if (properties != nullptr) {
*properties = it->second.second;
}
return it->second.first;
}
[[nodiscard]] vk::DeviceSize StorageMinAlignment() const {
const auto alignment = physical_device_properties.limits.minStorageBufferOffsetAlignment;
return alignment != 0 ? alignment : 1;
}
[[nodiscard]] bool CreateAllocator();
void DestroyAllocator();
void LogMemoryBudget() const;
@@ -24,7 +92,6 @@ struct GraphicContext: public VulkanInstance {
[[nodiscard]] uint64_t GetDeviceMemoryUsage() const;
[[nodiscard]] uint64_t GetTotalMemoryBudget() const;
void CreateBuffer(uint64_t size, VulkanBuffer& buffer);
void DeleteBuffer(VulkanBuffer& buffer);
[[nodiscard]] bool CreateImage(const vk::ImageCreateInfo& info, VulkanImage& image);
void DeleteImage(VulkanImage& image);
void MapMemory(VulkanMemory& memory, void*& data);
@@ -36,6 +103,15 @@ struct GraphicContext: public VulkanInstance {
uint32_t screen_width = 0;
uint32_t screen_height = 0;
private:
mutable std::mutex m_format_properties_mutex;
mutable std::map<vk::Format, vk::FormatProperties> m_format_properties;
mutable std::mutex m_image_format_properties_mutex;
mutable std::map<std::tuple<vk::Format, vk::ImageType, vk::ImageTiling, vk::ImageUsageFlags,
vk::ImageCreateFlags>,
std::pair<vk::Result, vk::ImageFormatProperties>>
m_image_format_properties;
};
struct VulkanMemory {
+9 -8
View File
@@ -1,5 +1,6 @@
#include "graphics/host_gpu/pageManager.h"
#include "common/platform/sysDbg.h"
#include "graphics/host_gpu/regionDefinitions.h"
#include "kernel/memory.h"
@@ -52,20 +53,20 @@ constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
std::fputs("PageManager fail-fast: ", stderr);
std::fputs(reason != nullptr ? reason : "invalid page state", stderr);
std::fputc('\n', stderr);
#if !defined(__APPLE__)
void* frames[16] {};
int frame_count = static_cast<int>(std::size(frames));
SysStackWalk(frames, &frame_count);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
void* frames[16] {};
const auto frame_count =
CaptureStackBackTrace(0, static_cast<DWORD>(std::size(frames)), frames, nullptr);
const auto image_base = reinterpret_cast<uintptr_t>(GetModuleHandleW(nullptr));
for (uint16_t i = 0; i < frame_count; i++) {
for (int i = 0; i < frame_count; i++) {
const auto address = reinterpret_cast<uintptr_t>(frames[i]);
std::fprintf(stderr, " frame[%u]=0x%016" PRIxPTR " image_rva=0x%016" PRIxPTR "\n", i,
std::fprintf(stderr, " frame[%d]=0x%016" PRIxPTR " image_rva=0x%016" PRIxPTR "\n", i,
address, address >= image_base ? address - image_base : 0);
}
#elif !defined(__APPLE__)
void* frames[16] {};
const int frame_count = ::backtrace(frames, static_cast<int>(std::size(frames)));
#else
::backtrace_symbols_fd(frames, frame_count, STDERR_FILENO);
#endif
#endif
std::fflush(stderr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
+19 -1
View File
@@ -53,12 +53,30 @@ public:
}
}
void Clear() { m_ranges.clear(); }
template <typename Func>
void ForEach(Func&& func) const {
for (const auto& [begin, end]: m_ranges) {
func(begin, end);
}
}
[[nodiscard]] std::vector<Range> Intersections(uint64_t address, uint64_t size) const {
std::vector<Range> result;
ForEachIntersection(address, size, [&result](Range range) { result.push_back(range); });
return result;
}
[[nodiscard]] bool Intersects(uint64_t address, uint64_t size) const {
const auto end = End(address, size);
auto it = m_ranges.lower_bound(address);
if (it != m_ranges.begin() && std::prev(it)->second > address) {
return true;
}
return it != m_ranges.end() && it->first < end;
}
[[nodiscard]] bool Contains(uint64_t address, uint64_t size) const {
const auto end = End(address, size);
auto it = m_ranges.upper_bound(address);
@@ -89,7 +107,7 @@ public:
private:
static uint64_t End(uint64_t address, uint64_t size) {
if (address == 0 || size == 0 || size > UINT64_MAX - address) {
if (size == 0 || size > UINT64_MAX - address) {
EXIT("invalid range-set address or size\n");
}
return address + size;
File diff suppressed because it is too large. Load diff
+60 -54
View File
@@ -3,13 +3,15 @@
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "common/lruCache.h"
#include "common/slotVector.h"
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/cache/faultManager.h"
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include <map>
#include <memory>
#include <span>
#include <utility>
#include <vector>
@@ -17,91 +19,95 @@
namespace Libs::Graphics {
struct GraphicContext;
class CommandBuffer;
class CommandScheduler;
class TextureCache;
struct BufferBinding {
std::shared_ptr<void> owner;
vk::Buffer buffer = nullptr;
uint64_t offset = 0;
};
struct ImageBufferSource {
Buffer* buffer = nullptr;
uint64_t offset = 0;
};
using BufferId = Common::SlotId;
inline constexpr BufferId NULL_BUFFER_ID {0};
class BufferCache {
public:
static constexpr uint64_t CACHING_PAGE_SIZE = 16ull * 1024ull;
static constexpr uint64_t GetBufferOffset(uint64_t vaddr) {
return vaddr & (CACHING_PAGE_SIZE - 1);
}
static constexpr uint32_t CACHING_PAGEBITS = 14;
static constexpr uint64_t CACHING_PAGESIZE = uint64_t {1} << CACHING_PAGEBITS;
static constexpr uint64_t CACHING_NUMPAGES = uint64_t {1} << (40 - CACHING_PAGEBITS);
static constexpr uint64_t BDA_PAGETABLE_SIZE =
CACHING_NUMPAGES * sizeof(vk::DeviceAddress);
BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
TextureCache& texture_cache);
~BufferCache();
KYTY_CLASS_NO_COPY(BufferCache);
void InvalidateMemory(uint64_t vaddr, uint64_t size);
void ReadMemory(uint64_t vaddr, uint64_t size, bool is_write = false);
void UnmapMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] BufferBinding ObtainBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
bool is_written = false, bool is_read = true,
bool is_formatted = false);
[[nodiscard]] StreamBuffer& GetUtilityBuffer(MemoryUsage usage) noexcept;
[[nodiscard]] Buffer& GetGdsBuffer() noexcept { return m_gds_buffer; }
[[nodiscard]] const Buffer& GetGdsBuffer() const noexcept { return m_gds_buffer; }
[[nodiscard]] BufferBinding UploadTransient(const void* data, uint64_t size,
uint64_t alignment);
[[nodiscard]] std::shared_ptr<Buffer> ObtainNullBuffer();
[[nodiscard]] ImageBufferSource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds = false);
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds = false,
bool src_gds = false);
void InvalidateMemory(uint64_t vaddr, uint64_t size);
void ReadMemory(uint64_t vaddr, uint64_t size, bool is_write = false);
[[nodiscard]] Buffer& GetBuffer(BufferId id) { return m_slot_buffers[id]; }
[[nodiscard]] BufferId FindBuffer(uint64_t vaddr, uint64_t size);
[[nodiscard]] std::pair<Buffer*, uint64_t> ObtainBuffer(uint64_t vaddr, uint64_t size,
bool is_written,
bool is_texel_buffer = false,
BufferId id = {});
[[nodiscard]] StreamBuffer& GetUtilityBuffer(MemoryUsage usage) noexcept {
switch (usage) {
case MemoryUsage::Upload: return m_staging_buffer;
case MemoryUsage::Stream: return m_stream_buffer;
case MemoryUsage::Download: return m_download_buffer;
case MemoryUsage::DeviceLocal: return m_device_buffer;
}
EXIT("BufferCache: invalid utility-buffer usage\n");
}
[[nodiscard]] const Buffer* GetGdsBuffer() const noexcept { return &m_gds_buffer; }
[[nodiscard]] Buffer* GetBdaPageTableBuffer() noexcept { return &m_bda_pagetable_buffer; }
[[nodiscard]] Buffer* GetFaultBuffer() noexcept { return m_fault_manager.GetFaultBuffer(); }
[[nodiscard]] std::pair<Buffer*, uint64_t> ObtainBufferForImage(uint64_t vaddr, uint64_t size);
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds);
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds,
bool src_gds);
// Cache-index and exact dirty-range queries require GPU-thread serialization.
[[nodiscard]] bool IsRegionRegistered(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
void ProcessFaultBuffer();
void SynchronizeBuffersInRange(uint64_t vaddr, uint64_t size);
void RunGarbageCollector();
private:
friend struct BufferCacheTestAccess;
struct CacheRange {
uint64_t address = 0;
uint64_t size = 0;
};
struct CachedBuffer;
struct DownloadCopy;
struct DownloadRange;
struct RetiredBuffer;
using PageTable = MultiLevelPageTable<BufferId, CACHING_PAGEBITS, 40, 16>;
static_assert(CACHING_PAGESIZE == (uint64_t {1} << PageTable::kPageBits));
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
[[nodiscard]] static uint64_t AlignDown(uint64_t value) noexcept;
[[nodiscard]] static uint64_t AlignUp(uint64_t value);
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
}
[[nodiscard]] static std::pair<uint64_t, uint64_t> DownloadEnvelope(const DownloadCopy& copy);
[[nodiscard]] static bool ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept;
void Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset,
const void* source, uint64_t size);
[[nodiscard]] CachedBuffer& GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr,
uint64_t size);
void WriteDataBuffer(Buffer& buffer, uint64_t address, const void* source, uint64_t size);
void TouchBuffer(const Buffer& buffer);
[[nodiscard]] BufferId CreateBuffer(uint64_t vaddr, uint64_t size);
void Register(BufferId id);
void Unregister(BufferId id);
template <bool insert>
void ChangeRegister(BufferId id);
void DeleteBuffer(BufferId id);
[[nodiscard]] bool SynchronizeBuffer(Buffer& buffer, uint64_t vaddr, uint64_t size,
bool is_written, bool is_texel_buffer);
[[nodiscard]] vk::Buffer UploadCopies(Buffer& buffer, std::span<vk::BufferCopy> copies,
uint64_t total_size);
[[nodiscard]] bool SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size);
[[nodiscard]] std::vector<DownloadRange> RecordDownloads(std::span<const DownloadCopy> copies);
void PublishDownloads(std::span<const DownloadRange> downloads);
void QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire);
void DownloadBufferMemory(std::span<const DownloadCopy> copies);
void WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data);
void ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
FaultManager m_fault_manager;
Buffer m_gds_buffer;
Common::Mutex m_mutex;
std::shared_ptr<Buffer> m_null_buffer;
std::map<uint64_t, std::unique_ptr<CachedBuffer>> m_buffers;
Buffer m_bda_pagetable_buffer;
Common::SlotVector<Buffer> m_slot_buffers;
Common::LeastRecentlyUsedCache<BufferId, uint64_t> m_lru_cache;
std::map<uint64_t, BufferId> m_buffers;
PageTable m_page_table;
RangeSet m_gpu_modified_ranges;
MemoryTracker m_memory_tracker;
StreamBuffer m_staging_buffer;
@@ -109,7 +115,7 @@ private:
StreamBuffer m_download_buffer;
StreamBuffer m_device_buffer;
TextureCache& m_texture_cache;
uint64_t m_total_used_memory = 0;
uint64_t m_total_used_memory = 0;
uint64_t m_trigger_gc_memory = 1ull * 1024 * 1024 * 1024;
uint64_t m_critical_gc_memory = 2ull * 1024 * 1024 * 1024;
uint64_t m_gc_tick = 0;
+167
View File
@@ -0,0 +1,167 @@
#include "graphics/host_gpu/renderer/cache/faultManager.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "gpu_tiler_shaders/fault_buffer_process_spv.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <bit>
#include <cinttypes>
#include <cstring>
#include <limits>
namespace Libs::Graphics {
namespace {
constexpr size_t MaxPageFaults = 1024;
constexpr size_t PageFaultAreaSize = MaxPageFaults * sizeof(uint64_t);
} // namespace
FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler,
BufferCache& buffer_cache, uint32_t caching_pagebits,
uint64_t caching_num_pages)
: m_graphics(graphics), m_scheduler(scheduler), m_buffer_cache(buffer_cache),
m_caching_pagesize(uint64_t {1} << caching_pagebits),
m_caching_num_pages(caching_num_pages), m_fault_buffer_size(caching_num_pages / 8),
m_fault_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
m_fault_buffer_size),
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 0, AllFlags,
MaxPendingFaults * PageFaultAreaSize) {
SetVulkanObjectNameF(m_graphics.device, m_fault_buffer.Handle(), "Fault Buffer");
const vk::DescriptorSetLayoutBinding bindings[] {
{0, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr},
{1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr},
};
vk::DescriptorSetLayoutCreateInfo layout_info {};
layout_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
layout_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
layout_info.bindingCount = std::size(bindings);
layout_info.pBindings = bindings;
RequireVulkanSuccess(
m_graphics.device.createDescriptorSetLayout(&layout_info, nullptr,
&m_fault_process_desc_layout),
"create fault-buffer descriptor layout");
vk::ShaderModuleCreateInfo module_info {};
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
module_info.codeSize = std::size(FAULT_BUFFER_PROCESS_SPV) * sizeof(uint32_t);
module_info.pCode = FAULT_BUFFER_PROCESS_SPV;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create fault-buffer shader module");
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
pipeline_layout_info.setLayoutCount = 1;
pipeline_layout_info.pSetLayouts = &m_fault_process_desc_layout;
RequireVulkanSuccess(
m_graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&m_fault_process_pipeline_layout),
"create fault-buffer pipeline layout");
vk::PipelineShaderStageCreateInfo stage {};
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
stage.pName = "main";
vk::ComputePipelineCreateInfo pipeline_info {};
pipeline_info.sType = vk::StructureType::eComputePipelineCreateInfo;
pipeline_info.stage = stage;
pipeline_info.layout = m_fault_process_pipeline_layout;
const auto result = m_graphics.device.createComputePipelines(
nullptr, 1, &pipeline_info, nullptr, &m_fault_process_pipeline);
m_graphics.device.destroyShaderModule(module, nullptr);
RequireVulkanSuccess(result, "create fault-buffer pipeline");
SetVulkanObjectNameF(m_graphics.device, m_fault_process_pipeline, "Fault Buffer Parser");
}
FaultManager::~FaultManager() {
m_graphics.device.destroyPipeline(m_fault_process_pipeline, nullptr);
m_graphics.device.destroyPipelineLayout(m_fault_process_pipeline_layout, nullptr);
m_graphics.device.destroyDescriptorSetLayout(m_fault_process_desc_layout, nullptr);
}
void FaultManager::ProcessFaultBuffer() {
if (const auto wait_tick = m_fault_areas[m_current_area]; wait_tick != 0) {
m_scheduler.Wait(wait_tick);
m_scheduler.PopPendingOperations();
}
const auto offset = m_current_area * PageFaultAreaSize;
auto* mapped = m_download_buffer.Mapped().data() + offset;
std::memset(mapped, 0, PageFaultAreaSize);
m_download_buffer.Flush(offset, PageFaultAreaSize);
vk::BufferMemoryBarrier2 pre_barrier {};
pre_barrier.sType = vk::StructureType::eBufferMemoryBarrier2;
pre_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
pre_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
pre_barrier.dstStageMask = vk::PipelineStageFlagBits2::eComputeShader;
pre_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead;
pre_barrier.buffer = m_fault_buffer.Handle();
pre_barrier.offset = 0;
pre_barrier.size = m_fault_buffer_size;
auto post_barrier = pre_barrier;
post_barrier.srcStageMask = vk::PipelineStageFlagBits2::eComputeShader;
post_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
post_barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
post_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderWrite;
const vk::DescriptorBufferInfo infos[] {
{m_fault_buffer.Handle(), 0, m_fault_buffer_size},
{m_download_buffer.Handle(), offset, PageFaultAreaSize},
};
std::array<vk::WriteDescriptorSet, 2> writes {};
for (uint32_t index = 0; index < writes.size(); ++index) {
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
writes[index].dstBinding = index;
writes[index].descriptorCount = 1;
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
writes[index].pBufferInfo = &infos[index];
}
m_scheduler.EndRendering();
auto command = m_scheduler.Current().Handle();
vk::DependencyInfo dependency {};
dependency.sType = vk::StructureType::eDependencyInfo;
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &pre_barrier;
command.pipelineBarrier2(dependency);
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_fault_process_pipeline);
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute,
m_fault_process_pipeline_layout, 0, writes);
const auto num_threads = m_caching_num_pages / 32;
const auto num_workgroups = (num_threads + 63) / 64;
command.dispatch(static_cast<uint32_t>(num_workgroups), 1, 1);
dependency.pBufferMemoryBarriers = &post_barrier;
command.pipelineBarrier2(dependency);
const auto area = m_current_area;
m_scheduler.DeferOperation([this, mapped, offset, area] {
m_download_buffer.Invalidate(offset, PageFaultAreaSize);
m_fault_ranges.Clear();
const auto* faults = std::bit_cast<const uint64_t*>(mapped);
const auto count = static_cast<uint32_t>(faults[0]);
for (uint32_t index = 1; index <= count; ++index) {
m_fault_ranges.Add(faults[index], m_caching_pagesize);
LOGF("Accessed non-GPU cached memory at 0x%016" PRIx64 "\n", faults[index]);
}
m_fault_ranges.ForEach([this](uint64_t start, uint64_t end) {
EXIT_IF(end - start > std::numeric_limits<uint32_t>::max());
(void)m_buffer_cache.FindBuffer(start, end - start);
});
m_fault_areas[area] = 0;
});
m_fault_areas[m_current_area++] = m_scheduler.CurrentTick();
m_current_area %= MaxPendingFaults;
}
} // namespace Libs::Graphics
+46
View File
@@ -0,0 +1,46 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
#include "common/abi.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include <array>
#include <cstdint>
namespace Libs::Graphics {
class BufferCache;
class FaultManager {
static constexpr size_t MaxPendingFaults = 8;
public:
FaultManager(GraphicContext& graphics, CommandScheduler& scheduler, BufferCache& buffer_cache,
uint32_t caching_pagebits, uint64_t caching_num_pages);
~FaultManager();
KYTY_CLASS_NO_COPY(FaultManager);
[[nodiscard]] Buffer* GetFaultBuffer() noexcept { return &m_fault_buffer; }
void ProcessFaultBuffer();
private:
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
BufferCache& m_buffer_cache;
RangeSet m_fault_ranges;
uint64_t m_caching_pagesize;
uint64_t m_caching_num_pages;
uint64_t m_fault_buffer_size;
Buffer m_fault_buffer;
Buffer m_download_buffer;
std::array<uint64_t, MaxPendingFaults> m_fault_areas {};
uint32_t m_current_area = 0;
vk::DescriptorSetLayout m_fault_process_desc_layout = nullptr;
vk::Pipeline m_fault_process_pipeline = nullptr;
vk::PipelineLayout m_fault_process_pipeline_layout = nullptr;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
+14 -2
View File
@@ -60,10 +60,10 @@ void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
const auto unmap = [this, vaddr, size] {
if (m_scheduler.Active()) {
const auto tick = m_scheduler.CurrentTick();
m_scheduler.FinishCurrent();
m_scheduler.Finish();
m_scheduler.WaitPriorityOperations(tick);
}
m_buffer_cache.UnmapMemory(vaddr, size);
m_buffer_cache.InvalidateMemory(vaddr, size);
m_texture_cache.UnmapMemory(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size);
std::lock_guard lock(m_mapped_ranges_mutex);
@@ -76,7 +76,19 @@ void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
m_gpu->SendCommandSync(unmap);
}
void GpuResourceManager::PrepareBda() {
std::shared_lock lock(m_mapped_ranges_mutex);
m_mapped_ranges.ForEach([this](uint64_t start, uint64_t end) {
m_buffer_cache.SynchronizeBuffersInRange(start, end - start);
});
m_fault_process_pending = true;
}
void GpuResourceManager::RunGarbageCollector() {
if (m_fault_process_pending) {
m_fault_process_pending = false;
m_buffer_cache.ProcessFaultBuffer();
}
m_texture_cache.ProcessDownloadImages();
m_texture_cache.RunGarbageCollector();
m_buffer_cache.RunGarbageCollector();
+5 -3
View File
@@ -13,7 +13,7 @@
namespace Libs::Graphics {
class CommandScheduler;
class Gpu;
class GuestGpu;
class GpuResourceManager {
public:
@@ -23,13 +23,14 @@ public:
[[nodiscard]] BufferCache& GetBufferCache() { return m_buffer_cache; }
[[nodiscard]] TextureCache& GetTextureCache() { return m_texture_cache; }
void SetGpu(Gpu* gpu) noexcept { m_gpu = gpu; }
void SetGpu(GuestGpu* gpu) noexcept { m_gpu = gpu; }
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
[[nodiscard]] bool InvalidateMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
void MapMemory(uint64_t vaddr, uint64_t size);
void UnmapMemory(uint64_t vaddr, uint64_t size);
void PrepareBda();
void RunGarbageCollector();
private:
@@ -39,7 +40,8 @@ private:
TextureCache m_texture_cache;
mutable std::shared_mutex m_mapped_ranges_mutex;
RangeSet m_mapped_ranges;
Gpu* m_gpu = nullptr;
GuestGpu* m_gpu = nullptr;
bool m_fault_process_pending = false;
};
} // namespace Libs::Graphics
+26 -11
View File
@@ -73,8 +73,12 @@ Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsag
buffer_info.usage = flags;
buffer_info.sharingMode = vk::SharingMode::eExclusive;
const bool with_bda = bool(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress);
const VmaAllocationCreateFlags bda_flag =
with_bda ? VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT : 0;
VmaAllocationCreateInfo allocation_info {};
allocation_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | AllocationFlags(usage);
allocation_info.flags =
VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | bda_flag | AllocationFlags(usage);
allocation_info.usage = AllocationUsage(usage);
allocation_info.preferredFlags = usage == MemoryUsage::DeviceLocal
? VkMemoryPropertyFlags {}
@@ -99,6 +103,13 @@ Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsag
m_buffer->memory.type = allocation_result.memoryType;
m_buffer->memory.unique_id = VulkanNextMemoryUniqueId();
graphics.device.getBufferMemoryRequirements(m_buffer->buffer, &m_buffer->memory.requirements);
if (static_cast<bool>(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress)) {
vk::BufferDeviceAddressInfo address_info {};
address_info.sType = vk::StructureType::eBufferDeviceAddressInfo;
address_info.buffer = m_buffer->buffer;
m_device_address = graphics.device.getBufferAddress(address_info);
EXIT_IF(m_device_address == 0);
}
VkMemoryPropertyFlags properties = 0;
vmaGetAllocationMemoryProperties(graphics.allocator, m_buffer->memory.allocation, &properties);
@@ -122,6 +133,11 @@ vk::Buffer Buffer::Handle() const noexcept {
return m_buffer->buffer;
}
vk::DeviceAddress Buffer::BufferDeviceAddress() const noexcept {
EXIT_IF(m_device_address == 0);
return m_device_address;
}
bool Buffer::IsInBounds(uint64_t address, uint64_t size) const noexcept {
return address >= m_cpu_address && size <= m_size && address - m_cpu_address <= m_size - size;
}
@@ -141,6 +157,15 @@ void Buffer::Flush(uint64_t offset, uint64_t size) {
}
}
void Buffer::Invalidate(uint64_t offset, uint64_t size) {
EXIT_IF(m_usage != MemoryUsage::Download || offset > m_size || size > m_size - offset);
if (!m_is_coherent && size != 0) {
const auto result =
vmaInvalidateAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
}
vk::BufferMemoryBarrier Buffer::Barrier(uint64_t offset, uint64_t size, vk::AccessFlags source,
vk::AccessFlags destination) const {
if (Handle() == nullptr || size == 0 || offset > m_size || size > m_size - offset) {
@@ -310,16 +335,6 @@ void StreamBuffer::Commit() {
watch.tick = tick;
}
void StreamBuffer::Invalidate(uint64_t offset, uint64_t size) {
EXIT_IF(Usage() != MemoryUsage::Download || offset > Size() || size > Size() - offset);
if (IsCoherent() || size == 0) {
return;
}
const auto result = vmaInvalidateAllocation(Graphics().allocator,
NativeBuffer().memory.allocation, offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
uint64_t StreamBuffer::Copy(const void* source, uint64_t size, uint64_t alignment) {
EXIT_IF(source == nullptr);
const auto [data, offset] = Map(size, alignment);
+7 -3
View File
@@ -48,12 +48,14 @@ public:
[[nodiscard]] bool IsCoherent() const noexcept { return m_is_coherent; }
[[nodiscard]] MemoryUsage Usage() const noexcept { return m_usage; }
[[nodiscard]] uint64_t CpuAddress() const noexcept { return m_cpu_address; }
[[nodiscard]] vk::DeviceAddress BufferDeviceAddress() const noexcept;
[[nodiscard]] uint64_t Offset(uint64_t address) const noexcept {
return address - m_cpu_address;
}
[[nodiscard]] bool IsInBounds(uint64_t address, uint64_t size) const noexcept;
void Write(uint64_t offset, const void* source, uint64_t size);
void Flush(uint64_t offset, uint64_t size);
void Invalidate(uint64_t offset, uint64_t size);
void CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t source_offset,
uint64_t destination_offset, uint64_t size,
vk::AccessFlags source_before = vk::AccessFlagBits::eMemoryWrite,
@@ -65,6 +67,10 @@ public:
vk::AccessFlagBits::eMemoryWrite);
void Fill(uint64_t offset, uint64_t size, uint32_t value);
// BufferCache state lives directly on the resource.
bool is_deleted = false;
size_t lru_id = 0;
protected:
[[nodiscard]] GraphicContext& Graphics() const noexcept { return *m_graphics; }
[[nodiscard]] CommandScheduler& Scheduler() const noexcept { return *m_scheduler; }
@@ -80,6 +86,7 @@ private:
MemoryUsage m_usage = MemoryUsage::DeviceLocal;
uint64_t m_cpu_address = 0;
uint64_t m_size = 0;
vk::DeviceAddress m_device_address = 0;
std::unique_ptr<VulkanBuffer> m_buffer;
std::span<uint8_t> m_mapped;
bool m_is_coherent = false;
@@ -93,9 +100,6 @@ public:
[[nodiscard]] std::pair<uint8_t*, uint64_t> Map(uint64_t size, uint64_t alignment = 0,
bool allow_wait = true);
void Commit();
// Download mappings become visible to the CPU only after their GPU completion tick is free.
// Call this from the scheduler's deferred completion operation before reading Mapped().
void Invalidate(uint64_t offset, uint64_t size);
[[nodiscard]] uint64_t Copy(const void* source, uint64_t size, uint64_t alignment = 0);
private:
File diff suppressed because it is too large. Load diff
+47 -51
View File
@@ -4,19 +4,17 @@
#include "common/abi.h"
#include "common/common.h"
#include "common/lruCache.h"
#include "common/slotVector.h"
#include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/regionManager.h"
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
#include "graphics/host_gpu/renderer/image/blitHelper.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/renderer/image/tiler.h"
#include <compare>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <span>
#include <utility>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace Libs::Graphics {
@@ -27,8 +25,6 @@ class BufferCache;
class CommandBuffer;
class CommandScheduler;
class RenderExecutor;
class StreamBuffer;
class TileManager;
struct TextureCacheTestAccess;
class TextureCache {
@@ -59,20 +55,26 @@ public:
[[nodiscard]] vk::ImageView FindTexture(ImageId id, const ImageDesc& desc);
[[nodiscard]] vk::ImageView FindRenderTarget(ImageId id, const ImageDesc& desc);
[[nodiscard]] vk::ImageView FindDepthTarget(ImageId id, const ImageDesc& desc);
[[nodiscard]] Image& GetImage(ImageId id);
[[nodiscard]] const Image& GetImage(ImageId id) const;
void MarkGpuWritten(ImageId id);
[[nodiscard]] Image& GetImage(ImageId id) {
auto& image = m_slot_images[id];
TouchImage(image);
return image;
}
void MarkGpuWritten(ImageId id);
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size,
uint32_t packed_clear);
void InvalidateMemory(uint64_t address, uint64_t size);
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t address, uint64_t size,
bool formatted_buffer_write = false);
void InvalidateMemoryFromGPU(uint64_t address, uint64_t size);
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
[[nodiscard]] bool IsMeta(uint64_t address);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice,
uint32_t* fill_value = nullptr);
[[nodiscard]] bool ClearMeta(uint64_t address);
// Returns true when registered DCC absorbed the fill and the caller may skip the dispatch.
// False may still record PendingDcc state, but the guest dispatch must execute.
[[nodiscard]] bool TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t fill_value);
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
void UnmapMemory(uint64_t address, uint64_t size);
@@ -84,13 +86,18 @@ private:
struct ColorTransferPlan;
struct DownloadPlan;
struct Slot {
std::shared_ptr<Image> image;
uint32_t generation = 1;
};
struct MetaDataInfo {
// A guest metadata-fill dispatch may initialize DCC before its render target is bound.
// PendingDcc retains that exact fill until FindRenderTarget classifies the address,
// without exposing an unconfirmed buffer address to the normal metadata heuristics.
// Keep all surface metadata in one entry so CMask/FMask can be
// registered beside HTile and DCC without introducing parallel tracking paths.
enum class Type : uint8_t { PendingDcc, CMask, FMask, HTile, Dcc };
Type type = Type::PendingDcc;
uint32_t clear_mask = 0;
uint32_t fill_value = 0xffffffffu;
uint64_t fill_size = 0;
};
struct OverlapResult {
@@ -102,25 +109,21 @@ private:
using ImageIds = InlinePageOwnerList<ImageId, 16>;
using ImagePageTable = MultiLevelPageTable<ImageIds, 20, 40, 10>;
[[nodiscard]] Image& ResolveImage(ImageId id);
[[nodiscard]] const Image& ResolveImage(ImageId id) const;
[[nodiscard]] std::shared_ptr<Image> ResolveOwner(ImageId id) const;
[[nodiscard]] ImageId InsertImage(const ImageInfo& info);
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
void RegisterImage(ImageId id);
void UnregisterImage(ImageId id);
void DeleteImage(ImageId id);
void DeleteImages(std::span<const ImageId> ids, std::optional<ImageId> native_source = {});
void RetainImage(CommandBuffer& command, ImageId id);
void TouchImage(Image& image);
void TrackImage(ImageId id);
void TrackImageHead(ImageId id);
void TrackImageTail(ImageId id);
void UntrackImage(ImageId id);
void UntrackImageHead(ImageId id);
void UntrackImageTail(ImageId id);
void TrackImageDownload(ImageId id);
void TrackImageDownloadLocked(ImageId id, Image& image);
[[nodiscard]] ImageId InsertImage(const ImageInfo& info);
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
void RegisterImage(ImageId id);
void UnregisterImage(ImageId id);
void DeleteImage(ImageId id);
void FreeImage(ImageId id);
void TouchImage(Image& image);
void TrackImage(ImageId id);
void TrackImageHead(ImageId id);
void TrackImageTail(ImageId id);
void UntrackImage(ImageId id);
void UntrackImageHead(ImageId id);
void UntrackImageTail(ImageId id);
void MarkAsMaybeDirty(ImageId id, Image& image);
void TrackImageDownload(ImageId id, Image& image);
[[nodiscard]] static bool SameBacking(const ImageInfo& cached, const ImageInfo& requested,
bool exact_format);
[[nodiscard]] static BindingType UploadBinding(const Image& image);
@@ -149,31 +152,24 @@ private:
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
void CopyImage(ImageId destination, ImageId source);
void AssociateStencil(ImageId depth, GuestRange stencil);
void AssociateStencilLocked(ImageId depth, GuestRange stencil);
void CopyImageMip(ImageId destination, ImageId source, uint32_t mip, uint32_t layer);
void ValidateImageDesc(const ImageDesc& desc) const;
void InvalidateCpuAliases(uint64_t address, uint64_t size);
void ClearGpuModified(ImageId id);
void DownloadImage(ImageId id);
[[nodiscard]] bool TryDownloadImage(ImageId id);
[[nodiscard]] std::pair<uint8_t*, uint64_t> MapDownload(uint64_t size, uint64_t alignment);
void QueueDownload(GuestRange range, StreamBuffer& download, uint8_t* mapped, uint64_t offset);
void InvalidateCpuAliases(uint64_t address, uint64_t size);
[[nodiscard]] bool TryDownloadImage(ImageId id);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
TrackingSpinLock m_lock;
PageManager& m_page_manager;
BlitHelper m_blit_helper;
std::unique_ptr<TileManager> m_tiler;
TileManager m_tiler;
BufferCache& m_buffer_cache;
std::vector<Slot> m_slots;
std::vector<uint32_t> m_free_slots;
Common::SlotVector<Image> m_slot_images;
ImagePageTable m_image_page_table;
std::map<vk::Format, ImageId> m_null_images;
std::unordered_map<vk::Format, ImageId> m_null_images;
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
std::set<ImageId> m_download_images;
std::unordered_set<ImageId> m_download_images;
std::map<uint64_t, MetaDataInfo> m_surface_metas;
uint64_t m_total_used_memory = 0;
uint64_t m_trigger_gc_memory = 0;
@@ -9,20 +9,42 @@
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <array>
#include <atomic>
namespace Libs::Graphics {
static std::atomic<uint32_t> g_render_color_log_count = 0;
static void ResolveDccClearInfo(RenderColorInfo& info, vk::Format format, bool has_dcc,
uint32_t packed_clear) {
// Register-backed DCC clears use the target's packed clear value. Decode one-word guest
// formats here; unsupported encodings remain tracked without unsafe materialization.
info.metadata_clear_supported =
has_dcc && DecodePackedColorClear(format, packed_clear, info.color_clear_value);
switch (format) {
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Srgb:
case vk::Format::eB8G8R8A8Unorm:
case vk::Format::eB8G8R8A8Srgb:
case vk::Format::eA2B10G10R10UnormPack32:
case vk::Format::eA2R10G10B10UnormPack32:
info.metadata_fixed_clear_supported = has_dcc;
break;
default: info.metadata_fixed_clear_supported = false; break;
}
if (!info.metadata_clear_supported) {
info.color_clear_value = {};
}
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderColorInfo& r,
uint32_t render_target_slice_offset,
uint32_t render_target_slot, bool ignore_target_mask,
@@ -55,20 +77,22 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
}
// No color output
r.type = RenderColorType::NoColorOutput;
r.desc = {};
r.base_addr = 0;
r.image_id = {};
r.image_view = nullptr;
r.format = vk::Format::eUndefined;
r.extent = {};
r.base_mip_level = 0;
r.base_array_layer = 0;
r.buffer_size = 0;
r.samples = 1;
r.export_mapping = {};
r.color_clear_enable = false;
r.color_clear_value = {};
r.type = RenderColorType::NoColorOutput;
r.desc = {};
r.base_addr = 0;
r.image_id = {};
r.image_view = nullptr;
r.format = vk::Format::eUndefined;
r.extent = {};
r.base_mip_level = 0;
r.base_array_layer = 0;
r.buffer_size = 0;
r.samples = 1;
r.export_mapping = {};
r.color_clear_enable = false;
r.metadata_clear_supported = false;
r.metadata_fixed_clear_supported = false;
r.color_clear_value = {};
return;
}
const auto samples = render_sample_count(rt.attrib.num_fragments);
@@ -101,14 +125,15 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
" attrib3_tile=0x%08" PRIx32 " attrib3_dim=0x%08" PRIx32 " fmt=0x%08" PRIx32
" nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 "\n",
rt_slot, rt.base.addr, mask, rt.attrib2.width, rt.attrib2.height,
rt.attrib3.tile_mode, rt.attrib3.dimension, rt.info.format, rt.info.channel_type,
rt.info.channel_order);
static_cast<uint32_t>(rt.attrib3.tile_mode), rt.attrib3.dimension,
static_cast<uint32_t>(rt.info.format), static_cast<uint32_t>(rt.info.channel_type),
static_cast<uint32_t>(rt.info.channel_order));
}
}
// CB_COLOR_CONTROL describes the color-buffer operation / ROP3 logic op.
// ROP3 Copy is the normal color write path, not a render-target clear.
// SRGB clear words are still encoded as normalized component values.
// Color-control state selects the color-buffer operation and logical blend operation.
// The normal copy operation is a regular color write, not an attachment clear.
// Nonlinear clear values are still stored as normalized components.
// Fast color clears are metadata driven and must be handled explicitly when
// that metadata path is implemented; render-pass load must preserve contents.
r.color_clear_enable = false;
@@ -119,27 +144,42 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
uint32_t pitch = 0;
uint64_t size = 0;
bool tile = false;
const bool volume = rt.attrib3.dimension == 2;
if (rt.attrib3.dimension != 1 && !volume) {
static constexpr std::array image_types {Prospero::ImageType::kColor1D,
Prospero::ImageType::kColor2D,
Prospero::ImageType::kColor3D};
if (rt.attrib3.dimension >= image_types.size()) {
EXIT("unsupported render-target dimension: %u\n", rt.attrib3.dimension);
}
const auto image_type = image_types[rt.attrib3.dimension];
const bool is_1d = image_type == Prospero::ImageType::kColor1D;
const bool volume = image_type == Prospero::ImageType::kColor3D;
if (is_1d && rt.attrib2.height != 0) {
EXIT("1D render target has nonzero height: %u\n", rt.attrib2.height);
}
if (!volume && rt.attrib3.depth != 0) {
EXIT("2D render target has nonzero depth: %u\n", rt.attrib3.depth);
EXIT("non-3D render target has nonzero depth: %u\n", rt.attrib3.depth);
}
if (is_1d && samples != 1) {
EXIT("multisampled 1D render targets are unsupported\n");
}
if (volume && samples != 1) {
EXIT("multisampled 3D render targets are unsupported\n");
}
const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u;
const bool standard64 =
rt.attrib3.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB);
const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u;
const bool standard4 = rt.attrib3.tile_mode == Prospero::TileMode::kStandard4KB;
const bool standard64 = rt.attrib3.tile_mode == Prospero::TileMode::kStandard64KB;
const bool depth_tile = rt.attrib3.tile_mode == Prospero::TileMode::kDepth;
const bool texture_tile = standard4 || standard64 || depth_tile;
switch (rt.attrib3.tile_mode) {
case Prospero::GpuEnumValue(Prospero::TileMode::kLinear):
case Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB):
case Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget):
case Prospero::TileMode::kLinear:
case Prospero::TileMode::kStandard4KB:
case Prospero::TileMode::kStandard64KB:
case Prospero::TileMode::kDepth:
case Prospero::TileMode::kRenderTarget:
tile = !RenderIsColorTileModeLinear(rt.attrib3.tile_mode);
break;
default: EXIT("unknown tile mode: %u\n", rt.attrib3.tile_mode);
default: EXIT("unknown tile mode: %u\n", static_cast<uint32_t>(rt.attrib3.tile_mode));
}
if (!tile && levels > 1) {
EXIT("linear mipmapped render targets are unsupported\n");
@@ -147,6 +187,9 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
if (samples > 1 && (!tile || levels != 1)) {
EXIT("multisampled render targets require a single-mip tiled surface\n");
}
if (texture_tile && samples != 1) {
EXIT("texture-tiled color render targets do not support multisampling\n");
}
width = rt.attrib2.width + 1;
height = rt.attrib2.height + 1;
@@ -157,31 +200,38 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
EXIT("render-target format has no valid element size\n");
}
const auto transfer_format = ImageOps::RenderTargetTransferFormat(bytes_per_element);
if (standard64 &&
(rt.attrib3.dimension != 1 || rt.attrib3.depth != 0 || levels != 1 ||
rt.view.current_mip_level != 0 || view.base_layer != 0 || view.image_layers != 1 ||
samples != 1 || bytes_per_element != 4 || rt.pitch.pitch_div8_minus1 != 0 ||
(rt.base.addr & 0xffffu) != 0 || rt.info.fmask_compression_enable ||
rt.info.fmask_data_compression_disable || rt.info.fmask_one_frag_mode ||
rt.info.cmask_fast_clear_enable || rt.info.dcc_compression_enable ||
rt.info.cmask_is_linear != 0 || rt.info.cmask_addr_type != 0 || rt.info.alt_tile_mode ||
rt.cmask.addr != 0 || rt.fmask.addr != 0 || rt.dcc_addr.addr != 0 ||
rt.dcc.data_write_on_dcc_clear_to_reg)) {
EXIT("unsupported Standard64KB render target: addr=0x%016" PRIx64
TileTextureBlockLayout texture_tile_layout {};
if (texture_tile &&
(!TileGetTextureBlockLayout(transfer_format, rt.attrib3.tile_mode, volume,
texture_tile_layout) ||
rt.pitch.pitch_div8_minus1 != 0 ||
(rt.base.addr & (texture_tile_layout.block.block_size - 1u)) != 0 ||
rt.info.fmask_compression_enable || rt.info.fmask_data_compression_disable ||
rt.info.fmask_one_frag_mode || rt.info.cmask_fast_clear_enable ||
rt.info.dcc_compression_enable || rt.info.cmask_is_linear != 0 ||
rt.info.cmask_addr_type != 0 || rt.info.alt_tile_mode || rt.cmask.addr != 0 ||
rt.fmask.addr != 0 || rt.dcc_addr.addr != 0 || rt.dcc.data_write_on_dcc_clear_to_reg)) {
EXIT("unsupported texture-tiled render target: addr=0x%016" PRIx64 " tile=%u"
" dimension=%u depth=%u levels=%u layer=%u/%u samples=%u fragments=%u bpe=%u"
" cmask=0x%016" PRIx64 " fmask=0x%016" PRIx64 " dcc=0x%016" PRIx64 "\n",
rt.base.addr, rt.attrib3.dimension, rt.attrib3.depth, levels, view.base_layer,
view.image_layers, rt.attrib.num_samples, rt.attrib.num_fragments, bytes_per_element,
rt.cmask.addr, rt.fmask.addr, rt.dcc_addr.addr);
rt.base.addr, static_cast<uint32_t>(rt.attrib3.tile_mode), rt.attrib3.dimension,
rt.attrib3.depth, levels, view.base_layer, view.image_layers, rt.attrib.num_samples,
rt.attrib.num_fragments, bytes_per_element, rt.cmask.addr, rt.fmask.addr,
rt.dcc_addr.addr);
}
if ((standard64 || depth_tile) && (rt.attrib3.dimension != 1 || rt.attrib3.depth != 0 ||
view.base_layer != 0 || view.image_layers != 1)) {
EXIT("unsupported 64KB texture-tiled render-target view: dimension=%u depth=%u"
" layer=%u/%u\n",
rt.attrib3.dimension, rt.attrib3.depth, view.base_layer, view.image_layers);
}
if (rt.pitch.pitch_div8_minus1 != 0) {
pitch = (rt.pitch.pitch_div8_minus1 + 1u) << 3u;
} else if (tile) {
if (volume) {
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
} else if (standard64) {
pitch = TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float),
width, rt.attrib3.tile_mode);
} else if (texture_tile) {
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
} else {
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
}
@@ -207,18 +257,17 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
1};
if (!tile || !TileGetTiledTextureLayout(description, volume_layout)) {
EXIT("unsupported 3D render-target layout: %ux%ux%u levels=%u tile=%u\n", width, height,
depth, levels, rt.attrib3.tile_mode);
depth, levels, static_cast<uint32_t>(rt.attrib3.tile_mode));
}
size = volume_layout.block_slice_size;
backing_size = volume_layout.total_size;
} else if (tile) {
TileSizeAlign layout {};
bool valid_layout = false;
if (standard64) {
TileGetTextureSize(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), width,
height, levels, rt.attrib3.tile_mode, &layout, mip_sizes,
mip_padded);
valid_layout = layout.size != 0 && layout.align == 65536;
if (texture_tile) {
TileGetTextureSize(transfer_format, width, height, levels, rt.attrib3.tile_mode,
&layout, mip_sizes, mip_padded);
valid_layout = layout.size != 0 && layout.align == texture_tile_layout.block.block_size;
} else {
valid_layout =
levels == 1 ? TileGetRenderTargetSize(width, height, pitch, bytes_per_element,
@@ -278,8 +327,9 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
" extent=%ux%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u"
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 " samples=%u tile=%s\n",
rt_slot, rt.base.addr, backing_size, width, height, depth, rt.view.current_mip_level,
view_extent.width, view_extent.height, levels, pitch, rt.info.format,
rt.info.channel_type, rt.info.channel_order, samples, tile ? "tiled" : "linear");
view_extent.width, view_extent.height, levels, pitch,
static_cast<uint32_t>(rt.info.format), static_cast<uint32_t>(rt.info.channel_type),
static_cast<uint32_t>(rt.info.channel_order), samples, tile ? "tiled" : "linear");
}
TextureCache::ImageDesc desc {};
@@ -287,13 +337,20 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
desc.info.data = {rt.base.addr, backing_size};
desc.info.pixel_format = target_format.format;
desc.info.guest_format = transfer_format;
desc.info.type = volume ? Prospero::ImageType::kColor3D : Prospero::ImageType::kColor2D;
desc.info.type = image_type;
desc.info.extent = {width, height, depth};
desc.info.resources = {levels, volume ? 1u : view.image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes_per_element;
desc.info.samples = samples;
desc.info.tile_mode = rt.attrib3.tile_mode;
const bool has_dcc = rt.info.dcc_compression_enable && rt.dcc_addr.addr != 0;
if (has_dcc) {
// DCC uses a separate metadata allocation. Carry its address through ImageInfo so
// TextureCache can associate metadata fills observed before target registration.
desc.info.metadata.kind = ImageMetadataKind::Dcc;
desc.info.metadata.range.address = rt.dcc_addr.addr;
}
for (uint32_t level = 0; level < levels; level++) {
if (volume) {
const auto& mip = volume_layout.mips[level];
@@ -319,8 +376,13 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
};
}
desc.view_info.format = target_format.format;
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
if (is_1d) {
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e1D : vk::ImageViewType::e1DArray;
} else {
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
}
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
desc.view_info.base_level = rt.view.current_mip_level;
desc.view_info.level_count = 1;
@@ -340,7 +402,7 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
r.samples = samples;
r.export_mapping = target_format.export_mapping;
r.color_clear_enable = false;
r.color_clear_value = {};
ResolveDccClearInfo(r, target_format.format, has_dcc, rt.clear_word0.word0);
BindRenderTarget(r.image_id);
}
@@ -10,8 +10,6 @@
namespace Libs::Graphics {
class RenderCommandBuffer;
enum class RenderColorType {
NoColorOutput,
RenderTexture,
@@ -31,7 +29,9 @@ struct RenderColorInfo {
uint32_t target_slot = 0;
uint32_t samples = 1;
Prospero::ColorComponentMapping export_mapping;
bool color_clear_enable = false;
bool color_clear_enable = false;
bool metadata_clear_supported = false;
bool metadata_fixed_clear_supported = false;
vk::ClearColorValue color_clear_value {};
};
@@ -1,96 +1,97 @@
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "graphics/host_gpu/graphicContext.h"
#include <algorithm>
#include <cstdio>
#include <optional>
namespace Libs::Graphics {
static thread_local CommandScheduler* g_deferred_callback_scheduler = nullptr;
void CommandSlot::Reset() {
EXIT_IF(buffer == nullptr);
const auto result = buffer.reset(vk::CommandBufferResetFlagBits::eReleaseResources);
if (result != vk::Result::eSuccess) {
EXIT("failed to reset Vulkan command buffer: %s (%d)\n", VulkanToString(result).c_str(),
static_cast<int>(result));
}
namespace {
void ReportVulkanFatal(const char* what, vk::Result result, uint64_t tick, uint32_t debug_op,
uint64_t debug_submit, uint32_t arg0, uint32_t arg1, uint32_t arg2,
uint32_t arg3, uint64_t arg4) {
LOGF("%s failed: %s (%d), tick=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
what, VulkanToString(result).c_str(), static_cast<int>(result), tick, debug_op,
debug_submit, arg0, arg1, arg2, arg3, arg4);
std::printf("%s failed: %s (%d), tick=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
what, VulkanToString(result).c_str(), static_cast<int>(result), tick, debug_op,
debug_submit, arg0, arg1, arg2, arg3, arg4);
std::fflush(stdout);
}
CommandScheduler::CommandPool::~CommandPool() {
Destroy();
}
void CommandScheduler::CommandPool::Create(GraphicContext& graphics) {
EXIT_IF(m_pool != nullptr || m_graphics != nullptr ||
graphics.queue_family == static_cast<uint32_t>(-1));
m_graphics = &graphics;
} // namespace
CommandScheduler::CommandPool::CommandPool(GraphicContext& graphics, MasterSemaphore& master)
: m_graphics(graphics), m_master(master) {
EXIT_IF(graphics.queue_family == static_cast<uint32_t>(-1));
vk::CommandPoolCreateInfo create {};
create.sType = vk::StructureType::eCommandPoolCreateInfo;
create.queueFamilyIndex = graphics.queue_family;
create.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
create.flags = vk::CommandPoolCreateFlagBits::eTransient |
vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
const auto result = graphics.device.createCommandPool(&create, nullptr, &m_pool);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || m_pool == nullptr);
}
CommandSlot* CommandScheduler::CommandPool::CreateSlot() {
EXIT_IF(m_graphics == nullptr);
auto& graphics = *m_graphics;
CommandScheduler::CommandPool::~CommandPool() {
m_graphics.device.destroyCommandPool(m_pool, nullptr);
}
size_t CommandScheduler::CommandPool::Grow() {
const auto first = m_ticks.size();
m_ticks.resize(first + GrowStep);
m_buffers.resize(first + GrowStep);
vk::CommandBufferAllocateInfo allocate {};
allocate.sType = vk::StructureType::eCommandBufferAllocateInfo;
allocate.commandPool = m_pool;
allocate.level = vk::CommandBufferLevel::ePrimary;
allocate.commandBufferCount = 1;
vk::CommandBuffer buffer = nullptr;
EXIT_IF(graphics.device.allocateCommandBuffers(&allocate, &buffer) != vk::Result::eSuccess);
vk::FenceCreateInfo fence_create {};
fence_create.sType = vk::StructureType::eFenceCreateInfo;
fence_create.flags = vk::FenceCreateFlagBits::eSignaled;
vk::Fence fence = nullptr;
if (graphics.device.createFence(&fence_create, nullptr, &fence) != vk::Result::eSuccess) {
graphics.device.freeCommandBuffers(m_pool, 1, &buffer);
EXIT("failed to create command-buffer fence\n");
}
auto& slot = m_slots.emplace_back();
slot.pool_mutex = &m_mutex;
slot.id = static_cast<uint32_t>(m_slots.size() - 1);
slot.buffer = buffer;
slot.fence = fence;
return &slot;
allocate.commandBufferCount = static_cast<uint32_t>(GrowStep);
EXIT_IF(m_graphics.device.allocateCommandBuffers(&allocate, m_buffers.data() + first) !=
vk::Result::eSuccess);
return first;
}
CommandSlot* CommandScheduler::CommandPool::Allocate(GraphicContext& graphics) {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
Create(graphics);
}
EXIT_IF(m_graphics != &graphics);
auto found = std::ranges::find_if(m_slots, [](const auto& slot) { return !slot.busy; });
auto* slot = found != m_slots.end() ? &*found : CreateSlot();
slot->busy = true;
slot->Reset();
return slot;
}
vk::CommandBuffer CommandScheduler::CommandPool::Commit() {
auto gpu_tick = m_master.KnownGpuTick();
const auto search = [this, &gpu_tick](size_t begin, size_t end) -> std::optional<size_t> {
for (size_t index = begin; index < end; ++index) {
if (gpu_tick >= m_ticks[index]) {
m_ticks[index] = m_master.CurrentTick();
return index;
}
}
return std::nullopt;
};
void CommandScheduler::CommandPool::Destroy() {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
return;
auto found = search(m_hint, m_ticks.size());
if (!found) {
m_master.Refresh();
gpu_tick = m_master.KnownGpuTick();
found = search(m_hint, m_ticks.size());
}
EXIT_IF(std::ranges::any_of(m_slots, [](const auto& slot) { return slot.busy; }));
EXIT_IF(m_graphics == nullptr);
for (const auto& slot: m_slots) {
m_graphics->device.destroyFence(slot.fence, nullptr);
if (!found) {
found = search(0, m_hint);
}
m_graphics->device.destroyCommandPool(m_pool, nullptr);
m_slots.clear();
m_pool = nullptr;
m_graphics = nullptr;
if (!found) {
found = Grow();
m_ticks[*found] = m_master.CurrentTick();
}
m_hint = *found + 1;
if (m_hint == m_ticks.size()) {
m_hint = 0;
}
return m_buffers[*found];
}
bool CommandScheduler::InDeferredOperation() noexcept {
@@ -99,10 +100,8 @@ bool CommandScheduler::InDeferredOperation() noexcept {
CommandScheduler::CommandScheduler(RenderContext& context, GraphicContext& graphics)
: m_master(graphics), m_context(context), m_graphics(graphics),
m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {
m_buffers.reserve(CommandBufferGrowStep);
m_buffer_ticks.reserve(CommandBufferGrowStep);
}
m_command_pool(graphics, m_master), m_command(*this),
m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {}
CommandScheduler::~CommandScheduler() {
Shutdown();
@@ -128,9 +127,11 @@ void CommandScheduler::Shutdown() {
}
m_operation_state = OperationState::Draining;
}
if (Active() && m_recording) {
Finish();
if (!m_command.IsInvalid()) {
Submit();
}
m_master.Wait(CurrentTick() - 1);
PopPendingOperations();
DrainPriorityOperations();
m_priority_thread.request_stop();
m_operation_available.notify_all();
@@ -156,14 +157,10 @@ void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config
m_user_config = &user_config;
m_shaders = &shaders;
if (!Active()) {
m_current = static_cast<int>(GrowCommandBuffers());
}
BindCurrent();
if (!m_recording) {
Current().Begin();
m_recording = true;
if (m_command.IsInvalid()) {
BeginNext();
} else {
BindCurrent();
}
}
@@ -172,7 +169,7 @@ void CommandScheduler::BeginRendering(const RenderState& state) {
}
void CommandScheduler::EndRendering() {
if (Active() && m_recording) {
if (Active() && !m_command.IsInvalid()) {
Current().EndRendering();
}
}
@@ -183,55 +180,40 @@ void CommandScheduler::Flush() {
}
void CommandScheduler::Flush(SubmitInfo& submit) {
SubmitCurrent(submit);
Submit(submit);
BeginNext();
}
CommandBuffer& CommandScheduler::FlushAndGetSubmitted() {
SubmitInfo submit;
auto& submitted = SubmitCurrent(submit);
void CommandScheduler::FlushAndWait() {
const auto tick = Submit();
m_master.Wait(tick);
BeginNext();
return submitted;
}
void CommandScheduler::Finish() {
CheckActive();
const auto tick = CurrentTick();
if (m_recording) {
SubmitInfo submit;
SubmitCurrent(submit);
if (!m_command.IsInvalid()) {
Submit();
}
for (auto& buffer: m_buffers) {
buffer->WaitForFenceAndReset();
}
m_master.Wait(tick);
m_master.Wait(CurrentTick() - 1);
BeginNext();
PopPendingOperations();
BindCurrent();
Current().Begin();
m_recording = true;
}
void CommandScheduler::FinishCurrent() {
SubmitInfo submit;
auto& submitted = SubmitCurrent(submit);
submitted.WaitForFenceAndReset();
m_master.Refresh();
PopPendingOperations();
submitted.Begin();
m_recording = true;
}
void CommandScheduler::Wait(uint64_t tick) {
CheckActive();
EXIT_IF(tick > CurrentTick());
if (tick >= CurrentTick()) {
if (tick == CurrentTick()) {
CheckActive();
// A stream-buffer wrap can wait while a draw is being prepared through a reference to
// Current(). Recycle the same command object so that reference remains valid.
FinishCurrent();
return;
// Current(). The wrapper stays stable while its pooled Vulkan buffer is retired. Deferred
// resources are released only at the next GPU operation boundary.
const auto submitted_tick = Submit();
EXIT_IF(submitted_tick != tick);
m_master.Wait(tick);
BeginNext();
} else {
m_master.Wait(tick);
}
m_master.Wait(tick);
PopPendingOperations();
}
void CommandScheduler::PopPendingOperations() {
@@ -243,17 +225,18 @@ void CommandScheduler::PopPendingOperations(bool refresh_gpu_tick) {
m_master.Refresh();
}
for (;;) {
Common::UniqueFunction<void> callback;
PendingOperation operation;
{
std::lock_guard lock(m_operation_mutex);
if (m_pending_operations.empty() ||
!m_master.IsFree(m_pending_operations.front().tick)) {
return;
}
callback = std::move(m_pending_operations.front().callback);
operation = std::move(m_pending_operations.front());
m_pending_operations.pop();
}
RunOperation(std::move(callback));
WaitPriorityOperations(operation.tick);
RunOperation(std::move(operation.callback));
}
}
@@ -359,86 +342,81 @@ bool CommandScheduler::IsFree(uint64_t tick) {
return m_master.IsFree(tick);
}
CommandSlot* CommandScheduler::AllocateCommandBuffer() {
return m_command_pool.Allocate(m_graphics);
}
uint64_t CommandScheduler::NextSubmitSequence() noexcept {
return m_submit_sequence.fetch_add(1, std::memory_order_relaxed) + 1;
}
void CommandScheduler::CheckActive() const {
EXIT_IF(!Active() || static_cast<size_t>(m_current) >= m_buffers.size());
EXIT_IF(!Active());
}
RenderCommandBuffer& CommandScheduler::Current() const {
CommandBuffer& CommandScheduler::Current() {
CheckActive();
EXIT_IF(m_buffers[m_current] == nullptr);
return *m_buffers[m_current];
return m_command;
}
void CommandScheduler::BindCurrent() const {
void CommandScheduler::BindCurrent() {
EXIT_IF(m_registers == nullptr || m_user_config == nullptr || m_shaders == nullptr);
Current().Bind(*m_registers, *m_user_config, *m_shaders);
m_command.Bind(*m_registers, *m_user_config, *m_shaders);
}
CommandBuffer& CommandScheduler::SubmitCurrent(SubmitInfo& submit) {
CheckActive();
EXIT_IF(!m_recording);
auto& submitted = Current();
submitted.End();
const auto signal_tick = m_master.NextTick();
submit.AddSignal(m_master.Handle(), signal_tick);
submitted.Execute(submit);
m_buffer_ticks[static_cast<size_t>(m_current)] = signal_tick;
m_recording = false;
return submitted;
CommandBuffer& CommandScheduler::BeginCommand() {
EXIT_IF(!m_command.IsInvalid());
m_command.m_buffer = m_command_pool.Commit();
m_command.Begin();
return m_command;
}
int CommandScheduler::FindReusableBuffer(uint64_t gpu_tick) const {
EXIT_IF(m_buffer_ticks.size() != m_buffers.size());
const auto buffer_count = m_buffers.size();
for (size_t offset = 1; offset <= buffer_count; ++offset) {
const auto candidate = (static_cast<size_t>(m_current) + offset) % buffer_count;
if (gpu_tick >= m_buffer_ticks[candidate]) {
return static_cast<int>(candidate);
}
uint64_t CommandScheduler::Submit(SubmitInfo submit) {
EXIT_IF(m_command.IsInvalid());
EXIT_IF(submit.num_wait_semaphores > SubmitInfo::MaxSemaphores ||
submit.num_signal_semaphores >= SubmitInfo::MaxSemaphores);
m_command.End();
const auto buffer = m_command.m_buffer;
auto& graphics = m_graphics;
EXIT_IF(graphics.queue == nullptr);
vk::Result result;
uint64_t tick;
{
Common::LockGuard lock(graphics.queue_mutex);
tick = m_master.NextTick();
submit.AddSignal(m_master.Handle(), tick);
vk::TimelineSemaphoreSubmitInfo timeline_info {};
timeline_info.sType = vk::StructureType::eTimelineSemaphoreSubmitInfo;
timeline_info.waitSemaphoreValueCount = submit.num_wait_semaphores;
timeline_info.pWaitSemaphoreValues = submit.wait_ticks.data();
timeline_info.signalSemaphoreValueCount = submit.num_signal_semaphores;
timeline_info.pSignalSemaphoreValues = submit.signal_ticks.data();
vk::SubmitInfo submit_info {};
submit_info.sType = vk::StructureType::eSubmitInfo;
submit_info.pNext = &timeline_info;
submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
submit_info.pWaitSemaphores = submit.wait_semaphores.data();
submit_info.pWaitDstStageMask = submit.wait_stages.data();
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &buffer;
submit_info.signalSemaphoreCount = submit.num_signal_semaphores;
submit_info.pSignalSemaphores = submit.signal_semaphores.data();
result = graphics.queue.submit(1, &submit_info, nullptr);
}
return -1;
}
size_t CommandScheduler::GrowCommandBuffers() {
const auto first = m_buffers.size();
const auto end = first + CommandBufferGrowStep;
m_buffers.reserve(end);
m_buffer_ticks.reserve(end);
for (size_t i = first; i < end; ++i) {
m_buffers.emplace_back(std::make_unique<RenderCommandBuffer>(*this));
m_buffer_ticks.push_back(0);
if (result != vk::Result::eSuccess) {
ReportVulkanFatal("vkQueueSubmit", result, tick, m_command.m_debug_op,
m_command.m_debug_submit_id, m_command.m_debug_arg0,
m_command.m_debug_arg1, m_command.m_debug_arg2, m_command.m_debug_arg3,
m_command.m_debug_arg4);
}
return first;
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
m_command.m_buffer = nullptr;
return tick;
}
void CommandScheduler::BeginNext() {
EXIT_IF(m_recording);
auto candidate = FindReusableBuffer(m_master.KnownGpuTick());
bool refreshed = false;
if (candidate < 0) {
m_master.Refresh();
refreshed = true;
candidate = FindReusableBuffer(m_master.KnownGpuTick());
}
if (candidate < 0) {
candidate = static_cast<int>(GrowCommandBuffers());
}
m_current = candidate;
Current().WaitForFenceAndReset();
PopPendingOperations(!refreshed);
EXIT_IF(!m_command.IsInvalid());
BindCurrent();
Current().Begin();
m_recording = true;
BeginCommand();
}
} // namespace Libs::Graphics
@@ -6,11 +6,7 @@
#include "graphics/host_gpu/renderer/masterSemaphore.h"
#include "graphics/host_gpu/renderer/render.h"
#include <array>
#include <atomic>
#include <condition_variable>
#include <deque>
#include <memory>
#include <mutex>
#include <queue>
@@ -20,16 +16,6 @@
namespace Libs::Graphics {
struct CommandSlot {
Common::Mutex* pool_mutex = nullptr;
uint32_t id = 0;
vk::CommandBuffer buffer = nullptr;
vk::Fence fence = nullptr;
bool busy = false;
void Reset();
};
class CommandScheduler {
public:
CommandScheduler(RenderContext& context, GraphicContext& graphics);
@@ -41,9 +27,10 @@ public:
void EndRendering();
void Flush();
void Flush(SubmitInfo& submit);
CommandBuffer& FlushAndGetSubmitted();
void FlushAndWait();
void Finish();
void FinishCurrent();
CommandBuffer& BeginCommand();
uint64_t Submit(SubmitInfo submit = {});
// Deferred callbacks can observe an externally owned drain, but cannot initiate shutdown:
// the priority runner cannot join itself.
void Shutdown();
@@ -55,34 +42,35 @@ public:
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] static bool InDeferredOperation() noexcept;
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
void CheckActive() const;
RenderCommandBuffer& Current() const;
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
[[nodiscard]] bool IsFree(uint64_t tick);
[[nodiscard]] RenderContext& Context() const noexcept { return m_context; }
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
[[nodiscard]] bool Active() const noexcept { return m_registers != nullptr; }
void CheckActive() const;
CommandBuffer& Current();
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
[[nodiscard]] bool IsFree(uint64_t tick);
[[nodiscard]] MasterSemaphore& GetMasterSemaphore() noexcept { return m_master; }
[[nodiscard]] RenderContext& Context() const noexcept { return m_context; }
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
private:
static constexpr size_t CommandBufferGrowStep = 4;
class CommandPool {
public:
CommandPool() = default;
CommandPool(GraphicContext& graphics, MasterSemaphore& master);
~CommandPool();
KYTY_CLASS_NO_COPY(CommandPool);
CommandSlot* Allocate(GraphicContext& graphics);
vk::CommandBuffer Commit();
private:
void Create(GraphicContext& graphics);
CommandSlot* CreateSlot();
void Destroy();
static constexpr size_t GrowStep = 4;
GraphicContext* m_graphics = nullptr;
Common::Mutex m_mutex;
vk::CommandPool m_pool = nullptr;
std::deque<CommandSlot> m_slots;
size_t Grow();
GraphicContext& m_graphics;
MasterSemaphore& m_master;
vk::CommandPool m_pool = nullptr;
std::vector<vk::CommandBuffer> m_buffers;
std::vector<uint64_t> m_ticks;
size_t m_hint = 0;
};
enum class OperationState { Open, Draining, Closed };
@@ -92,39 +80,28 @@ private:
uint64_t tick = 0;
};
void BindCurrent() const;
CommandBuffer& SubmitCurrent(SubmitInfo& submit);
void BeginNext();
void PopPendingOperations(bool refresh_gpu_tick);
[[nodiscard]] int FindReusableBuffer(uint64_t gpu_tick) const;
[[nodiscard]] size_t GrowCommandBuffers();
void PriorityOperationsThread(std::stop_token stop);
void RunOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] CommandSlot* AllocateCommandBuffer();
[[nodiscard]] uint64_t NextSubmitSequence() noexcept;
void BindCurrent();
void BeginNext();
void PopPendingOperations(bool refresh_gpu_tick);
void PriorityOperationsThread(std::stop_token stop);
void RunOperation(Common::UniqueFunction<void>&& operation);
MasterSemaphore m_master;
RenderContext& m_context;
GraphicContext& m_graphics;
CommandPool m_command_pool;
std::vector<std::unique_ptr<RenderCommandBuffer>> m_buffers;
std::vector<uint64_t> m_buffer_ticks;
std::queue<PendingOperation> m_pending_operations;
std::queue<PendingOperation> m_priority_operations;
std::mutex m_operation_mutex;
std::condition_variable m_operation_available;
std::jthread m_priority_thread;
bool m_priority_active = false;
uint64_t m_priority_active_tick = 0;
OperationState m_operation_state = OperationState::Open;
int m_current = -1;
bool m_recording = false;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
std::atomic<uint64_t> m_submit_sequence = 0;
friend class CommandBuffer;
MasterSemaphore m_master;
RenderContext& m_context;
GraphicContext& m_graphics;
CommandPool m_command_pool;
CommandBuffer m_command;
std::queue<PendingOperation> m_pending_operations;
std::queue<PendingOperation> m_priority_operations;
std::mutex m_operation_mutex;
std::condition_variable m_operation_available;
std::jthread m_priority_thread;
bool m_priority_active = false;
uint64_t m_priority_active_tick = 0;
OperationState m_operation_state = OperationState::Open;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
};
} // namespace Libs::Graphics
+6 -198
View File
@@ -1,7 +1,5 @@
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
@@ -9,7 +7,6 @@
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h"
@@ -18,98 +15,28 @@
#include <algorithm>
#include <bit>
#include <cstring>
#include <memory>
namespace Libs::Graphics {
FenceResourceRetainer::~FenceResourceRetainer() {
if (!m_resources.empty()) {
EXIT("fence resource retainer destroyed before release\n");
}
}
void FenceResourceRetainer::Retain(std::shared_ptr<void> resource) {
if (resource == nullptr) {
EXIT("cannot retain a null fence resource\n");
}
if (std::ranges::none_of(m_resources, [&resource](const auto& retained) {
return retained.get() == resource.get();
})) {
m_resources.push_back(std::move(resource));
}
}
void FenceResourceRetainer::ReleaseAfterFence() noexcept {
m_resources.clear();
}
CommandBuffer::CommandBuffer(CommandScheduler& scheduler)
: m_context(scheduler.Context()), m_scheduler(scheduler), m_graphics(scheduler.Graphics()),
m_slot(scheduler.AllocateCommandBuffer()) {}
CommandBuffer::~CommandBuffer() {
Release();
}
: m_context(scheduler.Context()), m_graphics(scheduler.Graphics()) {}
bool CommandBuffer::IsInvalid() const {
return m_slot == nullptr;
return m_buffer == nullptr;
}
vk::CommandBuffer CommandBuffer::Handle() const {
EXIT_IF(IsInvalid());
const auto handle = m_slot->buffer;
EXIT_IF(handle == nullptr);
return handle;
return m_buffer;
}
void CommandBuffer::Release() {
EXIT_IF(IsInvalid());
Common::LockGuard lock(*m_slot->pool_mutex);
WaitForFence();
m_slot->busy = false;
m_slot->Reset();
ReleaseResourcesAfterFence();
m_slot = nullptr;
EXIT_NOT_IMPLEMENTED(!IsInvalid());
}
void CommandBuffer::RetireBufferAfterFence(std::unique_ptr<VulkanBuffer> buffer) {
if (IsInvalid() || m_execute || buffer == nullptr || buffer->buffer == nullptr) {
EXIT("cannot retire a buffer on an invalid or submitted command buffer\n");
}
m_retired_buffers.push_back(std::move(buffer));
}
void CommandBuffer::RetainResourceUntilFence(std::shared_ptr<void> resource) {
if (IsInvalid() || m_execute) {
EXIT("cannot retain a resource on an invalid or submitted command buffer\n");
}
m_fence_resources.Retain(std::move(resource));
}
void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet& set) {
m_descriptor_sets_after_fence.push_back(&set);
}
void CommandBuffer::RecycleDescriptorsAfterFence() {
for (auto* set: m_descriptor_sets_after_fence) {
m_context.GetDescriptorCache().Recycle(*set);
}
m_descriptor_sets_after_fence.clear();
}
void CommandBuffer::Begin() const {
EXIT_IF(m_rendering);
void CommandBuffer::Begin() {
EXIT_IF(m_rendering || IsInvalid());
auto buffer = Handle();
vk::CommandBufferBeginInfo begin_info {};
begin_info.sType = vk::StructureType::eCommandBufferBeginInfo;
begin_info.pNext = nullptr;
begin_info.flags = {};
begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
begin_info.pInheritanceInfo = nullptr;
auto result = buffer.begin(&begin_info);
@@ -137,125 +64,6 @@ void CommandBuffer::SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0,
m_debug_arg4 = arg4;
}
void CommandBuffer::Execute(const SubmitInfo& submit) {
EXIT_IF(IsInvalid());
EXIT_IF(m_execute);
EXIT_IF(submit.num_wait_semaphores > SubmitInfo::MaxSemaphores ||
submit.num_signal_semaphores > SubmitInfo::MaxSemaphores);
auto buffer = Handle();
auto fence = m_slot->fence;
vk::TimelineSemaphoreSubmitInfo timeline_info {};
timeline_info.sType = vk::StructureType::eTimelineSemaphoreSubmitInfo;
timeline_info.waitSemaphoreValueCount = submit.num_wait_semaphores;
timeline_info.pWaitSemaphoreValues = submit.wait_ticks.data();
timeline_info.signalSemaphoreValueCount = submit.num_signal_semaphores;
timeline_info.pSignalSemaphoreValues = submit.signal_ticks.data();
vk::SubmitInfo submit_info {};
submit_info.sType = vk::StructureType::eSubmitInfo;
submit_info.pNext = &timeline_info;
submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
submit_info.pWaitSemaphores = submit.wait_semaphores.data();
submit_info.pWaitDstStageMask = submit.wait_stages.data();
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &buffer;
submit_info.signalSemaphoreCount = submit.num_signal_semaphores;
submit_info.pSignalSemaphores = submit.signal_semaphores.data();
auto& graphics = m_graphics;
EXIT_IF(graphics.queue == nullptr);
auto result = graphics.device.resetFences(1, &fence);
if (result != vk::Result::eSuccess) {
LOGF("vkResetFences failed before submit: %s (%d)\n", VulkanToString(result).c_str(),
static_cast<int>(result));
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
if (Config::GraphicsDebugDumpEnabled()) {
LOGF("vkQueueSubmit begin: slot=%u waits=%u signals=%u debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
m_slot->id, submit.num_wait_semaphores, submit.num_signal_semaphores, m_debug_op,
m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
}
{
Common::LockGuard lock(graphics.queue_mutex);
m_submit_seq = m_scheduler.NextSubmitSequence();
result = graphics.queue.submit(1, &submit_info, fence);
}
m_execute = true;
m_fence_waited = false;
if (result != vk::Result::eSuccess) {
LOGF("vkQueueSubmit failed: %s (%d), slot=%u submit_seq=%" PRIu64
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
VulkanToString(result).c_str(), static_cast<int>(result), m_slot->id, m_submit_seq,
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
}
void CommandBuffer::WaitForFence() {
FinalizeFence(false);
}
void CommandBuffer::WaitForFenceOnly() {
EXIT_IF(IsInvalid());
if (!m_execute || m_fence_waited) {
return;
}
auto device = m_graphics.device;
auto result = device.waitForFences(1, &m_slot->fence, VK_TRUE, UINT64_MAX);
if (result != vk::Result::eSuccess) {
LOGF("vkWaitForFences failed: %s (%d), slot=%u submit_seq=%" PRIu64
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
VulkanToString(result).c_str(), static_cast<int>(result), m_slot->id, m_submit_seq,
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
m_fence_waited = true;
}
void CommandBuffer::WaitForFenceAndReset() {
FinalizeFence(true);
}
void CommandBuffer::FinalizeFence(bool reset_recording) {
const bool was_executed = m_execute;
WaitForFenceOnly();
if (was_executed) {
m_execute = false;
m_fence_waited = false;
if (reset_recording) {
Common::LockGuard lock(*m_slot->pool_mutex);
m_slot->Reset();
}
}
if (was_executed) {
ReleaseResourcesAfterFence();
}
DeleteBuffersAfterFence();
}
void CommandBuffer::ReleaseResourcesAfterFence() {
RecycleDescriptorsAfterFence();
m_fence_resources.ReleaseAfterFence();
}
void CommandBuffer::DeleteBuffersAfterFence() {
for (const auto& buffer: m_retired_buffers) {
m_graphics.DeleteBuffer(*buffer);
}
m_retired_buffers.clear();
}
void CommandBuffer::BeginRendering(const RenderState& state) const {
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
state.num_color_attachments > RENDER_COLOR_ATTACHMENTS_MAX);
+68 -72
View File
@@ -27,7 +27,7 @@ static bool RenderTargetMaskHasMrt(uint32_t mask) {
return (mask & ~0x0fu) != 0;
}
static bool RenderTargetMaskHasBoundMrt(const RenderCommandBuffer& buffer) {
static bool RenderTargetMaskHasBoundMrt(const CommandBuffer& buffer) {
const auto& hw = buffer.GetRegisters();
const auto mask = hw.GetRenderTargetMask();
@@ -45,7 +45,7 @@ static bool RenderTargetMaskHasBoundMrt(const RenderCommandBuffer& buffer) {
return bound_targets > 1;
}
uint32_t render_target_first_bound_slot(const RenderCommandBuffer& buffer) {
uint32_t render_target_first_bound_slot(const CommandBuffer& buffer) {
const auto& hw = buffer.GetRegisters();
const auto mask = hw.GetRenderTargetMask();
for (uint32_t i = 0; i < 8; i++) {
@@ -71,14 +71,16 @@ void uc_print(const char* func, const HW::UserConfig& uc) {
LOGF("\t GetPrimType() = 0x%08" PRIx32 "\n"
"\t GetIndexOffset() = 0x%08" PRIx32 "\n"
"\t GetObjectId() = 0x%08" PRIx32 "\n"
"\t primitive_reset = 0x%08" PRIx32 "\n"
"\t primitive_group_size = 0x%04" PRIx16 "\n"
"\t vertex_group_size = 0x%04" PRIx16 "\n"
"\t en_user_vgpr1 = %s\n"
"\t en_user_vgpr2 = %s\n"
"\t en_user_vgpr3 = %s\n",
uc.GetPrimType(), uc.GetIndexOffset(), uc.GetObjectId(), ge_cntl.primitive_group_size,
ge_cntl.vertex_group_size, user_en.vgpr1 ? "true" : "false",
user_en.vgpr2 ? "true" : "false", user_en.vgpr3 ? "true" : "false");
static_cast<uint32_t>(uc.GetPrimType()), uc.GetIndexOffset(), uc.GetObjectId(),
uc.GetPrimitiveResetControl(), ge_cntl.primitive_group_size, ge_cntl.vertex_group_size,
user_en.vgpr1 ? "true" : "false", user_en.vgpr2 ? "true" : "false",
user_en.vgpr3 ? "true" : "false");
}
void uc_check(const HW::UserConfig& uc) {
@@ -141,11 +143,12 @@ std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt)
fmt::format("\t info.cmask_addr_type = 0x{:08x}\n", rt.info.cmask_addr_type));
dst.push_back(fmt::format("\t info.alt_tile_mode = {}\n",
rt.info.alt_tile_mode ? "true" : "false"));
dst.push_back(fmt::format("\t info.format = 0x{:08x}\n", rt.info.format));
dst.push_back(
fmt::format("\t info.channel_type = 0x{:08x}\n", rt.info.channel_type));
dst.push_back(
fmt::format("\t info.channel_order = 0x{:08x}\n", rt.info.channel_order));
dst.push_back(fmt::format("\t info.format = 0x{:08x}\n",
static_cast<uint32_t>(rt.info.format)));
dst.push_back(fmt::format("\t info.channel_type = 0x{:08x}\n",
static_cast<uint32_t>(rt.info.channel_type)));
dst.push_back(fmt::format("\t info.channel_order = 0x{:08x}\n",
static_cast<uint32_t>(rt.info.channel_order)));
dst.push_back(fmt::format("\t info.blend_bypa = {}\n",
rt.info.blend_bypass ? "true" : "false"));
dst.push_back(fmt::format("\t info.blend_clamp = {}\n",
@@ -154,10 +157,10 @@ std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt)
rt.info.round_mode ? "true" : "false"));
dst.push_back(fmt::format("\t attrib.force_dest_alpha_to_one = {}\n",
rt.attrib.force_dest_alpha_to_one ? "true" : "false"));
dst.push_back(
fmt::format("\t attrib.tile_mode = 0x{:08x}\n", rt.attrib.tile_mode));
dst.push_back(
fmt::format("\t attrib.fmask_tile_mode = 0x{:08x}\n", rt.attrib.fmask_tile_mode));
dst.push_back(fmt::format("\t attrib.tile_mode = 0x{:08x}\n",
static_cast<uint32_t>(rt.attrib.tile_mode)));
dst.push_back(fmt::format("\t attrib.fmask_tile_mode = 0x{:08x}\n",
static_cast<uint32_t>(rt.attrib.fmask_tile_mode)));
dst.push_back(
fmt::format("\t attrib.num_samples = 0x{:08x}\n", rt.attrib.num_samples));
dst.push_back(
@@ -168,8 +171,8 @@ std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt)
dst.push_back(
fmt::format("\t attrib2.num_mip_levels = 0x{:08x}\n", rt.attrib2.num_mip_levels));
dst.push_back(fmt::format("\t attrib3.depth = 0x{:08x}\n", rt.attrib3.depth));
dst.push_back(
fmt::format("\t attrib3.tile_mode = 0x{:08x}\n", rt.attrib3.tile_mode));
dst.push_back(fmt::format("\t attrib3.tile_mode = 0x{:08x}\n",
static_cast<uint32_t>(rt.attrib3.tile_mode)));
dst.push_back(
fmt::format("\t attrib3.dimension = 0x{:08x}\n", rt.attrib3.dimension));
dst.push_back(fmt::format("\t attrib3.cmask_pipe_aligned = {}\n",
@@ -212,22 +215,22 @@ std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt)
return dst;
}
static bool RenderIsColorTileMode(uint32_t tile_mode) {
static bool RenderIsColorTileMode(Prospero::TileMode tile_mode) {
// AGC CxRenderTarget::TileMode shifted by CB_COLOR*_ATTRIB3.COLOR_SW_MODE.
switch (tile_mode) {
case Prospero::GpuEnumValue(Prospero::TileMode::kLinear):
case Prospero::GpuEnumValue(Prospero::TileMode::kStandard256B):
case Prospero::GpuEnumValue(Prospero::TileMode::kStandard4KB):
case Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB):
case Prospero::GpuEnumValue(Prospero::TileMode::kPrt):
case Prospero::GpuEnumValue(Prospero::TileMode::kDepth):
case Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget): return true;
case Prospero::TileMode::kLinear:
case Prospero::TileMode::kStandard256B:
case Prospero::TileMode::kStandard4KB:
case Prospero::TileMode::kStandard64KB:
case Prospero::TileMode::kPrt:
case Prospero::TileMode::kDepth:
case Prospero::TileMode::kRenderTarget: return true;
default: return false;
}
}
bool RenderIsColorTileModeLinear(uint32_t tile_mode) {
return tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kLinear);
bool RenderIsColorTileModeLinear(Prospero::TileMode tile_mode) {
return tile_mode == Prospero::TileMode::kLinear;
}
static bool RenderIsColorDimension(uint32_t dimension) {
@@ -263,20 +266,23 @@ static void RtCheck(const HW::RenderTarget& rt) {
logged = true;
}
}
const bool native_2x_one_frag =
rt.info.fmask_compression_enable && !rt.info.fmask_data_compression_disable &&
rt.info.cmask_fast_clear_enable && rt.fmask.addr != 0 && rt.cmask.addr != 0 &&
rt.attrib.num_samples == 1 && rt.attrib.num_fragments == 1;
EXIT_NOT_IMPLEMENTED(rt.info.fmask_one_frag_mode && !native_2x_one_frag);
if (rt.info.fmask_compression_enable) {
EXIT_NOT_IMPLEMENTED(rt.attrib.num_samples == 0 && rt.attrib.num_fragments == 0);
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: ignoring PS5 FMASK metadata for single-sample MSAA "
"fallback, fmask=0x%016" PRIx64 "\n",
LOGF("RenderTarget: using native Vulkan MSAA without guest FMASK metadata, "
"fmask=0x%016" PRIx64 "\n",
rt.fmask.addr);
logged = true;
}
}
// EXIT_NOT_IMPLEMENTED(rt.info.fmask_compression_mode != 0x00000000);
EXIT_NOT_IMPLEMENTED(rt.info.fmask_data_compression_disable != false);
EXIT_NOT_IMPLEMENTED(rt.info.fmask_one_frag_mode != false);
if (rt.info.cmask_fast_clear_enable || rt.info.dcc_compression_enable) {
static bool logged = false;
@@ -361,7 +367,8 @@ static void RtCheck(const HW::RenderTarget& rt) {
}
}
if (!RenderIsColorTileMode(rt.attrib3.tile_mode)) {
EXIT("unknown PS5 render-target tile mode: 0x%08" PRIx32 "\n", rt.attrib3.tile_mode);
EXIT("unknown PS5 render-target tile mode: 0x%08" PRIx32 "\n",
static_cast<uint32_t>(rt.attrib3.tile_mode));
}
if (!RenderIsColorDimension(rt.attrib3.dimension)) {
EXIT("unknown PS5 render-target dimension: 0x%08" PRIx32 "\n", rt.attrib3.dimension);
@@ -456,13 +463,14 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
"\t height = 0x%08" PRIx32 "\n"
"\t size.x_max = 0x%04" PRIx16 "\n"
"\t size.y_max = 0x%04" PRIx16 "\n",
z.z_info.format, z.z_info.num_samples,
Prospero::GpuEnumValue(z.z_info.texture_compatibility),
static_cast<uint32_t>(z.z_info.format), z.z_info.num_samples,
static_cast<uint32_t>(z.z_info.texture_compatibility),
z.z_info.htile_acceleration ? "true" : "false",
z.z_info.expclear_enabled ? "true" : "false",
Prospero::GpuEnumValue(z.z_info.z_compare_base),
static_cast<uint32_t>(z.z_info.z_compare_base),
z.z_info.partially_resident ? "true" : "false", z.z_info.max_mip_level,
z.stencil_info.format, Prospero::GpuEnumValue(z.stencil_info.texture_compatibility),
static_cast<uint32_t>(z.stencil_info.format),
static_cast<uint32_t>(z.stencil_info.texture_compatibility),
z.stencil_info.htile_stencil_disabled ? "true" : "false",
z.stencil_info.expclear_enabled ? "true" : "false",
z.stencil_info.partially_resident ? "true" : "false", z.depth_info.addr5_swizzle_mask,
@@ -491,15 +499,16 @@ static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
EXIT_NOT_IMPLEMENTED(!z.z_info.HasValidTextureCompatibility());
EXIT_NOT_IMPLEMENTED(!z.stencil_info.HasValidTextureCompatibility());
if (z.z_info.format == 0) {
EXIT_NOT_IMPLEMENTED(z.z_info.format != 0);
if (z.z_info.format == Prospero::DepthFormat::kInvalid) {
EXIT_NOT_IMPLEMENTED(z.z_info.format != Prospero::DepthFormat::kInvalid);
EXIT_NOT_IMPLEMENTED(z.z_info.num_samples != 0);
EXIT_NOT_IMPLEMENTED(z.z_info.htile_acceleration != false);
EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false);
EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false);
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
} else {
EXIT_NOT_IMPLEMENTED(z.z_info.format != 0x00000001 && z.z_info.format != 0x00000003);
EXIT_NOT_IMPLEMENTED(z.z_info.format != Prospero::DepthFormat::kZ16 &&
z.z_info.format != Prospero::DepthFormat::kZ32F);
if (z.z_info.num_samples != 0x00000000) {
static bool logged = false;
if (!logged) {
@@ -513,16 +522,17 @@ static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
}
if (z.stencil_info.format == 0) {
if (z.stencil_info.format == Prospero::StencilFormat::kInvalid) {
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
} else {
EXIT_NOT_IMPLEMENTED(z.stencil_info.format != 0x00000001);
EXIT_NOT_IMPLEMENTED(z.stencil_info.format != Prospero::StencilFormat::k8UInt);
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
}
if (z.z_info.format != 0 || z.stencil_info.format != 0) {
if (z.z_info.format != Prospero::DepthFormat::kInvalid ||
z.stencil_info.format != Prospero::StencilFormat::kInvalid) {
EXIT_NOT_IMPLEMENTED(z.depth_info.addr5_swizzle_mask != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.depth_info.array_mode != 0x00000000);
@@ -554,13 +564,11 @@ static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
z.depth_view.stencil_write_disable ? "true" : "false");
}
}
EXIT_NOT_IMPLEMENTED(z.z_read_base_addr != z.z_write_base_addr);
EXIT_NOT_IMPLEMENTED(z.stencil_read_base_addr != z.stencil_write_base_addr);
EXIT_NOT_IMPLEMENTED(z.z_write_base_addr == 0);
if (z.stencil_info.format != 0 && z.stencil_write_base_addr == 0) {
LOGF("\t warning: stencil format is set without a stencil base address, continuing "
"without stencil attachment\n");
}
EXIT_NOT_IMPLEMENTED(!z.depth_view.depth_write_disable &&
z.z_read_base_addr != z.z_write_base_addr);
EXIT_NOT_IMPLEMENTED(!z.depth_view.stencil_write_disable &&
z.stencil_read_base_addr != z.stencil_write_base_addr);
EXIT_NOT_IMPLEMENTED(!z.depth_view.depth_write_disable && z.z_write_base_addr == 0);
// EXIT_NOT_IMPLEMENTED(z.pitch_div8_minus1 != 0x000000ff);
// EXIT_NOT_IMPLEMENTED(z.height_div8_minus1 != 0x0000008f);
// EXIT_NOT_IMPLEMENTED(z.slice_div64_minus1 != 0x00008fff);
@@ -609,8 +617,7 @@ static void ClipCheck(const HW::ClipControl& c) {
// dx_linear_attr_clip_enable preserves linear (noperspective) attributes at clip-generated
// vertices, which Vulkan provides as part of clipping and interpolation.
EXIT_NOT_IMPLEMENTED(c.user_clip_planes != 0 || c.user_clip_plane_mode != 0 ||
c.vertex_kill_any || !c.IsZClipModeRepresentable() ||
c.user_clip_plane_negate_y || c.clip_disable ||
c.vertex_kill_any || c.user_clip_plane_negate_y || c.clip_disable ||
c.user_clip_plane_cull_only || c.cull_on_clipping_error_disable ||
c.force_viewport_index_from_vs_enable);
}
@@ -634,17 +641,6 @@ static void RcPrint(const char* func, const HW::RenderControl& c) {
static void RcCheck(const HW::RenderControl& c) {
// EXIT_NOT_IMPLEMENTED(c.depth_clear_enable != false);
// EXIT_NOT_IMPLEMENTED(c.stencil_clear_enable != false);
if ((c.resummarize_enable || c.copy_centroid || c.copy_sample != 0)) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 16) {
LOGF("\t warning: unsupported PS5 render-control metadata state, continuing: "
"resummarize=%s copy_centroid=%s copy_sample=%" PRIu8 "\n",
c.resummarize_enable ? "true" : "false", c.copy_centroid ? "true" : "false",
c.copy_sample);
}
return;
}
EXIT_NOT_IMPLEMENTED(c.resummarize_enable != false);
// EXIT_NOT_IMPLEMENTED(c.stencil_compress_disable != false);
// EXIT_NOT_IMPLEMENTED(c.depth_compress_disable != false);
EXIT_NOT_IMPLEMENTED(c.copy_centroid != false);
@@ -892,12 +888,10 @@ void LogDrawPhase(const char* draw_name, const char* phase) {
}
}
void LogPipelineTrace(const char* phase, uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0,
uint32_t ps_crc32) {
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id) {
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: %s VS=0x%08" PRIx32 "/0x%08" PRIx32 " PS=0x%08" PRIx32 "/0x%08" PRIx32
"\n",
phase, vs_hash0, vs_crc32, ps_hash0, ps_crc32);
LOGF("PipelineTrace: %s VS=%" PRIu64 " PS=%" PRIu64 "\n", phase, vertex_program_id,
pixel_program_id);
}
}
@@ -1161,7 +1155,7 @@ ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeC
return ScissorRectClamp(final, extent.width, extent.height);
}
void hw_check(const RenderCommandBuffer& buffer) {
void hw_check(const CommandBuffer& buffer) {
const auto& hw = buffer.GetRegisters();
const auto rt_slot = render_target_first_bound_slot(buffer);
const auto& rt = hw.GetRenderTarget(rt_slot);
@@ -1218,8 +1212,9 @@ void hw_check(const RenderCommandBuffer& buffer) {
const auto& mrt = hw.GetRenderTarget(i);
LOGF("\t RT%u addr=0x%010" PRIx64 " mask=0x%x fmt=0x%08" PRIx32
" width=%u height=%u tile=%u\n",
i, mrt.base.addr, (hw.GetRenderTargetMask() >> (i * 4u)) & 0x0fu, mrt.info.format,
mrt.attrib2.width + 1, mrt.attrib2.height + 1, mrt.attrib3.tile_mode);
i, mrt.base.addr, (hw.GetRenderTargetMask() >> (i * 4u)) & 0x0fu,
static_cast<uint32_t>(mrt.info.format), mrt.attrib2.width + 1,
mrt.attrib2.height + 1, static_cast<uint32_t>(mrt.attrib3.tile_mode));
}
}
if (rc.depth_clear_enable && hw.GetDepthClearValue() != 0.0f &&
@@ -1233,7 +1228,7 @@ void hw_check(const RenderCommandBuffer& buffer) {
// EXIT_NOT_IMPLEMENTED(hw.GetStencilClearValue() != 0);
}
void hw_print(const RenderCommandBuffer& buffer) {
void hw_print(const CommandBuffer& buffer) {
const auto& hw = buffer.GetRegisters();
const auto rt_slot = render_target_first_bound_slot(buffer);
const auto& rt = hw.GetRenderTarget(rt_slot);
@@ -1258,9 +1253,10 @@ void hw_print(const RenderCommandBuffer& buffer) {
"\t GetRenderTargetMask() = 0x%08" PRIx32 "\n"
"\t GetDepthClearValue() = %f\n"
"\t GetStencilClearValue() = %" PRIu8 "\n"
"\t GetLineWidth() = %f\n",
"\t GetLineWidth() = %f\n"
"\t primitive_reset_index = 0x%08" PRIx32 "\n",
hw.GetRenderTargetMask(), hw.GetDepthClearValue(), hw.GetStencilClearValue(),
hw.GetLineWidth());
hw.GetLineWidth(), hw.GetPrimitiveResetIndex());
LOGF("%s", Common::Concat(rt_print("RenderTraget:", rt), "").c_str());
+5 -5
View File
@@ -9,7 +9,7 @@
namespace Libs::Graphics {
class RenderCommandBuffer;
class CommandBuffer;
namespace HW {
class Context;
@@ -28,15 +28,15 @@ struct ScissorRect {
};
uint32_t render_target_mask_slot(uint32_t mask, uint32_t slot);
uint32_t render_target_first_bound_slot(const RenderCommandBuffer& buffer);
uint32_t render_target_first_bound_slot(const CommandBuffer& buffer);
bool graphics_debug_dump_enabled();
void uc_print(const char* func, const HW::UserConfig& uc);
void uc_check(const HW::UserConfig& uc);
void sh_print(const char* func, const HW::Shader& uc);
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt);
bool RenderIsColorTileModeLinear(uint32_t tile_mode);
void hw_print(const RenderCommandBuffer& buffer);
void hw_check(const RenderCommandBuffer& buffer);
bool RenderIsColorTileModeLinear(Prospero::TileMode tile_mode);
void hw_print(const CommandBuffer& buffer);
void hw_check(const CommandBuffer& buffer);
void LogDrawPhase(const char* draw_name, const char* phase);
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
vk::Extent2D extent);
@@ -13,7 +13,6 @@
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
@@ -67,13 +66,12 @@ static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t
}
static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
return fail == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp) ||
pass == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp) ||
depth_fail == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp);
constexpr auto replace_op = static_cast<uint8_t>(Prospero::StencilOp::kReplaceOp);
return fail == replace_op || pass == replace_op || depth_fail == replace_op;
}
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const RenderCommandBuffer& buffer,
const DepthFormatPolicy& policy,
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const CommandBuffer& buffer,
const DepthFormatPolicy& policy,
bool has_stencil,
uint32_t samples) {
auto& graphics = buffer.GetGraphics();
@@ -99,19 +97,18 @@ static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderDepthInfo& r) {
KYTY_PROFILER_FUNCTION();
(void)submit_id;
const auto& hw = buffer.GetRegisters();
const auto& z = hw.GetDepthRenderTarget();
const auto& rc = hw.GetRenderControl();
const auto& dc = hw.GetDepthControl();
const auto& sc = hw.GetStencilControl();
const auto& sm = hw.GetStencilMask();
const bool has_stencil =
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
const bool depth_active =
const auto& hw = buffer.GetRegisters();
const auto& z = hw.GetDepthRenderTarget();
const auto& rc = hw.GetRenderControl();
const auto& dc = hw.GetDepthControl();
const auto& sc = hw.GetStencilControl();
const auto& sm = hw.GetStencilMask();
const bool has_stencil = z.stencil_info.format != Prospero::StencilFormat::kInvalid;
const bool depth_active =
dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable || rc.depth_clear_enable;
const bool stencil_active = has_stencil && (dc.stencil_enable || rc.stencil_clear_enable);
if (!depth_active && !stencil_active) {
@@ -122,9 +119,8 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
DepthFatal("invalid PS5 depth texture-compatibility encoding");
}
const bool attachment_unbound =
z.z_info.format == Prospero::GpuEnumValue(Prospero::DepthFormat::kInvalid) &&
z.stencil_info.format == Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid) &&
z.z_info.num_samples == 0 &&
z.z_info.format == Prospero::DepthFormat::kInvalid &&
z.stencil_info.format == Prospero::StencilFormat::kInvalid && z.z_info.num_samples == 0 &&
z.z_info.texture_compatibility == Prospero::TextureCompatiblePlaneCompression::kDisable &&
!z.z_info.expclear_enabled && !z.z_info.partially_resident && z.z_info.max_mip_level == 0 &&
z.stencil_info.texture_compatibility == Prospero::TextureCompatibleStencil::kDisable &&
@@ -169,27 +165,28 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
z.depth_view.slice_max);
}
if (rc.resummarize_enable || rc.copy_centroid || rc.copy_sample != 0 ||
z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
z.z_info.partially_resident || z.stencil_info.partially_resident ||
z.z_info.max_mip_level != 0 || z.depth_view.current_mip_level != 0 ||
z.depth_info.addr5_swizzle_mask != 0 || z.depth_info.array_mode != 0 ||
z.depth_info.pipe_config != 0 || z.depth_info.bank_width != 0 ||
z.depth_info.bank_height != 0 || z.depth_info.macro_tile_aspect != 0 ||
z.depth_info.num_banks != 0 || z.htile_surface.linear != 0 ||
z.htile_surface.full_cache != 0 || z.htile_surface.htile_uses_preload_win != 0 ||
z.htile_surface.preload != 0 || z.htile_surface.prefetch_width != 0 ||
z.htile_surface.prefetch_height != 0 || z.htile_surface.dst_outside_zero_to_one != 0 ||
z.z_read_base_addr == 0 || z.z_write_base_addr != z.z_read_base_addr ||
if (rc.copy_centroid || rc.copy_sample != 0 || z.z_info.expclear_enabled ||
z.stencil_info.expclear_enabled || z.z_info.partially_resident ||
z.stencil_info.partially_resident || z.z_info.max_mip_level != 0 ||
z.depth_view.current_mip_level != 0 || z.depth_info.addr5_swizzle_mask != 0 ||
z.depth_info.array_mode != 0 || z.depth_info.pipe_config != 0 ||
z.depth_info.bank_width != 0 || z.depth_info.bank_height != 0 ||
z.depth_info.macro_tile_aspect != 0 || z.depth_info.num_banks != 0 ||
z.htile_surface.linear != 0 || z.htile_surface.full_cache != 0 ||
z.htile_surface.htile_uses_preload_win != 0 || z.htile_surface.preload != 0 ||
z.htile_surface.prefetch_width != 0 || z.htile_surface.prefetch_height != 0 ||
z.htile_surface.dst_outside_zero_to_one != 0 || z.z_read_base_addr == 0 ||
(!z.depth_view.depth_write_disable && z.z_write_base_addr != z.z_read_base_addr) ||
(z.z_read_base_addr & 0xffffu) != 0 ||
dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways)) {
DepthFatal("unsupported depth register state");
}
if (has_stencil) {
if (z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::k8UInt) ||
!htile_stencil_compat || z.stencil_read_base_addr == 0 ||
z.stencil_write_base_addr != z.stencil_read_base_addr ||
(z.stencil_read_base_addr & 0xffffu) != 0 || z.depth_view.stencil_write_disable) {
if (z.stencil_info.format != Prospero::StencilFormat::k8UInt || !htile_stencil_compat ||
z.stencil_read_base_addr == 0 ||
(!z.depth_view.stencil_write_disable &&
z.stencil_write_base_addr != z.stencil_read_base_addr) ||
(z.stencil_read_base_addr & 0xffffu) != 0) {
DepthFatal("unsupported stencil attachment state");
}
} else if (z.stencil_read_base_addr != 0 || z.stencil_write_base_addr != 0 ||
@@ -205,8 +202,6 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
if (z.depth_view.slice_max >= 32) {
DepthFatal("HTile clear tracking supports at most 32 slices");
}
} else if (z.htile_data_base_addr != 0) {
DepthFatal("HTile address without an enabled tile surface");
}
const bool size_xy_valid = z.size.valid;
const bool wh_valid = z.width_height_valid && z.width != 0 && z.height != 0;
@@ -231,7 +226,7 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
DepthFatal("no host depth/stencil format supports required usage for %s",
VulkanToString(ideal_format).c_str());
}
const uint32_t guest_format = Prospero::GpuEnumValue(policy->guest_format);
const auto guest_format = policy->guest_format;
const uint32_t bytes = policy->bytes_per_element;
const auto pitch = TileGetDepthPitch(width, bytes, z.z_info.num_samples);
if (z.pitch_height_valid && ((static_cast<uint64_t>(z.pitch_div8_minus1) + 1u) * 8u != pitch ||
@@ -292,18 +287,22 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
r.depth_min_bounds = hw.GetDepthBoundsMin();
r.depth_max_bounds = hw.GetDepthBoundsMax();
r.stencil_clear_enable = has_stencil && rc.stencil_clear_enable;
r.stencil_clear_value = hw.GetStencilClearValue();
r.stencil_test_enable = has_stencil && dc.stencil_enable;
r.stencil_clear_enable =
has_stencil && rc.stencil_clear_enable && !z.depth_view.stencil_write_disable;
r.stencil_clear_value = hw.GetStencilClearValue();
r.stencil_test_enable = has_stencil && dc.stencil_enable;
if (r.stencil_test_enable) {
const uint8_t front_write_mask = rc.stencil_clear_enable ? 0 : sm.stencil_writemask;
const uint8_t back_write_mask = rc.stencil_clear_enable ? 0 : sm.stencil_writemask_bf;
const bool stencil_ops_disabled =
rc.stencil_clear_enable || z.depth_view.stencil_write_disable;
const uint8_t front_write_mask = stencil_ops_disabled ? 0 : sm.stencil_writemask;
const uint8_t back_write_mask = stencil_ops_disabled ? 0 : sm.stencil_writemask_bf;
if (dc.stencilfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
(dc.backface_enable &&
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways)) ||
(UsesStencilOpValue(sc.stencil_fail, sc.stencil_zpass, sc.stencil_zfail) &&
(front_write_mask != 0 &&
UsesStencilOpValue(sc.stencil_fail, sc.stencil_zpass, sc.stencil_zfail) &&
sm.stencil_opval != sm.stencil_testval) ||
(dc.backface_enable &&
(dc.backface_enable && back_write_mask != 0 &&
UsesStencilOpValue(sc.stencil_fail_bf, sc.stencil_zpass_bf, sc.stencil_zfail_bf) &&
sm.stencil_opval_bf != sm.stencil_testval_bf)) {
DepthFatal("unsupported stencil compare or replacement state");
@@ -345,7 +344,7 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes;
desc.info.samples = samples;
desc.info.tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kDepth);
desc.info.tile_mode = Prospero::TileMode::kDepth;
desc.info.mip_layout[0] = {0, r.depth_buffer_size, pitch, height};
desc.info.metadata.range = {r.htile_buffer_vaddr, r.htile_buffer_size};
desc.info.metadata.kind = has_htile ? ImageMetadataKind::Htile : ImageMetadataKind::None;
@@ -11,8 +11,6 @@
namespace Libs::Graphics {
class RenderCommandBuffer;
inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stencil, bool has_htile,
bool htile_stencil_disabled) {
return htile_stencil_disabled || (has_stencil && has_htile);
+9 -14
View File
@@ -646,13 +646,13 @@ void Validate(const ImageInfo& info) {
}
}
uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element) {
Prospero::BufferFormat RenderTargetTransferFormat(uint32_t bytes_per_element) {
switch (bytes_per_element) {
case 1: return Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm);
case 2: return Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm);
case 4: return Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float);
case 8: return Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float);
case 16: return Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32Float);
case 1: return Prospero::BufferFormat::k8UNorm;
case 2: return Prospero::BufferFormat::k16UNorm;
case 4: return Prospero::BufferFormat::k32Float;
case 8: return Prospero::BufferFormat::k16_16_16_16Float;
case 16: return Prospero::BufferFormat::k32_32_32_32Float;
default: EXIT("unsupported render-target element size: %u\n", bytes_per_element);
}
}
@@ -736,15 +736,10 @@ Image::~Image() {
if (m_graphics == nullptr) {
return;
}
{
std::lock_guard lock(views.mutex);
for (auto& cached: views.views) {
if (cached.view != nullptr) {
m_graphics->device.destroyImageView(cached.view, nullptr);
cached.view = nullptr;
}
for (const auto& cached: views) {
if (cached.view != nullptr) {
m_graphics->device.destroyImageView(cached.view, nullptr);
}
views.views.clear();
}
if (backing.image != nullptr) {
m_graphics->DeleteImage(backing);
+6 -21
View File
@@ -2,12 +2,12 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#include "common/assert.h"
#include "common/slotVector.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image/imageInfo.h"
#include <compare>
#include <limits>
#include <mutex>
#include <optional>
#include <span>
#include <utility>
@@ -19,29 +19,13 @@ class Buffer;
class CommandScheduler;
struct ImageTestAccess;
struct ImageId {
uint32_t index = std::numeric_limits<uint32_t>::max();
uint32_t generation = 0;
[[nodiscard]] explicit operator bool() const noexcept {
return index != std::numeric_limits<uint32_t>::max();
}
auto operator<=>(const ImageId&) const = default;
};
using ImageId = Common::SlotId;
struct CachedImageView {
ImageViewInfo info;
vk::ImageView view = nullptr;
};
struct ImageViewCache {
ImageViewCache() = default;
KYTY_CLASS_NO_COPY(ImageViewCache);
std::mutex mutex;
std::vector<CachedImageView> views;
};
struct ImageUsage {
bool texture = false;
bool storage = false;
@@ -55,6 +39,7 @@ struct ImageBinding {
bool is_target = false;
bool needs_rebind = false;
bool force_general = false;
bool shader_write = false;
};
class Image final {
@@ -156,7 +141,7 @@ public:
ImageInfo info;
VulkanImage backing;
ImageViewCache views;
std::vector<CachedImageView> views;
ImageUsage usage;
ImageBinding binding;
bool registered = false;
@@ -188,8 +173,8 @@ private:
namespace ImageOps {
void Validate(const ImageInfo& info);
[[nodiscard]] uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element);
void Validate(const ImageInfo& info);
[[nodiscard]] Prospero::BufferFormat RenderTargetTransferFormat(uint32_t bytes_per_element);
} // namespace ImageOps
@@ -67,14 +67,14 @@ struct ImageInfo {
ImageMetadataInfo metadata;
uint32_t htile_clear_mask = UINT32_MAX;
vk::Format pixel_format = vk::Format::eUndefined;
uint32_t guest_format = 0;
Prospero::BufferFormat guest_format = Prospero::BufferFormat::kInvalid;
Prospero::ImageType type = Prospero::ImageType::kColor2D;
vk::Extent3D extent = {1, 1, 1};
ImageSubresources resources;
uint32_t pitch = 0;
uint32_t bytes_per_block = 0;
uint32_t samples = 1;
uint32_t tile_mode = 0;
Prospero::TileMode tile_mode = Prospero::TileMode::kLinear;
bool bgra16 = false;
std::array<ImageMipInfo, 16> mip_layout {};
@@ -86,9 +86,7 @@ struct ImageInfo {
[[nodiscard]] bool IsBlock() const noexcept {
return Prospero::BlockCompressedBytesPerBlock(guest_format) != 0;
}
[[nodiscard]] bool IsTiled() const noexcept {
return tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kLinear);
}
[[nodiscard]] bool IsTiled() const noexcept { return tile_mode != Prospero::TileMode::kLinear; }
[[nodiscard]] constexpr bool IsVolume() const noexcept {
return type == Prospero::ImageType::kColor3D;
}
@@ -214,7 +212,6 @@ struct DepthFormatPolicy {
Prospero::DepthFormat depth_format;
Prospero::BufferFormat guest_format;
uint32_t bytes_per_element;
vk::Format sampled_view_format;
vk::Format depth_attachment_format;
std::array<vk::Format, 3> stencil_attachment_formats;
};
@@ -223,21 +220,19 @@ inline constexpr std::array<DepthFormatPolicy, 2> DEPTH_FORMAT_POLICIES {{
{Prospero::DepthFormat::kZ16,
Prospero::BufferFormat::k16UNorm,
2,
vk::Format::eR16Unorm,
vk::Format::eD16Unorm,
{vk::Format::eD16UnormS8Uint, vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint}},
{Prospero::DepthFormat::kZ32F,
Prospero::BufferFormat::k32Float,
4,
vk::Format::eR32Sfloat,
vk::Format::eD32Sfloat,
{vk::Format::eD32SfloatS8Uint, vk::Format::eUndefined, vk::Format::eUndefined}},
}};
[[nodiscard]] inline constexpr const DepthFormatPolicy*
FindDepthFormatPolicy(uint32_t depth_format) noexcept {
FindDepthFormatPolicy(Prospero::DepthFormat depth_format) noexcept {
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
if (Prospero::GpuEnumValue(policy.depth_format) == depth_format) {
if (policy.depth_format == depth_format) {
return &policy;
}
}
@@ -245,9 +240,9 @@ FindDepthFormatPolicy(uint32_t depth_format) noexcept {
}
[[nodiscard]] inline constexpr const DepthFormatPolicy*
FindGuestDepthFormatPolicy(uint32_t guest_format) noexcept {
FindGuestDepthFormatPolicy(Prospero::BufferFormat guest_format) noexcept {
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
if (Prospero::GpuEnumValue(policy.guest_format) == guest_format) {
if (policy.guest_format == guest_format) {
return &policy;
}
}
@@ -269,10 +264,11 @@ FindGuestDepthFormatPolicy(uint32_t guest_format) noexcept {
return has_stencil ? policy.stencil_attachment_formats.front() : policy.depth_attachment_format;
}
[[nodiscard]] inline constexpr vk::Format DepthAttachmentFormat(uint32_t depth_format,
uint32_t stencil_format) noexcept {
[[nodiscard]] inline constexpr vk::Format
DepthAttachmentFormat(Prospero::DepthFormat depth_format,
Prospero::StencilFormat stencil_format) noexcept {
bool has_stencil = false;
switch (static_cast<Prospero::StencilFormat>(stencil_format)) {
switch (stencil_format) {
case Prospero::StencilFormat::kInvalid: break;
case Prospero::StencilFormat::k8UInt: has_stencil = true; break;
default: return vk::Format::eUndefined;
@@ -318,26 +314,6 @@ inline bool ImageInfo::IsDepth() const noexcept {
return std::bit_cast<uint32_t>(static_cast<float>(value) / 65535.0f);
}
[[nodiscard]] inline constexpr bool IsSupportedSampledDepthFormat(vk::Format image_format,
uint32_t guest_format,
vk::Format view_format) noexcept {
const auto* policy = FindGuestDepthFormatPolicy(guest_format);
return policy != nullptr && view_format == policy->sampled_view_format &&
(image_format == policy->depth_attachment_format ||
IsStencilAttachmentFormat(*policy, image_format));
}
[[nodiscard]] inline constexpr bool IsSupportedSampledDepthFormat(vk::Format image_format,
vk::Format view_format) noexcept {
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
if (IsSupportedSampledDepthFormat(image_format, Prospero::GpuEnumValue(policy.guest_format),
view_format)) {
return true;
}
}
return false;
}
[[nodiscard]] inline constexpr bool IsSupportedDepthTargetFormat(const ImageInfo& info) {
const auto* policy = FindGuestDepthFormatPolicy(info.guest_format);
return policy != nullptr && info.bytes_per_block == policy->bytes_per_element &&
@@ -383,10 +359,10 @@ CanUseVideoOutNativeWithoutUpload(VideoOutCompression compression, bool render_t
}
struct VideoOutPixelFormatInfo {
vk::Format format = vk::Format::eUndefined;
uint32_t guest_format = 0;
uint32_t bytes_per_element = 0;
bool bgra16 = false;
vk::Format format = vk::Format::eUndefined;
Prospero::BufferFormat guest_format = Prospero::BufferFormat::kInvalid;
uint32_t bytes_per_element = 0;
bool bgra16 = false;
};
struct VideoOutFormatPolicy {
@@ -396,23 +372,17 @@ struct VideoOutFormatPolicy {
inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {{
{0x8000000022000000ull,
{vk::Format::eR8G8B8A8Srgb, Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb), 4,
false}},
{vk::Format::eR8G8B8A8Srgb, Prospero::BufferFormat::k8_8_8_8Srgb, 4, false}},
{0x8000000000000000ull,
{vk::Format::eB8G8R8A8Srgb, Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb), 4,
false}},
{vk::Format::eB8G8R8A8Srgb, Prospero::BufferFormat::k8_8_8_8Srgb, 4, false}},
{0x8100000022000000ull,
{vk::Format::eA2B10G10R10UnormPack32,
Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm), 4, false}},
{vk::Format::eA2B10G10R10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
{0x8100000000000000ull,
{vk::Format::eA2R10G10B10UnormPack32,
Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm), 4, false}},
{vk::Format::eA2R10G10B10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
{0xc001000622000000ull,
{vk::Format::eR16G16B16A16Sfloat,
Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float), 8, false}},
{vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, false}},
{0xc001000600000000ull,
{vk::Format::eR16G16B16A16Sfloat,
Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float), 8, true}},
{vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, true}},
}};
[[nodiscard]] inline bool DecodeVideoOutPixelFormat(uint64_t pixel_format,
@@ -439,15 +409,15 @@ inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {
}
[[nodiscard]] inline constexpr bool
IsSupportedDisplayRenderTargetTileMode(uint32_t tile_mode) noexcept {
return tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
IsSupportedDisplayRenderTargetTileMode(Prospero::TileMode tile_mode) noexcept {
return tile_mode == Prospero::TileMode::kRenderTarget;
}
[[nodiscard]] inline constexpr bool IsSupportedStandard64RenderTarget(const ImageInfo& info) {
if (info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB) ||
info.data.address == 0 || (info.data.address & 0xffffu) != 0 || info.extent.width == 0 ||
info.extent.height == 0 || info.bytes_per_block != 4 || info.resources.levels != 1 ||
info.resources.layers != 1 || info.samples != 1) {
if (info.tile_mode != Prospero::TileMode::kStandard64KB || info.data.address == 0 ||
(info.data.address & 0xffffu) != 0 || info.extent.width == 0 || info.extent.height == 0 ||
info.bytes_per_block != 4 || info.resources.levels != 1 || info.resources.layers != 1 ||
info.samples != 1) {
return false;
}
const auto expected_pitch =
@@ -460,7 +430,7 @@ IsSupportedDisplayRenderTargetTileMode(uint32_t tile_mode) noexcept {
}
[[nodiscard]] inline constexpr bool IsTiledRenderTarget(const ImageInfo& info) noexcept {
return info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) ||
return info.tile_mode == Prospero::TileMode::kRenderTarget ||
IsSupportedStandard64RenderTarget(info);
}
@@ -4,8 +4,6 @@
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include <mutex>
namespace Libs::Graphics {
namespace {
@@ -36,17 +34,6 @@ namespace {
}
}
[[nodiscard]] bool IsDepthViewFormat(vk::Format format) {
switch (format) {
case vk::Format::eD16Unorm:
case vk::Format::eR16Unorm:
case vk::Format::eD32Sfloat:
case vk::Format::eR32Sfloat:
case vk::Format::eR32Uint: return true;
default: return false;
}
}
[[nodiscard]] bool IsValidViewType(const VulkanImage& image, const ImageViewInfo& info) {
switch (image.image_type) {
case vk::ImageType::e1D:
@@ -327,13 +314,13 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
const auto& image = backing;
auto normalized = view_info;
const bool is_storage = static_cast<bool>(normalized.usage & vk::ImageUsageFlagBits::eStorage);
normalized.aspect = FullAspectMask(image.format);
if (normalized.aspect & vk::ImageAspectFlagBits::eDepth &&
IsDepthViewFormat(normalized.format)) {
const auto image_aspect = FullAspectMask(image.format);
if (image_aspect & vk::ImageAspectFlagBits::eDepth &&
ImageViewOps::IsFormatDepthCompatible(normalized.format)) {
normalized.format = image.format;
normalized.aspect = vk::ImageAspectFlagBits::eDepth;
}
if (normalized.aspect & vk::ImageAspectFlagBits::eStencil &&
if (image_aspect & vk::ImageAspectFlagBits::eStencil &&
IsStencilViewFormat(normalized.format)) {
normalized.format = image.format;
normalized.aspect = vk::ImageAspectFlagBits::eStencil;
@@ -368,8 +355,7 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
image.layers);
}
std::lock_guard lock(views.mutex);
for (const auto& cached: views.views) {
for (const auto& cached: views) {
if (cached.info == normalized) {
return cached.view;
}
@@ -405,7 +391,7 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
view_info.level_count, view_info.base_layer, view_info.layer_count,
static_cast<vk::ImageUsageFlags::MaskType>(view_info.usage));
}
views.views.push_back({normalized, view});
views.push_back({normalized, view});
return view;
}
@@ -12,8 +12,25 @@ namespace ImageViewOps {
[[nodiscard]] vk::ImageAspectFlags DepthAspectMask(vk::Format format);
[[nodiscard]] bool FormatsCompatible(vk::Format base, vk::Format view) noexcept;
[[nodiscard]] inline bool IsFormatDepthCompatible(vk::Format format) noexcept {
switch (format) {
case vk::Format::eD32Sfloat:
case vk::Format::eR32Sfloat:
case vk::Format::eR32Uint:
case vk::Format::eD16Unorm:
case vk::Format::eR16Unorm: return true;
default: return false;
}
}
} // namespace ImageViewOps
[[nodiscard]] inline bool IsSupportedSampledDepthFormat(vk::Format image_format,
vk::Format view_format) noexcept {
return DepthAspectTransferFormat(image_format) != vk::Format::eUndefined &&
ImageViewOps::IsFormatDepthCompatible(view_format);
}
[[nodiscard]] inline bool IsValidImageSwizzle(uint32_t swizzle) noexcept {
if ((swizzle & ~0xfffu) != 0) {
return false;
@@ -86,8 +103,14 @@ SelectSampledDepthView(vk::Format image_format, vk::Format view_format, uint32_t
[[nodiscard]] inline bool
IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
return resource.kind == ShaderRecompiler::IR::ResourceKind::Image &&
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
if (resource.kind != ShaderRecompiler::IR::ResourceKind::Image &&
resource.kind != ShaderRecompiler::IR::ResourceKind::ImageUint) {
return false;
}
if (resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint && resource.depth_compare) {
return false;
}
return (resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaaArray) &&
@@ -95,14 +118,6 @@ IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resou
!resource.written && !resource.atomic;
}
[[nodiscard]] inline bool
IsSupportedSampledDepthUintResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
return resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint &&
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
!resource.written && !resource.atomic && !resource.depth_compare;
}
inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format,
uint32_t swizzle) noexcept {
if (!ImageViewOps::FormatsCompatible(image_format, view_format) ||
@@ -120,7 +135,11 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.written &&
((resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None &&
resource.mip_count == 1u) ||
(resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage &&
resource.mip_count != 0u)) &&
resource.written &&
(!resource.atomic ||
(resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint &&
resource.read)) &&
@@ -14,156 +14,67 @@
namespace Libs::Graphics {
namespace {
struct RenderTargetFormatMapping {
Prospero::ChannelLayout layout;
Prospero::ChannelType type;
Prospero::ChannelOrder order;
RenderTargetFormatInfo info;
// Rows are channel order; columns are the number of physical components minus one. Unused
// selectors complete each entry to a permutation so logical write masks can be inverted.
constexpr Prospero::ColorComponentMapping kRenderTargetColorMappings[4][4] = {
{Prospero::ColorMappingRgba, Prospero::ColorMappingRgba, Prospero::ColorMappingRgba,
Prospero::ColorMappingRgba},
{Prospero::ColorMappingGr, Prospero::ColorMappingRabg, Prospero::ColorMappingRgab,
Prospero::ColorMappingBgra},
{Prospero::ColorMappingBgra, Prospero::ColorMappingGr, Prospero::ColorMappingBgra,
Prospero::ColorMappingAbgr},
{Prospero::ColorMappingAgba, Prospero::ColorMappingArbg, Prospero::ColorMappingAgbr,
Prospero::ColorMappingArgb},
};
constexpr RenderTargetFormatMapping kRenderTargetFormats[] = {
{Prospero::ChannelLayout::k8,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8Uint, 1}},
{Prospero::ChannelLayout::k8_8,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8G8Unorm, 2}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8G8B8A8Unorm, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kSNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8G8B8A8Snorm, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kSrgb,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8G8B8A8Srgb, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kAlt,
{vk::Format::eB8G8R8A8Unorm, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kSNorm,
Prospero::ChannelOrder::kAlt,
{vk::Format::eB8G8R8A8Snorm, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kSrgb,
Prospero::ChannelOrder::kAlt,
{vk::Format::eB8G8R8A8Srgb, 4}},
{Prospero::ChannelLayout::k5_5_5_1,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR5G5B5A1UnormPack16, 2}},
{Prospero::ChannelLayout::k4_4_4_4,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kReversed,
{vk::Format::eB4G4R4A4UnormPack16, 2}},
{Prospero::ChannelLayout::k10_10_10_2,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eA2B10G10R10UnormPack32, 4}},
{Prospero::ChannelLayout::k10_10_10_2,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kAlt,
{vk::Format::eA2R10G10B10UnormPack32, 4}},
{Prospero::ChannelLayout::k11_11_10,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eB10G11R11UfloatPack32, 4}},
{Prospero::ChannelLayout::k5_6_5,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eB5G6R5UnormPack16, 2}},
{Prospero::ChannelLayout::k16,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16Unorm, 2}},
{Prospero::ChannelLayout::k16,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16Uint, 2}},
{Prospero::ChannelLayout::k16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16Sfloat, 2}},
{Prospero::ChannelLayout::k16_16,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16Unorm, 4}},
{Prospero::ChannelLayout::k16_16,
Prospero::ChannelType::kSNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16Snorm, 4}},
{Prospero::ChannelLayout::k16_16,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16Uint, 4}},
{Prospero::ChannelLayout::k16_16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16Sfloat, 4}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16B16A16Unorm, 8}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16B16A16Uint, 8}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16B16A16Sfloat, 8}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kAlt,
{vk::Format::eR16G16B16A16Sfloat, 8, Prospero::ColorMappingBgra}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kReversed,
{vk::Format::eR16G16B16A16Sfloat, 8, Prospero::ColorMappingAbgr}},
{Prospero::ChannelLayout::k32,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR32Sfloat, 4}},
{Prospero::ChannelLayout::k32_32,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR32G32Uint, 8}},
{Prospero::ChannelLayout::k32_32,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR32G32Sfloat, 8}},
{Prospero::ChannelLayout::k32_32_32_32,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR32G32B32A32Sfloat, 16}},
struct RenderTargetHostFormatInfo {
vk::Format format = vk::Format::eUndefined;
Prospero::ColorComponentMapping host_to_storage;
};
RenderTargetHostFormatInfo ResolveRenderTargetHostFormat(Prospero::BufferFormat guest_format,
Prospero::ChannelOrder order) {
if (order == Prospero::ChannelOrder::kAlt) {
switch (guest_format) {
case Prospero::BufferFormat::k8_8_8_8UNorm:
return {vk::Format::eB8G8R8A8Unorm, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k8_8_8_8SNorm:
return {vk::Format::eB8G8R8A8Snorm, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k8_8_8_8Srgb:
return {vk::Format::eB8G8R8A8Srgb, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k10_10_10_2UNorm:
return {vk::Format::eA2R10G10B10UnormPack32, Prospero::ColorMappingBgra};
default: break;
}
}
switch (guest_format) {
case Prospero::BufferFormat::k5_5_5_1UNorm:
return {vk::Format::eA1R5G5B5UnormPack16, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k1_5_5_5UNorm:
return {vk::Format::eR5G5B5A1UnormPack16, Prospero::ColorMappingAbgr};
case Prospero::BufferFormat::k4_4_4_4UNorm:
return {vk::Format::eR4G4B4A4UnormPack16, Prospero::ColorMappingAbgr};
default: return {VulkanFormat(guest_format), {}};
}
}
} // namespace
// TODO: cleanup!
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t raw_layout, uint32_t raw_type,
uint32_t raw_order) {
const auto layout = static_cast<Prospero::ChannelLayout>(raw_layout);
const auto type = static_cast<Prospero::ChannelType>(raw_type);
const auto order = static_cast<Prospero::ChannelOrder>(raw_order);
if (layout == Prospero::ChannelLayout::k8 && type == Prospero::ChannelType::kUNorm &&
raw_order <= Prospero::GpuEnumValue(Prospero::ChannelOrder::kAltReversed)) {
return {vk::Format::eR8Unorm, 1};
}
for (const auto& mapping: kRenderTargetFormats) {
if (mapping.layout == layout && mapping.type == type && mapping.order == order) {
return mapping.info;
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
Prospero::ChannelType type,
Prospero::ChannelOrder order) {
const auto encoding = Prospero::ResolveRenderTargetFormat(layout, type);
if (encoding.IsValid() && encoding.SupportsOrder(order)) {
const auto host = ResolveRenderTargetHostFormat(encoding.buffer_format, order);
const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format);
if (host.format != vk::Format::eUndefined && bytes != 0) {
const auto order_mapping =
kRenderTargetColorMappings[static_cast<size_t>(order)][encoding.components - 1u];
return {host.format, bytes, host.host_to_storage.Then(order_mapping)};
}
}
EXIT("unsupported render-target format combination: layout=%u type=%u order=%u\n", raw_layout,
raw_type, raw_order);
EXIT("unsupported render-target format combination: layout=%u type=%u order=%u\n",
static_cast<uint32_t>(layout), static_cast<uint32_t>(type), static_cast<uint32_t>(order));
}
namespace {
@@ -208,13 +119,16 @@ vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) {
return components;
}
vk::Format TextureGetFormat(uint32_t fmt) {
const auto vk_format = VulkanFormat(fmt);
SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format) {
const auto backing_format = Prospero::RemapTextureFormat(format);
const auto vk_format = VulkanFormat(backing_format);
const auto conversion_format =
backing_format != format ? format : Prospero::BufferFormat::kInvalid;
if (vk_format != vk::Format::eUndefined) {
return vk_format;
return SurfaceFormatInfo(vk_format, conversion_format);
}
EXIT("unknown format: fmt = %u\n", fmt);
return vk::Format::eUndefined;
EXIT("unknown format: fmt = %u\n", static_cast<uint32_t>(format));
return SurfaceFormatInfo(vk::Format::eUndefined, Prospero::BufferFormat::kInvalid);
}
namespace {
@@ -271,13 +185,14 @@ uint64_t SetLinearUploadLevels(TextureUploadMipLayout* mips,
} // namespace
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint32_t width, uint32_t height,
uint32_t levels, uint32_t depth, uint32_t tile,
uint64_t upload_size, bool allow_depth_tile,
bool volume_texture, const char* owner) {
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
uint32_t height, uint32_t levels, uint32_t depth,
Prospero::TileMode tile, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner) {
TextureUploadLayout layout {};
layout.surface.description = {
fmt,
format,
tile,
volume_texture ? TileSurfaceDimension::Dim3D : TileSurfaceDimension::Dim2D,
width,
@@ -287,35 +202,39 @@ TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint32_t width, uint32
volume_texture ? 1u : depth,
};
const auto& description = layout.surface.description;
const auto tile_mode = static_cast<Prospero::TileMode>(description.tile_mode);
const auto tile_mode = description.tile_mode;
TileTextureElementLayout element {};
if (fmt == 0) {
if (format == Prospero::BufferFormat::kInvalid) {
EXIT("%s: legacy texture upload format unsupported: fmt=0 tile=%u size=%" PRIu64
" extent=%ux%u levels=%u\n",
owner, description.tile_mode, upload_size, width, height, levels);
owner, static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
levels);
}
switch (tile_mode) {
case Prospero::TileMode::kLinear:
if (!TileGetTextureElementLayout(fmt, element)) {
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner, fmt);
if (!TileGetTextureElementLayout(format, element)) {
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner,
static_cast<uint32_t>(format));
}
break;
case Prospero::TileMode::kDepth:
if (!allow_depth_tile || !TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(fmt), description.tile_mode, upload_size, width,
height, levels);
owner, static_cast<uint32_t>(format),
static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
levels);
}
break;
default:
if (!TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(fmt), description.tile_mode, upload_size, width,
height, levels);
owner, static_cast<uint32_t>(format),
static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
levels);
}
break;
}
@@ -324,12 +243,12 @@ TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint32_t width, uint32
element = {layout.surface.texture.block.bytes_per_element,
layout.surface.texture.texel_width, layout.surface.texture.texel_height};
}
layout.pitch = TileGetTexturePitch(fmt, width, description.tile_mode);
layout.pitch = TileGetTexturePitch(format, width, description.tile_mode);
if (tile_mode == Prospero::TileMode::kLinear) {
TileSizeOffset level_sizes[16] {};
TilePaddedSize padded_sizes[16] {};
TileGetTextureSize(fmt, width, height, levels, description.tile_mode, nullptr, level_sizes,
padded_sizes);
TileGetTextureSize(format, width, height, levels, description.tile_mode, nullptr,
level_sizes, padded_sizes);
for (uint32_t level = 0; level < levels; ++level) {
layout.mips[level] = {level_sizes[level].offset, level_sizes[level].size,
padded_sizes[level].width, padded_sizes[level].height};
@@ -356,10 +275,9 @@ std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayo
const auto depth = volume_texture ? description.depth : description.layers;
uint32_t mip_width = description.width;
uint32_t mip_height = description.height;
uint32_t mip_pitch = volume_texture && static_cast<Prospero::TileMode>(description.tile_mode) !=
Prospero::TileMode::kLinear
? description.width
: layout.pitch;
uint32_t mip_pitch = volume_texture && description.tile_mode != Prospero::TileMode::kLinear
? description.width
: layout.pitch;
std::vector<vk::BufferImageCopy> regions;
regions.reserve(GetTextureRegionCount(depth, description.levels, volume_texture));
@@ -376,8 +294,7 @@ std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayo
1};
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
region.imageExtent = {mip_width, mip_height, 1};
const bool linear = static_cast<Prospero::TileMode>(description.tile_mode) ==
Prospero::TileMode::kLinear;
const bool linear = description.tile_mode == Prospero::TileMode::kLinear;
if (linear) {
region.bufferRowLength = layout.mips[i].row_length;
region.bufferImageHeight = layout.mips[i].image_height;
@@ -3,7 +3,7 @@
#include "common/abi.h"
#include "common/common.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/vulkanCommon.h"
@@ -19,6 +19,15 @@ struct RenderTargetFormatInfo {
Prospero::ColorComponentMapping export_mapping;
};
struct SurfaceFormatInfo {
explicit constexpr SurfaceFormatInfo(vk::Format format,
Prospero::BufferFormat conversion_format)
: vk_format(format), conversion_format(conversion_format) {}
vk::Format vk_format;
Prospero::BufferFormat conversion_format;
};
struct TextureUploadMipLayout {
uint64_t offset = 0;
uint64_t size = 0;
@@ -35,12 +44,15 @@ struct TextureUploadLayout {
};
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
vk::Format TextureGetFormat(uint32_t fmt);
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t layout, uint32_t type, uint32_t order);
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint32_t width, uint32_t height,
uint32_t levels, uint32_t depth, uint32_t tile,
uint64_t upload_size, bool allow_depth_tile,
bool volume_texture, const char* owner);
SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format);
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
Prospero::ChannelType type,
Prospero::ChannelOrder order);
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
uint32_t height, uint32_t levels, uint32_t depth,
Prospero::TileMode tile, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner);
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout);
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t levels,
@@ -1,300 +0,0 @@
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "common/assert.h"
#include "common/profiler.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include <array>
namespace Libs::Graphics {
namespace {
using BindingKind = ShaderRecompiler::IR::DescriptorBindingKind;
vk::ShaderStageFlags StageFlags(DescriptorCache::Stage stage) {
switch (stage) {
case DescriptorCache::Stage::Vertex: return vk::ShaderStageFlagBits::eVertex;
case DescriptorCache::Stage::Pixel: return vk::ShaderStageFlagBits::eFragment;
case DescriptorCache::Stage::Compute: return vk::ShaderStageFlagBits::eCompute;
default: EXIT("unknown descriptor stage\n");
}
}
bool IsSampledImage(BindingKind kind) {
switch (kind) {
case BindingKind::Sampled1D:
case BindingKind::Sampled1DArray:
case BindingKind::Sampled2D:
case BindingKind::Sampled2DArray:
case BindingKind::Sampled2DMsaa:
case BindingKind::Sampled2DMsaaArray:
case BindingKind::Sampled3D:
case BindingKind::SampledUint1D:
case BindingKind::SampledUint1DArray:
case BindingKind::SampledUint2D:
case BindingKind::SampledUint2DArray:
case BindingKind::SampledUint2DMsaa:
case BindingKind::SampledUint2DMsaaArray:
case BindingKind::SampledUint3D: return true;
default: return false;
}
}
bool IsStorageImage(BindingKind kind) {
switch (kind) {
case BindingKind::Storage1D:
case BindingKind::Storage1DArray:
case BindingKind::Storage2D:
case BindingKind::Storage2DArray:
case BindingKind::Storage3D:
case BindingKind::StorageUint1D:
case BindingKind::StorageUint1DArray:
case BindingKind::StorageUint2D:
case BindingKind::StorageUint2DArray:
case BindingKind::StorageUint3D: return true;
default: return false;
}
}
vk::DescriptorType DescriptorType(BindingKind kind) {
if (kind == BindingKind::Samplers) {
return vk::DescriptorType::eSampler;
}
if (IsSampledImage(kind)) {
return vk::DescriptorType::eSampledImage;
}
if (IsStorageImage(kind)) {
return vk::DescriptorType::eStorageImage;
}
return vk::DescriptorType::eStorageBuffer;
}
uint32_t DescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding) {
return binding.resources.empty() ? 1u : static_cast<uint32_t>(binding.resources.size());
}
std::vector<uint32_t> LayoutKey(DescriptorCache::Stage stage,
const ShaderRecompiler::IR::Program& program) {
std::vector<uint32_t> key;
key.reserve(1u + program.bindings.descriptors.size() * 4u);
key.push_back(static_cast<uint32_t>(stage));
for (const auto& binding: program.bindings.descriptors) {
key.push_back(static_cast<uint32_t>(binding.kind));
key.push_back(binding.binding);
key.push_back(DescriptorCount(binding));
key.push_back(static_cast<uint32_t>(DescriptorType(binding.kind)));
}
return key;
}
vk::DescriptorBufferInfo BufferInfo(const BufferView& view) {
EXIT_IF(view.buffer == nullptr);
return {view.buffer, view.offset, view.range};
}
} // namespace
vk::DescriptorImageInfo DescriptorCache::MakeImageInfo(const TextureBinding& texture) {
EXIT_IF(!texture.image_id || texture.image_view == nullptr ||
texture.layout == vk::ImageLayout::eUndefined);
return {nullptr, texture.image_view, texture.layout};
}
DescriptorCache::~DescriptorCache() {
for (auto& [layout, sets]: m_free_sets_by_layout) {
(void)layout;
for (auto* set: sets) {
delete set;
}
}
for (const auto& [key, layout]: m_descriptor_set_layouts) {
(void)key;
m_graphics.device.destroyDescriptorSetLayout(layout, nullptr);
}
for (const auto& pool: m_pools) {
m_graphics.device.destroyDescriptorPool(pool.pool, nullptr);
}
}
vk::DescriptorSetLayout
DescriptorCache::GetDescriptorSetLayoutInternal(Stage stage,
const ShaderRecompiler::IR::Program& program) {
const auto key = LayoutKey(stage, program);
if (const auto found = m_descriptor_set_layouts.find(key);
found != m_descriptor_set_layouts.end()) {
return found->second;
}
std::vector<vk::DescriptorSetLayoutBinding> bindings;
bindings.reserve(program.bindings.descriptors.size());
for (const auto& descriptor: program.bindings.descriptors) {
bindings.push_back({descriptor.binding, DescriptorType(descriptor.kind),
DescriptorCount(descriptor), StageFlags(stage), nullptr});
}
if (bindings.empty()) {
return nullptr;
}
vk::DescriptorSetLayoutCreateInfo info {};
info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
info.bindingCount = static_cast<uint32_t>(bindings.size());
info.pBindings = bindings.data();
vk::DescriptorSetLayout layout = nullptr;
const auto result = m_graphics.device.createDescriptorSetLayout(&info, nullptr, &layout);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || layout == nullptr);
m_descriptor_set_layouts.emplace(key, layout);
return layout;
}
void DescriptorCache::CreatePool() {
KYTY_PROFILER_FUNCTION();
constexpr uint32_t MaxSets = 512;
const vk::DescriptorPoolSize sizes[] = {
{vk::DescriptorType::eStorageBuffer,
MaxSets * (ShaderRecompiler::IR::ShaderInfo::MaxBuffers +
ShaderRecompiler::IR::ShaderInfo::MaxAddresses + 3u)},
{vk::DescriptorType::eSampledImage, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxImages},
{vk::DescriptorType::eStorageImage, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxImages},
{vk::DescriptorType::eSampler, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxSamplers},
};
vk::DescriptorPoolCreateInfo info {};
info.sType = vk::StructureType::eDescriptorPoolCreateInfo;
info.poolSizeCount = static_cast<uint32_t>(std::size(sizes));
info.pPoolSizes = sizes;
info.maxSets = MaxSets;
const auto pool_id = static_cast<int>(m_pools.size());
auto& pool = m_pools.emplace_back();
const auto result = m_graphics.device.createDescriptorPool(&info, nullptr, &pool.pool);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || pool.pool == nullptr);
pool.next_free_pool = m_first_free_pool;
m_first_free_pool = pool_id;
}
VulkanDescriptorSet* DescriptorCache::Allocate(Stage stage,
const ShaderRecompiler::IR::Program& program) {
KYTY_PROFILER_FUNCTION();
Common::LockGuard lock(m_mutex);
const auto layout = GetDescriptorSetLayoutInternal(stage, program);
EXIT_IF(layout == nullptr);
auto& free_sets = m_free_sets_by_layout[layout];
if (!free_sets.empty()) {
auto* result = free_sets.back();
free_sets.pop_back();
return result;
}
constexpr uint32_t Batch = 32;
for (int attempt = 0; attempt < 2; attempt++) {
for (int pool_id = m_first_free_pool; pool_id != -1;
pool_id = m_pools[pool_id].next_free_pool) {
auto& pool = m_pools[pool_id];
std::array<vk::DescriptorSetLayout, Batch> layouts;
layouts.fill(layout);
std::array<vk::DescriptorSet, Batch> sets {};
vk::DescriptorSetAllocateInfo info {};
info.sType = vk::StructureType::eDescriptorSetAllocateInfo;
info.descriptorPool = pool.pool;
info.descriptorSetCount = Batch;
info.pSetLayouts = layouts.data();
if (m_graphics.device.allocateDescriptorSets(&info, sets.data()) ==
vk::Result::eSuccess) {
free_sets.reserve(free_sets.size() + Batch - 1u);
for (uint32_t i = 0; i + 1u < Batch; i++) {
free_sets.push_back(new VulkanDescriptorSet {sets[i], layout, pool_id});
}
return new VulkanDescriptorSet {sets.back(), layout, pool_id};
}
m_first_free_pool = pool.next_free_pool;
break;
}
CreatePool();
}
return nullptr;
}
void DescriptorCache::Recycle(VulkanDescriptorSet& set) {
EXIT_IF(set.set == nullptr || set.layout == nullptr);
Common::LockGuard lock(m_mutex);
m_free_sets_by_layout[set.layout].push_back(&set);
}
VulkanDescriptorSet& DescriptorCache::GetDescriptor(Stage stage,
const ShaderRecompiler::IR::Program& program,
const NativeDescriptors& data) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(data.buffers.size() != program.info.buffers.size() ||
data.images.size() != program.info.images.size() ||
data.samplers.size() != program.info.samplers.size() ||
data.addresses.size() != program.info.addresses.size());
auto* set = Allocate(stage, program);
EXIT_NOT_IMPLEMENTED(set == nullptr);
const auto descriptor_count = program.info.buffers.size() + program.info.images.size() +
program.info.samplers.size() + program.info.addresses.size() + 3u;
std::vector<vk::DescriptorBufferInfo> buffer_infos;
std::vector<vk::DescriptorImageInfo> image_infos;
std::vector<vk::WriteDescriptorSet> writes;
buffer_infos.reserve(descriptor_count);
image_infos.reserve(descriptor_count);
writes.reserve(program.bindings.descriptors.size());
for (const auto& binding: program.bindings.descriptors) {
vk::WriteDescriptorSet write {};
write.sType = vk::StructureType::eWriteDescriptorSet;
write.dstSet = set->set;
write.dstBinding = binding.binding;
write.descriptorType = DescriptorType(binding.kind);
write.descriptorCount = DescriptorCount(binding);
const auto buffer_start = buffer_infos.size();
const auto image_start = image_infos.size();
switch (binding.kind) {
case BindingKind::Buffers:
for (const auto resource: binding.resources) {
buffer_infos.push_back(BufferInfo(data.buffers.at(resource)));
}
break;
case BindingKind::AddressMemory:
for (const auto resource: binding.resources) {
buffer_infos.push_back(BufferInfo(data.addresses.at(resource)));
}
break;
case BindingKind::FlattenedSrt:
buffer_infos.push_back(BufferInfo(data.flattened_srt));
break;
case BindingKind::UserData: buffer_infos.push_back(BufferInfo(data.user_data)); break;
case BindingKind::Gds: buffer_infos.push_back(BufferInfo(data.gds)); break;
case BindingKind::Samplers:
for (const auto resource: binding.resources) {
const auto sampler = data.samplers.at(resource);
EXIT_IF(sampler == nullptr);
image_infos.push_back({sampler, nullptr, vk::ImageLayout::eUndefined});
}
break;
default: {
for (const auto resource: binding.resources) {
const auto& texture = data.images.at(resource);
image_infos.push_back(MakeImageInfo(texture));
}
break;
}
}
if (buffer_infos.size() != buffer_start) {
write.pBufferInfo = buffer_infos.data() + buffer_start;
}
if (image_infos.size() != image_start) {
write.pImageInfo = image_infos.data() + image_start;
}
writes.push_back(write);
}
m_graphics.device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0,
nullptr);
return *set;
}
vk::DescriptorSetLayout
DescriptorCache::GetDescriptorSetLayout(Stage stage, const ShaderRecompiler::IR::Program& program) {
Common::LockGuard lock(m_mutex);
return GetDescriptorSetLayoutInternal(stage, program);
}
} // namespace Libs::Graphics
@@ -1,114 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/shaderBindings.h"
#include <map>
#include <memory>
#include <span>
#include <unordered_map>
#include <vector>
namespace Libs::Graphics {
namespace ShaderRecompiler::IR {
struct DescriptorValue;
struct ImageResource;
struct Program;
struct ResourceSnapshot;
} // namespace ShaderRecompiler::IR
class CommandBuffer;
struct DescriptorCacheTestAccess;
struct ShaderStageRuntime;
struct VulkanDescriptorSet {
vk::DescriptorSet set = nullptr;
vk::DescriptorSetLayout layout = nullptr;
int pool_id = -1;
};
struct BufferView {
std::shared_ptr<void> owner;
vk::Buffer buffer = nullptr;
vk::DeviceSize offset = 0;
vk::DeviceSize range = VK_WHOLE_SIZE;
};
class DescriptorCache {
public:
enum class Stage { Unknown, Vertex, Pixel, Compute };
struct TextureBinding {
ImageId image_id;
vk::ImageView image_view = nullptr;
TextureCache::ImageDesc desc;
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
};
struct NativeDescriptors {
std::vector<BufferView> buffers;
std::vector<TextureBinding> images;
std::vector<vk::Sampler> samplers;
std::vector<BufferView> addresses;
BufferView gds;
BufferView flattened_srt;
BufferView user_data;
};
struct PreparedBindings {
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
std::shared_ptr<const ShaderRecompiler::IR::ResourceSnapshot> snapshot;
NativeDescriptors resources;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
vk::ShaderStageFlags shader_stage;
Stage stage = Stage::Unknown;
bool committed = false;
};
explicit DescriptorCache(GraphicContext& graphics): m_graphics(graphics) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~DescriptorCache();
KYTY_CLASS_NO_COPY(DescriptorCache);
vk::DescriptorSetLayout GetDescriptorSetLayout(Stage stage,
const ShaderRecompiler::IR::Program& program);
void Recycle(VulkanDescriptorSet& set);
VulkanDescriptorSet& GetDescriptor(Stage stage, const ShaderRecompiler::IR::Program& program,
const NativeDescriptors& descriptors);
private:
friend struct DescriptorCacheTestAccess;
struct Pool {
vk::DescriptorPool pool = nullptr;
int next_free_pool = -1;
};
static vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture);
void CreatePool();
VulkanDescriptorSet* Allocate(Stage stage, const ShaderRecompiler::IR::Program& program);
vk::DescriptorSetLayout
GetDescriptorSetLayoutInternal(Stage stage, const ShaderRecompiler::IR::Program& program);
GraphicContext& m_graphics;
Common::Mutex m_mutex;
std::vector<Pool> m_pools;
int m_first_free_pool = -1;
std::unordered_map<vk::DescriptorSetLayout, std::vector<VulkanDescriptorSet*>>
m_free_sets_by_layout;
std::map<std::vector<uint32_t>, vk::DescriptorSetLayout> m_descriptor_set_layouts;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
@@ -0,0 +1,97 @@
#include "graphics/host_gpu/renderer/pipeline/descriptorHeap.h"
#include "common/assert.h"
#include "common/profiler.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/masterSemaphore.h"
namespace Libs::Graphics {
namespace {
constexpr uint32_t DescriptorHeapCount = 1024;
constexpr std::array DescriptorPoolSizes = {
vk::DescriptorPoolSize {vk::DescriptorType::eStorageBuffer, 8192},
vk::DescriptorPoolSize {vk::DescriptorType::eSampledImage, 8192},
vk::DescriptorPoolSize {vk::DescriptorType::eStorageImage, 1024},
vk::DescriptorPoolSize {vk::DescriptorType::eSampler, 1024},
};
} // namespace
DescriptorHeap::DescriptorHeap(GraphicContext& graphics, MasterSemaphore& master_semaphore)
: m_graphics(graphics), m_master_semaphore(master_semaphore) {
CreatePool();
}
DescriptorHeap::~DescriptorHeap() {
m_graphics.device.destroyDescriptorPool(m_current_pool, nullptr);
for (const auto& [pool, tick]: m_pending_pools) {
m_master_semaphore.Wait(tick);
m_graphics.device.destroyDescriptorPool(pool, nullptr);
}
}
vk::DescriptorSet DescriptorHeap::Commit(vk::DescriptorSetLayout layout) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(layout == nullptr);
auto& batch = m_sets[layout];
if (batch.size != 0) {
return batch.sets[--batch.size];
}
if (Allocate(layout, batch)) {
return batch.sets[--batch.size];
}
m_pending_pools.emplace_back(m_current_pool, m_master_semaphore.CurrentTick());
if (const auto& [pool, tick] = m_pending_pools.front(); m_master_semaphore.IsFree(tick)) {
m_current_pool = pool;
m_pending_pools.pop_front();
EXIT_IF(m_graphics.device.resetDescriptorPool(m_current_pool, {}) != vk::Result::eSuccess);
} else {
CreatePool();
}
m_sets.clear();
auto& fresh_batch = m_sets[layout];
EXIT_IF(!Allocate(layout, fresh_batch));
return fresh_batch.sets[--fresh_batch.size];
}
bool DescriptorHeap::Allocate(vk::DescriptorSetLayout layout, Batch& batch) {
std::array<vk::DescriptorSetLayout, DescriptorSetBatch> layouts;
layouts.fill(layout);
vk::DescriptorSetAllocateInfo allocate {};
allocate.sType = vk::StructureType::eDescriptorSetAllocateInfo;
allocate.descriptorPool = m_current_pool;
allocate.pSetLayouts = layouts.data();
for (;;) {
allocate.descriptorSetCount = batch.allocation;
const auto result = m_graphics.device.allocateDescriptorSets(&allocate, batch.sets.data());
if (result == vk::Result::eSuccess) {
batch.size = batch.allocation;
return true;
}
EXIT_IF(result != vk::Result::eErrorOutOfPoolMemory &&
result != vk::Result::eErrorFragmentedPool);
if (batch.allocation == 1) {
return false;
}
batch.allocation /= 2;
}
}
void DescriptorHeap::CreatePool() {
vk::DescriptorPoolCreateInfo create {};
create.sType = vk::StructureType::eDescriptorPoolCreateInfo;
create.flags = {};
create.maxSets = DescriptorHeapCount;
create.poolSizeCount = static_cast<uint32_t>(DescriptorPoolSizes.size());
create.pPoolSizes = DescriptorPoolSizes.data();
EXIT_IF(m_graphics.device.createDescriptorPool(&create, nullptr, &m_current_pool) !=
vk::Result::eSuccess);
}
} // namespace Libs::Graphics
@@ -0,0 +1,45 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORHEAP_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORHEAP_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <array>
#include <deque>
#include <unordered_map>
namespace Libs::Graphics {
struct GraphicContext;
class MasterSemaphore;
class DescriptorHeap {
public:
DescriptorHeap(GraphicContext& graphics, MasterSemaphore& master_semaphore);
~DescriptorHeap();
KYTY_CLASS_NO_COPY(DescriptorHeap);
[[nodiscard]] vk::DescriptorSet Commit(vk::DescriptorSetLayout layout);
private:
static constexpr uint32_t DescriptorSetBatch = 32;
struct Batch {
std::array<vk::DescriptorSet, DescriptorSetBatch> sets {};
uint32_t size = 0;
uint32_t allocation = DescriptorSetBatch;
};
[[nodiscard]] bool Allocate(vk::DescriptorSetLayout layout, Batch& batch);
void CreatePool();
GraphicContext& m_graphics;
MasterSemaphore& m_master_semaphore;
vk::DescriptorPool m_current_pool = nullptr;
std::deque<std::pair<vk::DescriptorPool, uint64_t>> m_pending_pools;
std::unordered_map<vk::DescriptorSetLayout, Batch> m_sets;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORHEAP_H_
File diff suppressed because it is too large. Load diff
@@ -2,6 +2,8 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
#include "common/assert.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
@@ -10,9 +12,54 @@
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <vector>
namespace Libs::Graphics {
namespace ShaderRecompiler::IR {
struct ResourceSnapshot;
}
struct BufferView {
vk::Buffer buffer = nullptr;
vk::DeviceSize offset = 0;
vk::DeviceSize range = VK_WHOLE_SIZE;
};
struct TextureBinding {
ImageId image_id;
vk::ImageView image_view = nullptr;
TextureCache::ImageDesc desc;
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
std::vector<vk::ImageView> mip_views;
};
struct NativeDescriptors {
std::vector<BufferView> buffers;
std::vector<TextureBinding> images;
std::vector<vk::Sampler> samplers;
BufferView gds;
BufferView flattened_srt;
BufferView user_data;
};
struct PreparedBindings {
const ShaderRecompiler::IR::Program* program = nullptr;
const ShaderRecompiler::IR::ResourceSnapshot* snapshot = nullptr;
NativeDescriptors resources;
std::vector<BufferId> buffer_ids;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
bool committed = false;
};
[[nodiscard]] vk::DescriptorType
NativeDescriptorType(ShaderRecompiler::IR::DescriptorBindingKind kind);
[[nodiscard]] uint32_t
NativeDescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding);
[[nodiscard]] vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture,
uint32_t element = 0);
template <typename T>
[[nodiscard]] T DecodeNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& value) {
static_assert(std::is_trivially_copyable_v<T>);
@@ -34,9 +81,9 @@ ResolveTargetTextureView(const ShaderRecompiler::IR::ImageResource& resource,
Prospero::ImageType type, uint32_t base_layer, uint32_t image_layers);
[[nodiscard]] bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
const Image& image);
const Image& image, bool r128 = false);
[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor,
const Image& image);
const Image& image, bool r128 = false);
[[nodiscard]] bool
IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, const Image& image);
@@ -10,11 +10,14 @@
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/shaderCompiler.h"
#include <atomic>
#include <cstdio>
#include <cstring>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
@@ -36,16 +39,132 @@ void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_param
} // namespace
struct PipelineCache::ProgramCache {
struct SourceKey {
ShaderType stage = ShaderType::Unknown;
uint64_t hash = 0;
bool operator==(const SourceKey&) const = default;
};
struct Permutation {
std::vector<uint32_t> static_key;
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
ShaderProgram handle;
};
struct SourceKeyHash {
std::size_t operator()(const SourceKey& key) const {
std::size_t hash = static_cast<std::size_t>(key.stage);
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash));
if constexpr (sizeof(std::size_t) < sizeof(uint64_t)) {
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash >> 32u));
}
return hash;
}
};
static uint64_t MixId(uint64_t hash, uint64_t value) {
return hash ^ (value + 0x9e3779b97f4a7c15ull + (hash << 6u) + (hash >> 2u));
}
static constexpr std::size_t MaxStaticKeyWords =
5 + ShaderVertexInputInfo::RES_MAX * 17;
template <typename InputInfo>
ShaderProgram Get(const ShaderParams& params, InputInfo& input_info) {
constexpr ShaderType stage = [] {
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
return ShaderType::Vertex;
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
return ShaderType::Pixel;
} else {
static_assert(std::is_same_v<InputInfo, ShaderComputeInputInfo>);
return ShaderType::Compute;
}
}();
BuildStageStaticKey(input_info, key_scratch);
auto& permutations = programs[{stage, params.hash}];
for (const auto& permutation: permutations) {
if (permutation.static_key == key_scratch &&
MaterializeProgram(permutation.program, params, input_info)) {
return permutation.handle;
}
}
const auto module = CompileProgram(device, params, input_info);
EXIT_IF(module == nullptr || !input_info.stage);
uint64_t id = MixId(MixId(params.hash, static_cast<uint64_t>(stage)), permutations.size());
if (id == 0) {
id = 1;
}
const ShaderProgram handle {.id = id, .module = module};
permutations.push_back({key_scratch, input_info.stage.program, handle});
std::printf("Num compiled %u shaders\n", ++num_compiled);
return handle;
}
explicit ProgramCache(vk::Device device): device(device) {
key_scratch.reserve(MaxStaticKeyWords);
}
std::unordered_map<SourceKey, std::vector<Permutation>, SourceKeyHash> programs;
std::vector<uint32_t> key_scratch;
vk::Device device;
uint32_t num_compiled = 0;
};
PipelineCache::PipelineCache(GraphicContext& graphics)
: m_graphics(graphics), m_program_cache(std::make_unique<ProgramCache>(graphics.device)) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
PipelineCache::~PipelineCache() {
auto destroy = [this](const auto& pipelines) {
for (const auto& [key, pipeline]: pipelines) {
(void)key;
m_graphics.device.destroyPipeline(pipeline->pipeline, nullptr);
m_graphics.device.destroyPipelineLayout(pipeline->pipeline_layout, nullptr);
m_graphics.device.destroyDescriptorSetLayout(pipeline->descriptor_set_layout, nullptr);
}
};
destroy(m_graphics_pipelines);
destroy(m_compute_pipelines);
for (const auto& [key, permutations]: m_program_cache->programs) {
(void)key;
for (const auto& permutation: permutations) {
m_graphics.device.destroyShaderModule(permutation.handle.module, nullptr);
}
}
}
ShaderProgram PipelineCache::GetVertexProgram(const HW::VertexShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderVertexInputInfo& input_info) {
const auto params = PrepareProgram(regs, sh, input_info);
Common::LockGuard lock(m_mutex);
return m_program_cache->Get(params, input_info);
}
ShaderProgram PipelineCache::GetPixelProgram(
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
const ShaderVertexInputInfo& vertex_info,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
ShaderPixelInputInfo& input_info) {
const auto params = PrepareProgram(regs, sh, vertex_info, target_export_mapping, input_info);
Common::LockGuard lock(m_mutex);
return m_program_cache->Get(params, input_info);
}
ShaderProgram PipelineCache::GetComputeProgram(const HW::ComputeShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info) {
input_info.needs_lds_barriers = !m_graphics.compute_wave64_supported;
const auto params = PrepareProgram(regs, sh, input_info);
Common::LockGuard lock(m_mutex);
return m_program_cache->Get(params, input_info);
}
bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other) const noexcept {
@@ -53,23 +172,22 @@ bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other)
}
PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology, bool ps_active,
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv) {
std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
bool primitive_restart_enable, const ShaderProgram& vertex_program,
const ShaderProgram& pixel_program) {
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Gfx)", profiler::colors::DeepOrangeA200);
EXIT_IF(colors == nullptr);
EXIT_IF(color_count > RENDER_COLOR_ATTACHMENTS_MAX);
EXIT_IF(vs_spirv.empty());
EXIT_IF(ps_active && ps_spirv.empty());
EXIT_IF(colors.size() > RENDER_COLOR_ATTACHMENTS_MAX);
EXIT_IF(!vertex_program);
const bool ps_active = ps_input_info != nullptr;
EXIT_IF(ps_active && !pixel_program);
const auto color_count = static_cast<uint32_t>(colors.size());
Common::LockGuard lock(m_mutex);
auto& ctx = command.GetRegisters();
auto& sh_ctx = command.GetShaders();
auto& ctx = command.GetRegisters();
const auto& vertex_info = sh_ctx.GetVs();
const auto& ps_regs = sh_ctx.GetPs();
const HW::BlendColor& bclr = ctx.GetBlendColor();
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < color_count; i++) {
@@ -80,11 +198,8 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
}
const HW::ModeControl& mc = ctx.GetModeControl();
auto vs_id = ShaderGetIdVS(vertex_info, vs_input_info, true);
ShaderId ps_id {};
if (ps_active) {
ps_id = ShaderGetIdPS(ps_regs, *ps_input_info, true);
}
const auto vs_id = vertex_program.id;
const auto ps_id = ps_active ? pixel_program.id : 0;
PipelineStaticParameters static_params {};
GraphicsPipeline p {};
@@ -120,6 +235,12 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
depth.samples);
}
}
if (color_count == 0 && !with_depth) {
attachment_samples = render_sample_count(ctx.GetAaConfig().msaa_num_samples);
EXIT_IF(!static_cast<bool>(
m_graphics.GetPhysicalDeviceProperties().limits.framebufferNoAttachmentsSampleCounts &
vulkan_sample_count(attachment_samples)));
}
EXIT_IF(attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {});
@@ -130,12 +251,12 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
}
}
const auto& clip_control = ctx.GetClipControl();
EXIT_NOT_IMPLEMENTED(!clip_control.IsZClipModeRepresentable());
static_params.negative_one_to_one = !clip_control.dx_clip_space;
static_params.depth_clip_enable = clip_control.IsZClipEnabled();
static_params.topology = topology;
static_params.samples = attachment_samples;
const auto& clip_control = ctx.GetClipControl();
static_params.negative_one_to_one = !clip_control.dx_clip_space;
static_params.depth_clip_enable = clip_control.IsZClipEnabled();
static_params.topology = topology;
static_params.primitive_restart_enable = primitive_restart_enable;
static_params.samples = attachment_samples;
static_params.sample_shading_enable =
ps_active && attachment_samples > 1 && ps_input_info->ps_sample_shading;
if (static_params.sample_shading_enable && !m_graphics.sample_rate_shading_enabled) {
@@ -194,20 +315,17 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
if (ps_active) {
ShaderDbgDumpInputInfo(*ps_input_info);
}
LOGF("PipelineTrace: shader binaries VS=0x%08" PRIx32 "/0x%08" PRIx32 " words=%" PRIu64
" PS=0x%08" PRIx32 "/0x%08" PRIx32 " words=%" PRIu64 "\n",
vs_id.hash0, vs_id.crc32, static_cast<uint64_t>(vs_spirv.size()), ps_id.hash0,
ps_id.crc32, static_cast<uint64_t>(ps_spirv.size()));
LOGF("PipelineTrace: shader modules VS=%" PRIu64 " module=%p PS=%" PRIu64
" module=%p\n",
vs_id, static_cast<void*>(vertex_program.module), ps_id,
static_cast<void*>(pixel_program.module));
}
auto cached = std::make_unique<GraphicsPipeline>(p);
LogPipelineTrace("CreatePipelineInternal begin", vs_id.hash0, vs_id.crc32, ps_id.hash0,
ps_id.crc32);
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, rendering, vs_input_info,
vs_spirv, ps_input_info, ps_spirv, static_params, vs_id.hash0,
vs_id.crc32, ps_id.hash0, ps_id.crc32, ps_active);
LogPipelineTrace("CreatePipelineInternal done", vs_id.hash0, vs_id.crc32, ps_id.hash0,
ps_id.crc32);
LogPipelineTrace("CreatePipelineInternal begin", vs_id, ps_id);
CreatePipelineInternal(m_graphics, *cached, rendering, vs_input_info, vertex_program.module,
ps_input_info, pixel_program.module, static_params);
LogPipelineTrace("CreatePipelineInternal done", vs_id, ps_id);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
@@ -219,19 +337,16 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
}
PipelineCache::ComputePipeline&
PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
const HW::ComputeShaderInfo& cs_regs,
std::span<const uint32_t> cs_spirv) {
PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
const ShaderProgram& compute_program) {
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Compute)", profiler::colors::RedA100);
EXIT_IF(cs_spirv.empty());
EXIT_IF(!compute_program);
Common::LockGuard lock(m_mutex);
auto cs_id = ShaderGetIdCS(cs_regs, input_info, true);
ComputePipeline p {};
p.cs_shader_id = cs_id;
p.cs_shader_id = compute_program.id;
ComputePipelineKey key {};
key.cs_shader_id = p.cs_shader_id;
@@ -245,7 +360,7 @@ PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
}
auto cached = std::make_unique<ComputePipeline>(p);
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, input_info, cs_spirv);
CreatePipelineInternal(m_graphics, *cached, input_info, compute_program.module);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
@@ -20,8 +20,7 @@ namespace Libs::Graphics {
struct GraphicContext;
struct RenderColorInfo;
struct RenderDepthInfo;
class RenderCommandBuffer;
class DescriptorCache;
class CommandBuffer;
namespace HW {
class Context;
@@ -38,6 +37,7 @@ struct PipelineStaticParameters {
bool depth_clip_enable = true;
int scissor_ltrb[4] = {0};
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
bool primitive_restart_enable = false;
uint32_t samples = 1;
bool sample_shading_enable = false;
bool with_depth = false;
@@ -79,9 +79,9 @@ static_assert(std::is_standard_layout_v<PipelineStaticParameters>);
static_assert(alignof(PipelineStaticParameters) == 1);
static_assert(sizeof(PipelineStaticParameters) ==
sizeof(float[3]) + sizeof(float[3]) + sizeof(bool) * 2 + sizeof(int[4]) +
sizeof(vk::PrimitiveTopology) + sizeof(uint32_t) + sizeof(bool) * 4 +
sizeof(vk::CompareOp) + sizeof(bool) + sizeof(float) * 2 + sizeof(bool) +
sizeof(PipelineStencilStaticState) * 2 + sizeof(uint32_t) +
sizeof(vk::PrimitiveTopology) + sizeof(bool) + sizeof(uint32_t) +
sizeof(bool) * 4 + sizeof(vk::CompareOp) + sizeof(bool) + sizeof(float) * 2 +
sizeof(bool) + sizeof(PipelineStencilStaticState) * 2 + sizeof(uint32_t) +
sizeof(uint32_t[RENDER_COLOR_ATTACHMENTS_MAX]) + sizeof(bool) * 3 +
sizeof(uint8_t[RENDER_COLOR_ATTACHMENTS_MAX]) * 6 +
sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) * 3 + sizeof(float) * 4);
@@ -95,44 +95,65 @@ struct PipelineRenderingState {
bool operator==(const PipelineRenderingState&) const = default;
};
struct ShaderProgram {
uint64_t id = 0;
vk::ShaderModule module = nullptr;
explicit operator bool() const {
return id != 0 && module != nullptr;
}
};
class PipelineCache {
public:
PipelineCache(GraphicContext& graphics, DescriptorCache& descriptor_cache)
: m_graphics(graphics), m_descriptor_cache(descriptor_cache) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
explicit PipelineCache(GraphicContext& graphics);
~PipelineCache();
KYTY_CLASS_NO_COPY(PipelineCache);
struct Pipeline {
vk::PipelineLayout pipeline_layout = nullptr;
vk::Pipeline pipeline = nullptr;
vk::PipelineLayout pipeline_layout = nullptr;
vk::Pipeline pipeline = nullptr;
vk::DescriptorSetLayout descriptor_set_layout = nullptr;
bool uses_push_descriptors = false;
};
struct GraphicsPipeline: Pipeline {
ShaderId vs_shader_id;
ShaderId ps_shader_id;
uint64_t vs_shader_id = 0;
uint64_t ps_shader_id = 0;
};
struct ComputePipeline: Pipeline {
ShaderId cs_shader_id;
uint64_t cs_shader_id = 0;
};
GraphicsPipeline&
CreateGraphicsPipeline(RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
bool ps_active, std::span<const uint32_t> vs_spirv,
std::span<const uint32_t> ps_spirv);
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
const HW::ComputeShaderInfo& cs_regs,
std::span<const uint32_t> cs_spirv);
ShaderProgram GetVertexProgram(const HW::VertexShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderVertexInputInfo& input_info);
ShaderProgram
GetPixelProgram(const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
const ShaderVertexInputInfo& vertex_info,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
ShaderPixelInputInfo& input_info);
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info);
GraphicsPipeline& CreateGraphicsPipeline(
std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
bool primitive_restart_enable, const ShaderProgram& vertex_program,
const ShaderProgram& pixel_program);
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
const ShaderProgram& compute_program);
private:
struct ProgramCache;
struct GraphicsPipelineKey {
PipelineRenderingState rendering;
ShaderId vs_shader_id;
ShaderId ps_shader_id;
uint64_t vs_shader_id = 0;
uint64_t ps_shader_id = 0;
PipelineStaticParameters static_params;
bool operator==(const GraphicsPipelineKey& other) const {
@@ -142,7 +163,7 @@ private:
};
struct ComputePipelineKey {
ShaderId cs_shader_id;
uint64_t cs_shader_id = 0;
bool operator==(const ComputePipelineKey& other) const {
return cs_shader_id == other.cs_shader_id;
@@ -155,15 +176,6 @@ private:
(hash >> 2u);
}
static void MixShaderId(std::size_t& hash, const ShaderId& id) {
Mix(hash, id.hash0);
Mix(hash, id.crc32);
Mix(hash, id.ids.size());
for (auto value: id.ids) {
Mix(hash, value);
}
}
static void MixStaticParams(std::size_t& hash, const PipelineStaticParameters& params) {
const auto* bytes = reinterpret_cast<const uint8_t*>(&params);
for (std::size_t i = 0; i < sizeof(params); i++) {
@@ -185,8 +197,8 @@ private:
std::size_t operator()(const GraphicsPipelineKey& key) const {
std::size_t hash = 0;
PipelineKeyHash::MixRendering(hash, key.rendering);
PipelineKeyHash::MixShaderId(hash, key.vs_shader_id);
PipelineKeyHash::MixShaderId(hash, key.ps_shader_id);
PipelineKeyHash::Mix(hash, key.vs_shader_id);
PipelineKeyHash::Mix(hash, key.ps_shader_id);
PipelineKeyHash::MixStaticParams(hash, key.static_params);
return hash;
}
@@ -195,13 +207,13 @@ private:
struct ComputePipelineKeyHash {
std::size_t operator()(const ComputePipelineKey& key) const {
std::size_t hash = 0;
PipelineKeyHash::MixShaderId(hash, key.cs_shader_id);
PipelineKeyHash::Mix(hash, key.cs_shader_id);
return hash;
}
};
GraphicContext& m_graphics;
DescriptorCache& m_descriptor_cache;
GraphicContext& m_graphics;
std::unique_ptr<ProgramCache> m_program_cache;
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>,
GraphicsPipelineKeyHash>
m_graphics_pipelines;
@@ -210,19 +222,15 @@ private:
Common::Mutex m_mutex;
};
void LogPipelineTrace(const char* phase, uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0,
uint32_t ps_crc32);
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
void CreatePipelineInternal(
GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::GraphicsPipeline& pipeline, const PipelineRenderingState& rendering,
const ShaderVertexInputInfo& vs_input_info, std::span<const uint32_t> vs_shader,
const ShaderPixelInputInfo* ps_input_info, std::span<const uint32_t> ps_shader,
const PipelineStaticParameters& static_params, uint32_t vs_hash0, uint32_t vs_crc32,
uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active);
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
std::span<const uint32_t> cs_shader);
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params);
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
vk::ShaderModule compute_module);
} // namespace Libs::Graphics
@@ -2,7 +2,7 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERRESOURCEBARRIER_H_
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include <vector>
@@ -1,60 +0,0 @@
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
namespace Libs::Graphics {
ShaderSubgroupCapabilities::ShaderSubgroupCapabilities(const GraphicContext& graphics)
: subgroup_size(graphics.subgroup_size), min_subgroup_size(graphics.min_subgroup_size),
max_subgroup_size(graphics.max_subgroup_size),
required_subgroup_size_stages(graphics.required_subgroup_size_stages),
subgroup_size_control_enabled(graphics.subgroup_size_control_enabled) {}
ShaderLaneMaskMode SelectGraphicsLaneMaskMode(uint32_t guest_wave_size) {
// A 64-wide guest wave can't assume the host GPU's lane layout matches
// GCN's. AMD RDNA also reports a 64-wide subgroup but uses a different
// lane/pixel layout, which corrupted rendering under "native wave" mode.
// Per-invocation emulation works regardless of the host's lane layout.
if (guest_wave_size == 64u) {
return ShaderLaneMaskMode::PerInvocation;
}
return ShaderLaneMaskMode::NativeWave;
}
ShaderSubgroupConfiguration ConfigureShaderSubgroup(const ShaderSubgroupCapabilities& capabilities,
vk::ShaderStageFlagBits stage,
const ShaderRecompiler::IR::Program& program) {
const auto guest_wave_size = program.wave_size;
if (guest_wave_size != 32u && guest_wave_size != 64u) {
return {};
}
if (stage != vk::ShaderStageFlagBits::eCompute) {
const auto expected = SelectGraphicsLaneMaskMode(guest_wave_size);
if (program.lane_mask_mode != expected || (capabilities.subgroup_size != guest_wave_size &&
expected != ShaderLaneMaskMode::PerInvocation)) {
return {};
}
return {expected == ShaderLaneMaskMode::NativeWave
? ShaderSubgroupMode::Natural
: ShaderSubgroupMode::PerInvocationGraphics,
0};
}
if (program.lane_mask_mode != ShaderLaneMaskMode::NativeWave) {
return {};
}
if (capabilities.subgroup_size == guest_wave_size) {
return {ShaderSubgroupMode::Natural, 0};
}
if (capabilities.subgroup_size_control_enabled &&
(capabilities.required_subgroup_size_stages & stage) &&
guest_wave_size >= capabilities.min_subgroup_size &&
guest_wave_size <= capabilities.max_subgroup_size) {
return {ShaderSubgroupMode::Controlled, guest_wave_size};
}
if (!ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(program)) {
return {ShaderSubgroupMode::FlattenedMasks, 0};
}
return {};
}
} // namespace Libs::Graphics
@@ -1,40 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
namespace Libs::Graphics {
enum class ShaderSubgroupMode {
Natural,
Controlled,
PerInvocationGraphics,
FlattenedMasks,
Unsupported
};
struct ShaderSubgroupConfiguration {
ShaderSubgroupMode mode = ShaderSubgroupMode::Unsupported;
uint32_t required_size = 0;
};
struct ShaderSubgroupCapabilities {
explicit ShaderSubgroupCapabilities(const GraphicContext& graphics);
uint32_t subgroup_size = 0;
uint32_t min_subgroup_size = 0;
uint32_t max_subgroup_size = 0;
vk::ShaderStageFlags required_subgroup_size_stages;
bool subgroup_size_control_enabled = false;
};
ShaderLaneMaskMode SelectGraphicsLaneMaskMode(uint32_t guest_wave_size);
ShaderSubgroupConfiguration ConfigureShaderSubgroup(const ShaderSubgroupCapabilities& capabilities,
vk::ShaderStageFlagBits stage,
const ShaderRecompiler::IR::Program& program);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERSUBGROUP_H_
@@ -6,9 +6,8 @@
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
@@ -20,10 +19,13 @@
#include <algorithm>
#include <limits>
#include <span>
#include <vector>
namespace Libs::Graphics {
// IDK: maybe we can remove it?
constexpr uint8_t kTemporaryVertexAttribFormat113 =
static_cast<uint8_t>(Prospero::VertexAttribFormat::k16_16SInt);
constexpr uint32_t kTemporaryPs5BufferFormat121 = 121u;
static bool NarrowInputFormat(vk::Format& format, uint32_t& size, uint32_t used_components) {
@@ -88,8 +90,9 @@ static bool NarrowInputFormat(vk::Format& format, uint32_t& size, uint32_t used_
static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format, uint32_t& size,
uint32_t used_components) {
auto fmt = res.Format();
if (fmt == Prospero::GpuEnumValue(Prospero::VertexAttribFormat::k16_16SInt)) {
const auto fmt = res.Format();
const auto raw_format = res.RawFormat();
if (raw_format == kTemporaryVertexAttribFormat113) {
static bool logged_113 = false;
if (!logged_113) {
LOGF("InputFormat: temporary: accepting invalid PS5 buffer format 113 as "
@@ -99,12 +102,12 @@ static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format,
format = vk::Format::eR32G32B32A32Sfloat;
size = 4;
if (NarrowInputFormat(format, size, used_components)) {
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", fmt,
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", raw_format,
VulkanToString(format).c_str(), used_components);
}
return;
}
if (fmt == kTemporaryPs5BufferFormat121) {
if (raw_format == kTemporaryPs5BufferFormat121) {
static bool logged_121 = false;
if (!logged_121) {
LOGF("InputFormat: accepting PS5 buffer format 121 as vk::Format::eR16G16Sfloat\n");
@@ -113,13 +116,13 @@ static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format,
format = vk::Format::eR16G16Sfloat;
size = 2;
if (NarrowInputFormat(format, size, used_components)) {
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", fmt,
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", raw_format,
VulkanToString(format).c_str(), used_components);
}
return;
}
switch (static_cast<Prospero::BufferFormat>(fmt)) {
switch (fmt) {
case Prospero::BufferFormat::k32_32_32_32Float:
format = vk::Format::eR32G32B32A32Sfloat;
size = 4;
@@ -329,7 +332,7 @@ static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format,
size = 1;
break;
default:
EXIT("unknown format: fmt = %u\n", fmt);
EXIT("unknown format: fmt = %u\n", raw_format);
format = vk::Format::eUndefined;
size = 4;
break;
@@ -386,116 +389,52 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
return vk::BlendOp::eAdd;
}
static void CreateLayout(DescriptorCache& descriptor_cache,
std::span<vk::DescriptorSetLayout> set_layouts, uint32_t& set_layouts_num,
std::span<vk::PushConstantRange> push_constant_info,
uint32_t& push_constant_info_num,
const ShaderRecompiler::IR::Program& program,
vk::ShaderStageFlags vk_stage, DescriptorCache::Stage stage) {
const auto& bindings = program.bindings;
const bool need_descriptor = !bindings.descriptors.empty();
if (bindings.push_constant_size != 0) {
auto index = push_constant_info_num;
push_constant_info[index].stageFlags = vk_stage;
push_constant_info[index].offset = bindings.push_constant_offset;
push_constant_info[index].size = bindings.push_constant_size;
push_constant_info_num++;
}
if (need_descriptor) {
EXIT_IF(bindings.descriptor_set != set_layouts_num);
set_layouts[set_layouts_num] = descriptor_cache.GetDescriptorSetLayout(stage, program);
set_layouts_num++;
static void AddLayoutBindings(std::vector<vk::DescriptorSetLayoutBinding>& descriptor_bindings,
const ShaderRecompiler::IR::Program& program,
vk::ShaderStageFlagBits stage) {
for (const auto& binding: program.bindings.descriptors) {
descriptor_bindings.push_back(
{ShaderRecompiler::IR::NativeBinding(program.stage, binding.kind),
NativeDescriptorType(binding.kind), NativeDescriptorCount(binding), stage, nullptr});
}
}
static void ConfigureSubgroupSize(const GraphicContext& graphics, vk::ShaderStageFlagBits vk_stage,
const ShaderRecompiler::IR::Program& program,
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo& required,
vk::PipelineShaderStageCreateInfo& stage) {
const auto config =
ConfigureShaderSubgroup(ShaderSubgroupCapabilities {graphics}, vk_stage, program);
switch (config.mode) {
case ShaderSubgroupMode::Natural: return;
case ShaderSubgroupMode::PerInvocationGraphics: {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("Vulkan running %u-lane graphics shader stage 0x%08x with per-invocation "
"EXEC/VCC on the host-native subgroup\n",
program.wave_size, static_cast<uint32_t>(vk_stage));
}
return;
}
case ShaderSubgroupMode::FlattenedMasks: {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("Vulkan running %u-lane shader stage 0x%08x with flattened per-invocation "
"EXEC/VCC\n",
program.wave_size, static_cast<uint32_t>(vk_stage));
}
return;
}
case ShaderSubgroupMode::Controlled: break;
case ShaderSubgroupMode::Unsupported:
default:
EXIT("Vulkan cannot run %u-lane shader stage 0x%08x: default=%u min=%u max=%u "
"controlled_stages=0x%08x\n",
program.wave_size, static_cast<uint32_t>(vk_stage), graphics.subgroup_size,
graphics.min_subgroup_size, graphics.max_subgroup_size,
static_cast<vk::ShaderStageFlags::MaskType>(
graphics.required_subgroup_size_stages));
static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
std::span<const vk::DescriptorSetLayoutBinding> bindings) {
uint32_t descriptor_count = 0;
for (const auto& binding: bindings) {
descriptor_count += binding.descriptorCount;
}
pipeline.uses_push_descriptors = descriptor_count <= graphics.max_push_descriptors;
required.sType = vk::StructureType::ePipelineShaderStageRequiredSubgroupSizeCreateInfo;
required.requiredSubgroupSize = config.required_size;
stage.pNext = &required;
vk::DescriptorSetLayoutCreateInfo create {};
create.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
create.flags = pipeline.uses_push_descriptors
? vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR
: vk::DescriptorSetLayoutCreateFlags {};
create.bindingCount = static_cast<uint32_t>(bindings.size());
create.pBindings = bindings.data();
EXIT_IF(graphics.device.createDescriptorSetLayout(
&create, nullptr, &pipeline.descriptor_set_layout) != vk::Result::eSuccess);
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(
GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::GraphicsPipeline& pipeline, const PipelineRenderingState& rendering,
const ShaderVertexInputInfo& vs_input_info, std::span<const uint32_t> vs_shader,
const ShaderPixelInputInfo* ps_input_info, std::span<const uint32_t> ps_shader,
const PipelineStaticParameters& static_params, uint32_t vs_hash0, uint32_t vs_crc32,
uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active) {
EXIT_IF(ps_active && ps_input_info == nullptr);
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params) {
const bool ps_active = ps_input_info != nullptr;
EXIT_IF(vertex_module == nullptr || (ps_active && pixel_module == nullptr));
const bool rect_list = static_params.topology == vk::PrimitiveTopology::ePatchList;
vk::ShaderModule vert_shader_module = nullptr;
vk::ShaderModule tess_control_shader_module = nullptr;
vk::ShaderModule tess_eval_shader_module = nullptr;
vk::ShaderModule frag_shader_module = nullptr;
vk::ShaderModuleCreateInfo create_info {};
create_info.sType = vk::StructureType::eShaderModuleCreateInfo;
create_info.pNext = nullptr;
create_info.flags = {};
create_info.codeSize = vs_shader.size() * 4;
create_info.pCode = vs_shader.data();
auto result = graphics.device.createShaderModule(&create_info, nullptr, &vert_shader_module);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateShaderModule VS done result=%s module=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(vert_shader_module));
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
if (ps_active) {
create_info.codeSize = ps_shader.size() * 4;
create_info.pCode = ps_shader.data();
result = graphics.device.createShaderModule(&create_info, nullptr, &frag_shader_module);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateShaderModule PS done result=%s module=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(frag_shader_module));
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
}
vk::Result result {};
if (rect_list) {
const auto shaders =
BuildRectListShaders(vs_input_info, ps_active ? ps_input_info : nullptr);
@@ -520,40 +459,27 @@ void CreatePipelineInternal(
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
}
EXIT_NOT_IMPLEMENTED(vert_shader_module == nullptr);
EXIT_NOT_IMPLEMENTED(
rect_list && (tess_control_shader_module == nullptr || tess_eval_shader_module == nullptr));
EXIT_NOT_IMPLEMENTED(ps_active && frag_shader_module == nullptr);
vk::PipelineShaderStageCreateInfo vert_shader_stage_info {};
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo vert_subgroup_size {};
vk::PipelineShaderStageCreateInfo vert_shader_stage_info {};
vert_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
vert_shader_stage_info.pNext = nullptr;
vert_shader_stage_info.flags = {};
vert_shader_stage_info.stage = vk::ShaderStageFlagBits::eVertex;
vert_shader_stage_info.module = vert_shader_module;
vert_shader_stage_info.module = vertex_module;
vert_shader_stage_info.pName = "main";
vert_shader_stage_info.pSpecializationInfo = nullptr;
EXIT_IF(!vs_input_info.stage);
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eVertex, *vs_input_info.stage.program,
vert_subgroup_size, vert_shader_stage_info);
vk::PipelineShaderStageCreateInfo frag_shader_stage_info {};
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo frag_subgroup_size {};
vk::PipelineShaderStageCreateInfo frag_shader_stage_info {};
frag_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
frag_shader_stage_info.pNext = nullptr;
frag_shader_stage_info.flags = {};
frag_shader_stage_info.stage = vk::ShaderStageFlagBits::eFragment;
frag_shader_stage_info.module = frag_shader_module;
frag_shader_stage_info.module = pixel_module;
frag_shader_stage_info.pName = "main";
frag_shader_stage_info.pSpecializationInfo = nullptr;
if (ps_active) {
EXIT_IF(!ps_input_info->stage);
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eFragment,
*ps_input_info->stage.program, frag_subgroup_size,
frag_shader_stage_info);
}
vk::PipelineShaderStageCreateInfo tess_control_shader_stage_info {};
tess_control_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
@@ -596,9 +522,10 @@ void CreatePipelineInternal(
uint32_t attr_size = 4;
auto registers_num = vs_input_info.resources_dst[index].registers_num;
auto used_components = (vs_input_info.resource_fetch_components[index] > 0
? vs_input_info.resource_fetch_components[index]
: registers_num);
const auto compiled_components =
vs_input_info.stage.program->info.vertex_fetch_components[index];
auto used_components =
compiled_components > 0 ? static_cast<int>(compiled_components) : registers_num;
GetInputFormat(vs_input_info.resources[index], input_attr[index].format, attr_size,
static_cast<uint32_t>(used_components));
@@ -723,7 +650,8 @@ void CreatePipelineInternal(
input_assembly.pNext = nullptr;
input_assembly.flags = {};
input_assembly.topology = static_params.topology;
input_assembly.primitiveRestartEnable = VK_FALSE;
input_assembly.primitiveRestartEnable =
static_params.primitive_restart_enable ? VK_TRUE : VK_FALSE;
vk::Viewport viewport {};
viewport.x = static_params.viewport_offset[0] - static_params.viewport_scale[0];
@@ -873,40 +801,36 @@ void CreatePipelineInternal(
color_blending.blendConstants[2] = static_params.blend_color_blue;
color_blending.blendConstants[3] = static_params.blend_color_alpha;
vk::DescriptorSetLayout set_layouts[2] = {};
uint32_t set_layouts_num = 0;
vk::PushConstantRange push_constant_info[2];
uint32_t push_constant_info_num = 0;
EXIT_IF(!vs_input_info.stage);
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
push_constant_info_num, *vs_input_info.stage.program,
vk::ShaderStageFlagBits::eVertex, DescriptorCache::Stage::Vertex);
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
AddLayoutBindings(descriptor_bindings, *vs_input_info.stage.program,
vk::ShaderStageFlagBits::eVertex);
if (ps_active) {
EXIT_IF(!ps_input_info->stage);
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
push_constant_info_num, *ps_input_info->stage.program,
vk::ShaderStageFlagBits::eFragment, DescriptorCache::Stage::Pixel);
AddLayoutBindings(descriptor_bindings, *ps_input_info->stage.program,
vk::ShaderStageFlagBits::eFragment);
}
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
constexpr auto GraphicsStages =
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
const vk::PushConstantRange push_constants {
GraphicsStages, 0, ShaderRecompiler::IR::NativePushConstantSize};
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
pipeline_layout_info.pNext = nullptr;
pipeline_layout_info.flags = {};
pipeline_layout_info.setLayoutCount = set_layouts_num;
pipeline_layout_info.pSetLayouts = (set_layouts_num > 0 ? set_layouts : nullptr);
pipeline_layout_info.pushConstantRangeCount = push_constant_info_num;
pipeline_layout_info.pPushConstantRanges =
push_constant_info_num > 0 ? push_constant_info : nullptr;
pipeline_layout_info.setLayoutCount = 1;
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
pipeline_layout_info.pushConstantRangeCount = 1;
pipeline_layout_info.pPushConstantRanges = &push_constants;
EXIT_IF(pipeline.pipeline_layout != nullptr);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreatePipelineLayout begin VS=0x%08" PRIx32 "/0x%08" PRIx32
" PS=0x%08" PRIx32 "/0x%08" PRIx32 " set_layouts=%" PRIu32 " push_constants=%" PRIu32
"\n",
vs_hash0, vs_crc32, ps_hash0, ps_crc32, set_layouts_num, push_constant_info_num);
LOGF("PipelineTrace: vkCreatePipelineLayout begin VS=%" PRIu64 " PS=%" PRIu64
" set_layouts=1 push_constants=%" PRIu32 "\n",
pipeline.vs_shader_id, pipeline.ps_shader_id, 1u);
}
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
@@ -953,11 +877,17 @@ void CreatePipelineInternal(
vk::DynamicState::eStencilReference,
vk::DynamicState::eStencilWriteMask,
#if !defined(__APPLE__)
// Keep last so depth-only pipelines can omit this dynamic state.
vk::DynamicState::eColorWriteEnableEXT, // unsupported by MoltenVK; static mask instead
#endif
};
const auto dynamic_states_count =
auto dynamic_states_count =
static_cast<uint32_t>(sizeof(dynamic_states) / sizeof(dynamic_states[0]));
#if !defined(__APPLE__)
if (static_params.color_count == 0) {
dynamic_states_count--;
}
#endif
vk::PipelineDynamicStateCreateInfo dynamic_state {};
dynamic_state.sType = vk::StructureType::ePipelineDynamicStateCreateInfo;
@@ -999,11 +929,12 @@ void CreatePipelineInternal(
EXIT_IF(pipeline.pipeline != nullptr);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateGraphicsPipelines begin VS=0x%08" PRIx32 "/0x%08" PRIx32
" PS=0x%08" PRIx32 "/0x%08" PRIx32 " topology=%" PRIu32 " color_mask=0x%08" PRIx32
LOGF("PipelineTrace: vkCreateGraphicsPipelines begin VS=%" PRIu64 " PS=%" PRIu64
" topology=%" PRIu32 " color_mask=0x%08" PRIx32
" depth=%s blend=%s dyn_states=%" PRIu32 " viewport=%f,%f %fx%f"
" scissor=%d,%d %ux%u\n",
vs_hash0, vs_crc32, ps_hash0, ps_crc32, static_cast<uint32_t>(static_params.topology),
pipeline.vs_shader_id, pipeline.ps_shader_id,
static_cast<uint32_t>(static_params.topology),
static_params.color_mask[0], (static_params.with_depth ? "true" : "false"),
(static_params.blend_enable[0] ? "true" : "false"), dynamic_states_count, viewport.x,
viewport.y, viewport.width, viewport.height, scissor.offset.x, scissor.offset.y,
@@ -1019,40 +950,19 @@ void CreatePipelineInternal(
EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);
if (frag_shader_module != nullptr) {
graphics.device.destroyShaderModule(frag_shader_module, nullptr);
}
if (tess_control_shader_module != nullptr) {
graphics.device.destroyShaderModule(tess_control_shader_module, nullptr);
}
if (tess_eval_shader_module != nullptr) {
graphics.device.destroyShaderModule(tess_eval_shader_module, nullptr);
}
graphics.device.destroyShaderModule(vert_shader_module, nullptr);
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
std::span<const uint32_t> cs_shader) {
vk::ShaderModule comp_shader_module = nullptr;
vk::ShaderModuleCreateInfo create_info {};
create_info.sType = vk::StructureType::eShaderModuleCreateInfo;
create_info.pNext = nullptr;
create_info.flags = {};
create_info.codeSize = cs_shader.size() * 4;
create_info.pCode = cs_shader.data();
LOGF("PipelineTrace: vkCreateShaderModule CS begin words=%" PRIu64 "\n",
static_cast<uint64_t>(cs_shader.size()));
auto result = graphics.device.createShaderModule(&create_info, nullptr, &comp_shader_module);
LOGF("PipelineTrace: vkCreateShaderModule CS done result=%s module=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(comp_shader_module));
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
EXIT_NOT_IMPLEMENTED(comp_shader_module == nullptr);
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
vk::ShaderModule compute_module) {
EXIT_IF(compute_module == nullptr);
vk::PipelineShaderStageCreateInfo comp_shader_stage_info {};
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo comp_subgroup_size {};
@@ -1060,40 +970,41 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
comp_shader_stage_info.pNext = nullptr;
comp_shader_stage_info.flags = {};
comp_shader_stage_info.stage = vk::ShaderStageFlagBits::eCompute;
comp_shader_stage_info.module = comp_shader_module;
comp_shader_stage_info.module = compute_module;
comp_shader_stage_info.pName = "main";
comp_shader_stage_info.pSpecializationInfo = nullptr;
EXIT_IF(!input_info.stage);
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eCompute, *input_info.stage.program,
comp_subgroup_size, comp_shader_stage_info);
const auto wave_size = input_info.stage.program->wave_size;
if (graphics.compute_subgroup_size_control_enabled &&
wave_size >= graphics.min_subgroup_size && wave_size <= graphics.max_subgroup_size) {
comp_subgroup_size.sType =
vk::StructureType::ePipelineShaderStageRequiredSubgroupSizeCreateInfo;
comp_subgroup_size.requiredSubgroupSize = wave_size;
comp_shader_stage_info.pNext = &comp_subgroup_size;
}
vk::DescriptorSetLayout set_layouts[1] = {};
uint32_t set_layouts_num = 0;
vk::PushConstantRange push_constant_info[1];
uint32_t push_constant_info_num = 0;
EXIT_IF(!input_info.stage);
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
push_constant_info_num, *input_info.stage.program,
vk::ShaderStageFlagBits::eCompute, DescriptorCache::Stage::Compute);
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
AddLayoutBindings(descriptor_bindings, *input_info.stage.program,
vk::ShaderStageFlagBits::eCompute);
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
const vk::PushConstantRange push_constants {vk::ShaderStageFlagBits::eCompute, 0,
ShaderRecompiler::IR::NativePushConstantSize};
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
pipeline_layout_info.pNext = nullptr;
pipeline_layout_info.flags = {};
pipeline_layout_info.setLayoutCount = set_layouts_num;
pipeline_layout_info.pSetLayouts = (set_layouts_num > 0 ? set_layouts : nullptr);
pipeline_layout_info.pushConstantRangeCount = push_constant_info_num;
pipeline_layout_info.pPushConstantRanges =
push_constant_info_num > 0 ? push_constant_info : nullptr;
pipeline_layout_info.setLayoutCount = 1;
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
pipeline_layout_info.pushConstantRangeCount = 1;
pipeline_layout_info.pPushConstantRanges = &push_constants;
EXIT_IF(pipeline.pipeline_layout != nullptr);
LOGF("PipelineTrace: vkCreatePipelineLayout CS begin set_layouts=%u push_constants=%u\n",
set_layouts_num, push_constant_info_num);
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
LOGF("PipelineTrace: vkCreatePipelineLayout CS begin set_layouts=1 push_constants=%u\n",
1u);
auto result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
LOGF("PipelineTrace: vkCreatePipelineLayout CS done result=%s layout=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
@@ -1119,8 +1030,6 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);
graphics.device.destroyShaderModule(comp_shader_module, nullptr);
}
} // namespace Libs::Graphics
+61 -99
View File
@@ -4,12 +4,14 @@
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <array>
#include <memory>
#include <optional>
#include <span>
#include <vector>
namespace Libs::Graphics {
@@ -23,9 +25,6 @@ class Shader;
struct GraphicContext;
struct ShaderBufferResource;
struct ShaderComputeInputInfo;
struct CommandSlot;
struct VulkanBuffer;
struct VulkanDescriptorSet;
struct RenderDepthInfo;
struct RenderColorInfo;
struct DrawCallInfo;
@@ -49,20 +48,6 @@ enum class CommandBufferDebugOp : uint32_t {
Unknown,
};
class FenceResourceRetainer {
public:
FenceResourceRetainer() = default;
~FenceResourceRetainer();
KYTY_CLASS_NO_COPY(FenceResourceRetainer);
void Retain(std::shared_ptr<void> resource);
void ReleaseAfterFence() noexcept;
[[nodiscard]] bool Empty() const noexcept { return m_resources.empty(); }
private:
std::vector<std::shared_ptr<void>> m_resources;
};
struct SubmitInfo {
static constexpr uint32_t MaxSemaphores = 3;
@@ -91,78 +76,52 @@ struct SubmitInfo {
class CommandBuffer {
public:
explicit CommandBuffer(CommandScheduler& scheduler);
~CommandBuffer();
~CommandBuffer() = default;
KYTY_CLASS_NO_COPY(CommandBuffer);
[[nodiscard]] bool IsInvalid() const;
void Begin() const;
void End() const;
void Execute(const SubmitInfo& submit = {});
void SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0 = 0, uint32_t arg1 = 0,
uint32_t arg2 = 0, uint32_t arg3 = 0, uint64_t arg4 = 0);
void BeginRendering(const RenderState& state) const;
void EndRendering() const;
void WaitForFenceOnly();
void WaitForFence();
void WaitForFenceAndReset();
void RetireBufferAfterFence(std::unique_ptr<VulkanBuffer> buffer);
void RetainResourceUntilFence(std::shared_ptr<void> resource);
void RecycleDescriptorAfterFence(VulkanDescriptorSet& set);
[[nodiscard]] vk::CommandBuffer Handle() const;
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
[[nodiscard]] RenderContext& GetContext() const noexcept { return m_context; }
[[nodiscard]] bool IsExecute() const { return m_execute; }
[[nodiscard]] HW::Context& GetRegisters() const noexcept { return *m_registers; }
[[nodiscard]] HW::UserConfig& GetUserConfig() const noexcept { return *m_user_config; }
[[nodiscard]] HW::Shader& GetShaders() const noexcept { return *m_shaders; }
private:
void Release();
void FinalizeFence(bool reset_recording);
void ReleaseResourcesAfterFence();
void DeleteBuffersAfterFence();
void RecycleDescriptorsAfterFence();
RenderContext& m_context;
CommandScheduler& m_scheduler;
GraphicContext& m_graphics;
CommandSlot* m_slot = nullptr;
bool m_execute = false;
bool m_fence_waited = false;
uint64_t m_submit_seq = 0;
uint32_t m_debug_op = 0;
uint64_t m_debug_submit_id = 0;
uint32_t m_debug_arg0 = 0;
uint32_t m_debug_arg1 = 0;
uint32_t m_debug_arg2 = 0;
uint32_t m_debug_arg3 = 0;
uint64_t m_debug_arg4 = 0;
std::vector<std::unique_ptr<VulkanBuffer>> m_retired_buffers;
FenceResourceRetainer m_fence_resources;
std::vector<VulkanDescriptorSet*> m_descriptor_sets_after_fence;
mutable RenderState m_render_state;
mutable bool m_rendering = false;
};
class RenderCommandBuffer final: public CommandBuffer {
public:
explicit RenderCommandBuffer(CommandScheduler& scheduler): CommandBuffer(scheduler) {}
explicit CommandBuffer(CommandScheduler& scheduler);
void Bind(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders) noexcept {
m_registers = &registers;
m_user_config = &user_config;
m_shaders = &shaders;
}
[[nodiscard]] HW::Context& GetRegisters() const noexcept { return *m_registers; }
[[nodiscard]] HW::UserConfig& GetUserConfig() const noexcept { return *m_user_config; }
[[nodiscard]] HW::Shader& GetShaders() const noexcept { return *m_shaders; }
void Begin();
void End() const;
private:
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
RenderContext& m_context;
GraphicContext& m_graphics;
vk::CommandBuffer m_buffer = nullptr;
uint32_t m_debug_op = 0;
uint64_t m_debug_submit_id = 0;
uint32_t m_debug_arg0 = 0;
uint32_t m_debug_arg1 = 0;
uint32_t m_debug_arg2 = 0;
uint32_t m_debug_arg3 = 0;
uint64_t m_debug_arg4 = 0;
mutable RenderState m_render_state;
mutable bool m_rendering = false;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
friend class CommandScheduler;
};
class RenderExecutor {
@@ -170,66 +129,69 @@ public:
explicit RenderExecutor(RenderContext& context): m_context(context) {}
KYTY_CLASS_NO_COPY(RenderExecutor);
void DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_type_and_size,
void DrawIndex(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_type_and_size,
uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
uint32_t instance_count = 1, uint32_t render_target_slice_offset = 0,
int32_t vertex_offset_add = 0, uint32_t first_instance = 0);
void DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
uint32_t flags, uint32_t render_target_slice_offset = 0,
uint32_t instance_count = 1, uint32_t first_vertex = 0,
uint32_t first_instance = 0);
void DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t thread_group_x,
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_count, uint32_t flags,
uint32_t render_target_slice_offset = 0, uint32_t instance_count = 1,
uint32_t first_vertex = 0, uint32_t first_instance = 0);
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
[[nodiscard]] DescriptorCache::PreparedBindings
PrepareBindings(CommandBuffer& buffer, const ShaderStageRuntime& runtime,
vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage);
void RebindBuffers(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
void RebindImages(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
[[nodiscard]] PreparedBindings PrepareBindings(const ShaderStageRuntime& runtime);
void FindBuffers(PreparedBindings& bindings);
void RebindBuffers(PreparedBindings& bindings);
void RebindImages(PreparedBindings& bindings);
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
vk::PipelineLayout layout, DescriptorCache::PreparedBindings& bindings);
const PipelineCache::Pipeline& pipeline,
std::span<PreparedBindings* const> bindings);
private:
struct GraphicsBindings {
DescriptorCache::PreparedBindings vertex;
std::optional<DescriptorCache::PreparedBindings> pixel;
PreparedBindings vertex;
std::optional<PreparedBindings> pixel;
};
[[nodiscard]] DescriptorCache::TextureBinding
ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value);
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(CommandBuffer& buffer,
const ShaderStageRuntime& vertex,
[[nodiscard]] TextureBinding ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value);
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel,
bool pixel_active);
void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderColorInfo& target, uint32_t render_target_slice_offset = 0,
uint32_t render_target_slot = UINT32_MAX,
bool ignore_target_mask = false, bool exact_format = false);
void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderDepthInfo& target);
[[nodiscard]] bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
[[nodiscard]] bool PrepareDrawRenderState(uint64_t submit_id, CommandBuffer& buffer,
const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
bool log_setup_phases, DrawRenderState& state);
void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source, bool log_pipeline_phase,
void ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buffer, const DrawCallInfo& draw,
DrawRenderState& state, vk::PrimitiveTopology topology,
const DrawEmitInfo& emit, const DrawIndexBufferSource& index_source,
bool primitive_restart_enable, bool log_pipeline_phase,
bool set_bind_debug, bool set_auto_debug);
[[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth);
[[nodiscard]] bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
[[nodiscard]] bool ResolveColorTargets(uint64_t submit_id, CommandBuffer& buffer,
uint32_t render_target_slice_offset);
void BindImage(ImageId id, bool storage);
void BindRenderTarget(ImageId id);
void TrackImageBinding(ImageId id);
void ResetBindings();
[[nodiscard]] bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const RenderCommandBuffer& buffer);
const CommandBuffer& buffer);
RenderContext& m_context;
std::vector<std::shared_ptr<Image>> m_bound_images;
RenderContext& m_context;
std::vector<ImageId> m_bound_images;
std::vector<vk::DescriptorBufferInfo> m_descriptor_buffers;
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
std::vector<uint32_t> m_image_occurrences;
std::array<uint32_t, ShaderRecompiler::IR::NativePushConstantSize / sizeof(uint32_t)>
m_push_constants {};
friend struct RenderExecutorTestAccess;
};
@@ -11,16 +11,14 @@
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image/imageInfo.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/shader.h"
#include "kernel/eventQueue.h"
#include "kernel/pthread.h"
@@ -46,7 +44,7 @@ static uint64_t BufferDescriptorSize(const ShaderBufferResource& descriptor) {
}
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const RenderCommandBuffer& buffer) {
const CommandBuffer& buffer) {
const auto& program = *input.stage.program;
const auto& resources = *input.stage.resources;
if (resources.buffers.size() != program.info.buffers.size()) {
@@ -83,9 +81,8 @@ bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t grou
const auto& program = *input.stage.program;
const auto& resources = *input.stage.resources;
if (program.info.buffers.size() != 1 || resources.buffers.size() != 1 ||
!program.info.images.empty() || !program.info.samplers.empty() ||
!program.info.addresses.empty() || !resources.images.empty() ||
!resources.samplers.empty() || !resources.addresses.empty()) {
!program.info.images.empty() || !program.info.samplers.empty() || program.info.uses_dma ||
!resources.images.empty() || !resources.samplers.empty()) {
return false;
}
const auto& resource = program.info.buffers.front();
@@ -93,7 +90,7 @@ bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t grou
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(raw);
if (!resource.formatted || !resource.written || resource.read || resource.atomic ||
resource.scalar || resource.max_byte_extent != 16 || descriptor.Stride() != 16 ||
descriptor.Format() != Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32UInt) ||
descriptor.Format() != Prospero::BufferFormat::k32_32_32_32UInt ||
descriptor.SwizzleEnabled() || descriptor.IndexStride() != 0 || descriptor.AddTid() ||
resource.packed_stride != descriptor.PackedStride() || raw.dword_count != 4 ||
program.user_data_base != 0 || resources.user_data.size() != 8) {
@@ -115,7 +112,7 @@ bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t grou
group_z == 1 && input.dispatch_threads_num[0] == group_x &&
input.dispatch_threads_num[1] == 1 && input.dispatch_threads_num[2] == 1 &&
input.group_id[0] && !input.group_id[1] && !input.group_id[2] &&
input.thread_ids_num == 1 && input.wave_size == 32 && !input.tg_size_en && mode == 0x61u &&
input.thread_ids_num == 1 && input.wave_size == 64 && !input.tg_size_en && mode == 0x61u &&
group_x % input.threads_num[0] == 0 && descriptor.NumRecords() == group_x;
const auto size = BufferDescriptorSize(descriptor);
if (!full_dispatch || size == 0) {
@@ -139,6 +136,21 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
}
auto& cache = command.GetContext().GetTextureCache();
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
// Recognized metadata-fill shaders access DCC as an ordinary storage buffer and may run
// before the render target is bound. TryConsumeDccFill either consumes registered state
// or retains a PendingDcc fill while allowing the dispatch to run.
const bool registered_metadata =
cache.TryConsumeDccFill(descriptor.Base48(), size, packed_clear);
static std::atomic<uint32_t> logged_metadata_clears {0};
if (logged_metadata_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("GraphicsRenderDispatchDirect: %s metadata clear shader=0x%016" PRIx64
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
registered_metadata ? "tracked" : "deferred", input.stage.program->shader_hash,
descriptor.Base48(), size, packed_clear);
}
if (registered_metadata) {
return true;
}
return false;
}
static std::atomic<uint32_t> logged_clears {0};
@@ -150,10 +162,11 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
return true;
}
void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer,
void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
uint32_t thread_group_x, uint32_t thread_group_y,
uint32_t thread_group_z, uint32_t mode) {
EXIT_IF(buffer.IsInvalid());
m_context.GetCommandScheduler().PopPendingOperations();
auto& ctx = buffer.GetRegisters();
auto& sh_ctx = buffer.GetShaders();
@@ -194,16 +207,12 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
const auto& cs_regs = sh_ctx.GetCs();
const auto& sh_regs = ctx.GetShaderRegisters();
ShaderComputeInputInfo input_info {};
std::span<const uint32_t> cs_shader;
if (!ShaderCompileInfoCS(cs_regs, sh_regs, input_info, cs_shader)) {
EXIT("ShaderCompileInfoCS failed for dispatch with CS shader 0x%016" PRIx64 "\n",
cs_regs.cs_regs.data_addr);
}
ShaderComputeInputInfo input_info {};
const bool use_thread_dimensions = (mode & DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0;
input_info.dispatch_thread_dimensions = use_thread_dimensions;
const auto compute_program =
m_context.GetPipelineCache().GetComputeProgram(cs_regs, sh_regs, input_info);
if (use_thread_dimensions) {
input_info.dispatch_thread_dimensions = true;
input_info.dispatch_threads_num[0] = thread_group_x;
input_info.dispatch_threads_num[1] = thread_group_y;
input_info.dispatch_threads_num[2] = thread_group_z;
@@ -246,7 +255,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
LOGF(" CS buffer[%u]: source=%u usage=%s addr=0x%012" PRIx64
" stride=%u records=%u format=%u\n",
i, buffer.source, buffer.written ? "read-write" : "read-only", r.Base48(),
r.Stride(), r.NumRecords(), r.Format());
r.Stride(), r.NumRecords(), r.RawFormat());
}
for (uint32_t i = 0; i < program.info.images.size(); i++) {
const auto& image = program.info.images[i];
@@ -258,13 +267,14 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint)
? "true"
: "false",
r.Base40(), static_cast<uint32_t>(r.Type()), r.Format(),
r.Base40(), static_cast<uint32_t>(r.Type()), static_cast<uint32_t>(r.Format()),
static_cast<uint32_t>(r.Width5()) + 1u, static_cast<uint32_t>(r.Height5()) + 1u,
static_cast<uint32_t>(r.Depth()) + 1u,
std::max<uint32_t>(static_cast<uint32_t>(r.LastLevel()),
static_cast<uint32_t>(r.MaxMip())) +
1u,
r.TileMode());
r.Type() == Prospero::ImageType::kColor2DMsaa ||
r.Type() == Prospero::ImageType::kColor2DMsaaArray
? 1u
: static_cast<uint32_t>(image.r128 ? r.LastLevel() : r.MaxMip()) + 1u,
static_cast<uint32_t>(r.TileMode()));
}
for (uint32_t i = 0; i < program.info.samplers.size(); i++) {
const auto r = DecodeNativeDescriptor<ShaderSamplerResource>(resources.samplers[i]);
@@ -317,14 +327,19 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
buffer.EndRendering();
auto& pipeline =
m_context.GetPipelineCache().CreateComputePipeline(input_info, sh_ctx.GetCs(), cs_shader);
auto bindings = PrepareBindings(buffer, input_info.stage, vk::ShaderStageFlagBits::eCompute,
DescriptorCache::Stage::Compute);
RebindBuffers(buffer, bindings);
RebindImages(buffer, bindings);
m_context.GetPipelineCache().CreateComputePipeline(input_info, compute_program);
auto bindings = PrepareBindings(input_info.stage);
FindBuffers(bindings);
if (program.info.uses_dma) {
m_context.GetGpuResources().PrepareBda();
}
RebindBuffers(bindings);
RebindImages(bindings);
auto vk_buffer = buffer.Handle();
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline.pipeline_layout, bindings);
auto vk_buffer = buffer.Handle();
PreparedBindings* descriptor_stage = &bindings;
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline,
std::span {&descriptor_stage, 1u});
bool has_storage_writes = HasShaderBufferWrites(input_info.stage);
has_storage_writes =
std::any_of(program.info.images.begin(), program.info.images.end(),
@@ -4,7 +4,6 @@
#include "common/logging/log.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/presentation/videoOut.h"
#include "kernel/pthread.h"
#include "libs/errno.h"
#include <algorithm>
@@ -13,8 +12,9 @@ namespace Libs::Graphics {
RenderContext::RenderContext(GraphicContext& graphics)
: m_graphics(graphics), m_render_executor(*this), m_command_scheduler(*this, graphics),
m_descriptor_cache(graphics), m_pipeline_cache(graphics, m_descriptor_cache),
m_sampler_cache(graphics), m_gpu_resources(graphics, m_command_scheduler) {
m_descriptor_heap(graphics, m_command_scheduler.GetMasterSemaphore()),
m_pipeline_cache(graphics), m_sampler_cache(graphics),
m_gpu_resources(graphics, m_command_scheduler) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
@@ -26,7 +26,7 @@ RenderContext::~RenderContext() {
void RenderContext::InitializeGpu(VideoOut::VideoOutDriver* video_out) {
EXIT_IF(m_gpu != nullptr);
m_video_out = video_out;
m_gpu = std::make_unique<Gpu>(*this);
m_gpu = std::make_unique<GuestGpu>(*this);
m_gpu_resources.SetGpu(m_gpu.get());
}
@@ -38,14 +38,14 @@ void RenderContext::ShutdownGpu() {
}
if (m_video_out != nullptr) {
if (m_command_scheduler.Active()) {
m_command_scheduler.FinishCurrent();
m_command_scheduler.Finish();
}
m_command_scheduler.DrainPriorityOperations();
m_video_out = nullptr;
}
}
Gpu& RenderContext::GetGpu() const {
GuestGpu& RenderContext::GetGpu() const {
EXIT_IF(m_gpu == nullptr);
return *m_gpu;
}
@@ -55,60 +55,54 @@ VideoOut::VideoOutDriver& RenderContext::GetVideoOut() const {
return *m_video_out;
}
void RenderContext::AddEopEq(LibKernel::EventQueue::KernelEqueue eq, int id) {
auto queue = LibKernel::EventQueue::KernelPinEqueue(eq);
if (!queue) {
void RenderContext::AddInterruptEq(LibKernel::EventQueue::KernelEqueue eq, int event_id) {
Common::LockGuard lock(m_interrupt_mutex);
auto it = std::find_if(
m_interrupt_eqs.begin(), m_interrupt_eqs.end(),
[eq, event_id](const auto& entry) { return entry.eq == eq && entry.event_id == event_id; });
if (it != m_interrupt_eqs.end()) {
return;
}
Common::LockGuard lock(m_eop_mutex);
auto it = std::find_if(m_eop_eqs.begin(), m_eop_eqs.end(), [eq, id](const auto& entry) {
return entry.eq == eq && entry.id == id;
});
if (it != m_eop_eqs.end()) {
return;
}
m_eop_eqs.push_back({eq, std::move(queue), id});
m_interrupt_eqs.push_back({eq, event_id});
}
void RenderContext::DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id) {
Common::LockGuard lock(m_eop_mutex);
void RenderContext::DeleteInterruptEq(LibKernel::EventQueue::KernelEqueue eq, int event_id) {
Common::LockGuard lock(m_interrupt_mutex);
auto it = std::find_if(m_eop_eqs.begin(), m_eop_eqs.end(), [eq, id](const auto& entry) {
return entry.eq == eq && entry.id == id;
});
if (it == m_eop_eqs.end()) {
auto it = std::find_if(
m_interrupt_eqs.begin(), m_interrupt_eqs.end(),
[eq, event_id](const auto& entry) { return entry.eq == eq && entry.event_id == event_id; });
if (it == m_interrupt_eqs.end()) {
return;
}
m_eop_eqs.erase(it);
m_interrupt_eqs.erase(it);
}
void RenderContext::TriggerEopEvent(uint32_t context_id) {
std::vector<EopEqRegistration> registrations;
void RenderContext::TriggerInterrupt(int event_id, uint32_t context_id) {
std::vector<InterruptEqRegistration> registrations;
{
Common::LockGuard lock(m_eop_mutex);
registrations = m_eop_eqs;
Common::LockGuard lock(m_interrupt_mutex);
for (const auto& registration: m_interrupt_eqs) {
if (registration.event_id == event_id) {
registrations.push_back(registration);
}
}
}
for (const auto& registration: registrations) {
const auto result = LibKernel::EventQueue::KernelTriggerEvent(
registration.eq, static_cast<uintptr_t>(registration.id),
registration.eq, static_cast<uintptr_t>(registration.event_id),
LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS,
reinterpret_cast<void*>(static_cast<uintptr_t>(context_id)));
if (result == LibKernel::KERNEL_ERROR_EBADF || result == LibKernel::KERNEL_ERROR_ENOENT) {
DeleteEopEq(registration.eq, registration.id);
DeleteInterruptEq(registration.eq, registration.event_id);
continue;
}
EXIT_NOT_IMPLEMENTED(result != OK);
}
auto tsc = LibKernel::KernelReadTsc();
auto result = LibKernel::EventQueue::KernelTriggerUserEventForAll(AGC_USER_INTERRUPT_EVENT,
reinterpret_cast<void*>(tsc));
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_ENOENT);
}
} // namespace Libs::Graphics
+14 -16
View File
@@ -10,7 +10,7 @@
#include "graphics/host_gpu/renderer/cache/samplerCache.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorHeap.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "kernel/eventQueue.h"
@@ -23,8 +23,7 @@ class VideoOutDriver;
namespace Libs::Graphics {
constexpr int AGC_USER_INTERRUPT_EVENT = 0x1800;
class Gpu;
class GuestGpu;
class RenderContext {
public:
@@ -35,43 +34,42 @@ public:
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
void InitializeGpu(VideoOut::VideoOutDriver* video_out);
void ShutdownGpu();
[[nodiscard]] Gpu& GetGpu() const;
[[nodiscard]] GuestGpu& GetGpu() const;
[[nodiscard]] VideoOut::VideoOutDriver& GetVideoOut() const;
Common::Mutex& GetMutex() { return m_mutex; }
CommandScheduler& GetCommandScheduler() { return m_command_scheduler; }
PipelineCache& GetPipelineCache() { return m_pipeline_cache; }
DescriptorCache& GetDescriptorCache() { return m_descriptor_cache; }
DescriptorHeap& GetDescriptorHeap() { return m_descriptor_heap; }
SamplerCache& GetSamplerCache() { return m_sampler_cache; }
GpuResourceManager& GetGpuResources() { return m_gpu_resources; }
BufferCache& GetBufferCache() { return m_gpu_resources.GetBufferCache(); }
TextureCache& GetTextureCache() { return m_gpu_resources.GetTextureCache(); }
RenderExecutor& GetRenderExecutor() { return m_render_executor; }
void AddEopEq(LibKernel::EventQueue::KernelEqueue eq, int id);
void DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id);
void TriggerEopEvent(uint32_t context_id);
void AddInterruptEq(LibKernel::EventQueue::KernelEqueue eq, int event_id);
void DeleteInterruptEq(LibKernel::EventQueue::KernelEqueue eq, int event_id);
void TriggerInterrupt(int event_id, uint32_t context_id);
private:
struct EopEqRegistration {
LibKernel::EventQueue::KernelEqueue eq = LibKernel::EventQueue::KERNEL_EQUEUE_INVALID;
LibKernel::EventQueue::KernelEqueueRef queue;
int id = 0;
struct InterruptEqRegistration {
LibKernel::EventQueue::KernelEqueue eq = LibKernel::EventQueue::KERNEL_EQUEUE_INVALID;
int event_id = 0;
};
GraphicContext& m_graphics;
Common::Mutex m_mutex;
RenderExecutor m_render_executor;
CommandScheduler m_command_scheduler;
DescriptorCache m_descriptor_cache;
DescriptorHeap m_descriptor_heap;
PipelineCache m_pipeline_cache;
SamplerCache m_sampler_cache;
GpuResourceManager m_gpu_resources;
std::unique_ptr<Gpu> m_gpu;
std::unique_ptr<GuestGpu> m_gpu;
VideoOut::VideoOutDriver* m_video_out = nullptr;
Common::Mutex m_eop_mutex;
std::vector<EopEqRegistration> m_eop_eqs;
Common::Mutex m_interrupt_mutex;
std::vector<InterruptEqRegistration> m_interrupt_eqs;
};
} // namespace Libs::Graphics
+325 -254
View File
@@ -15,15 +15,13 @@
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/shader.h"
#include "kernel/eventQueue.h"
#include "kernel/memory.h"
@@ -35,9 +33,11 @@
#include <atomic>
#include <bit>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <limits>
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <unordered_map>
@@ -103,7 +103,7 @@ static bool IsDualSourceBlendFactor(uint32_t factor) {
}
static void LogFramebufferSkip(const char* draw_name, const RenderColorInfo& color,
const RenderDepthInfo& depth, const RenderCommandBuffer& buffer,
const RenderDepthInfo& depth, const CommandBuffer& buffer,
uint32_t index_count, uint32_t flags) {
const auto& ctx = buffer.GetRegisters();
const auto& ucfg = buffer.GetUserConfig();
@@ -123,10 +123,10 @@ static void LogFramebufferSkip(const char* draw_name, const RenderColorInfo& col
log_id, draw_name, RenderColorTypeName(color.type), color.base_addr, color.buffer_size,
color.image_id ? "yes" : "no", VulkanToString(depth.format).c_str(),
depth.image_id ? "yes" : "no", depth.vaddr_num, ctx.GetRenderTargetMask(),
ucfg.GetPrimType(), index_count, flags);
static_cast<uint32_t>(ucfg.GetPrimType()), index_count, flags);
}
static void LogMrtState(const char* draw_name, const RenderCommandBuffer& buffer,
static void LogMrtState(const char* draw_name, const CommandBuffer& buffer,
const ShaderPixelInputInfo& ps_input_info) {
const auto& ctx = buffer.GetRegisters();
const auto& sh_regs = ctx.GetShaderRegisters();
@@ -140,18 +140,6 @@ static void LogMrtState(const char* draw_name, const RenderCommandBuffer& buffer
(bc0.separate_alpha_blend && (IsDualSourceBlendFactor(bc0.alpha_srcblend) ||
IsDualSourceBlendFactor(bc0.alpha_destblend)));
for (uint32_t i = 1; i < 8; i++) {
const auto& rt = ctx.GetRenderTarget(i);
if (rt.base.addr != 0 || ps_input_info.target_output_mode[i] != 0 ||
((rt_mask >> (i * 4u)) & 0x0fu) != 0 || ((cb_shader_mask >> (i * 4u)) & 0x0fu) != 0) {
interesting = true;
}
}
if (!interesting) {
return;
}
auto log_id = g_mrt_state_log_count.fetch_add(1);
if (log_id >= 32) {
return;
@@ -179,15 +167,17 @@ static void LogMrtState(const char* draw_name, const RenderCommandBuffer& buffer
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32
" width=%u height=%u tile=%u"
" blend=%s src=%u dst=%u alpha_src=%u alpha_dst=%u\n",
log_id, i, rt.base.addr, ctm, csm, ps_input_info.target_output_mode[i], rt.info.format,
rt.info.channel_type, rt.info.channel_order, rt.attrib2.width + 1,
rt.attrib2.height + 1, rt.attrib3.tile_mode, bc.enable ? "true" : "false",
bc.color_srcblend, bc.color_destblend, bc.alpha_srcblend, bc.alpha_destblend);
log_id, i, rt.base.addr, ctm, csm, ps_input_info.target_output_mode[i],
static_cast<uint32_t>(rt.info.format), static_cast<uint32_t>(rt.info.channel_type),
static_cast<uint32_t>(rt.info.channel_order), rt.attrib2.width + 1,
rt.attrib2.height + 1, static_cast<uint32_t>(rt.attrib3.tile_mode),
bc.enable ? "true" : "false", bc.color_srcblend, bc.color_destblend, bc.alpha_srcblend,
bc.alpha_destblend);
}
}
static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& color,
const RenderDepthInfo& depth, const RenderCommandBuffer& buffer,
const RenderDepthInfo& depth, const CommandBuffer& buffer,
const ShaderPixelInputInfo& ps_input_info, uint32_t index_count,
uint32_t flags) {
const auto& ctx = buffer.GetRegisters();
@@ -225,7 +215,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
"scissor=(%d,%d)-(%d,%d)\n",
log_id, buffer.GetContext().GetGpu().GetFrameNum(), draw_name,
RenderColorTypeName(color.type), color.base_addr, extent.width, extent.height,
ucfg.GetPrimType(), index_count, flags, ctx.GetRenderTargetMask(),
static_cast<uint32_t>(ucfg.GetPrimType()), index_count, flags, ctx.GetRenderTargetMask(),
color.color_clear_enable ? "true" : "false", color.color_clear_value.float32[0],
color.color_clear_value.float32[1], color.color_clear_value.float32[2],
color.color_clear_value.float32[3], cc.mode, cc.op, bc.enable ? "true" : "false",
@@ -241,7 +231,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
LogMrtState(draw_name, buffer, ps_input_info);
}
static void LogDrawInputState(const RenderCommandBuffer& buffer, const RenderColorInfo& color,
static void LogDrawInputState(const CommandBuffer& buffer, const RenderColorInfo& color,
const ShaderVertexInputInfo& vs_input_info,
uint32_t index_type_and_size, uint32_t index_count,
const void* index_addr) {
@@ -289,8 +279,7 @@ static void LogDrawInputState(const RenderCommandBuffer& buffer, const RenderCol
const auto& rd = vs_input_info.resources_dst[res_index];
const auto offset = b.attr_offsets[ai];
if (offset + 4u <= b.stride &&
r.Format() ==
Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm)) {
r.Format() == Prospero::BufferFormat::k8_8_8_8UNorm) {
uint32_t packed = 0;
std::memcpy(&packed, rec_bytes + offset, sizeof(packed));
const auto r8 = (packed >> 0u) & 0xffu;
@@ -320,89 +309,43 @@ static void LogDrawInputState(const RenderCommandBuffer& buffer, const RenderCol
}
}
static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandBuffer& buffer,
const RenderColorInfo* colors,
uint32_t color_count,
const RenderDepthInfo& depth) {
static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuffer vk_buffer,
const RenderColorInfo* colors, uint32_t color_count,
const RenderDepthInfo& depth) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(colors == nullptr);
const auto& ctx = buffer.GetRegisters();
PipelineDynamicParameters ret {};
ret.color_write_count = color_count;
ret.stencil_test_enable = depth.stencil_test_enable;
const auto& vp = ctx.GetScreenViewport();
vk::Extent2D framebuffer_extent {};
if (color_count > 0 && colors[0].image_id) {
framebuffer_extent = colors[0].extent;
} else if (depth.image_id) {
framebuffer_extent = {depth.width, depth.height};
} else {
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
framebuffer_extent = {limits.maxFramebufferWidth, limits.maxFramebufferHeight};
}
const auto final_scissor = calc_final_scissor(vp, ctx.GetScanModeControl(), framebuffer_extent);
ret.viewport_scale[0] = vp.viewports[0].xscale;
ret.viewport_scale[1] = vp.viewports[0].yscale;
ret.viewport_scale[2] = vp.viewports[0].zscale;
ret.viewport_offset[0] = vp.viewports[0].xoffset;
ret.viewport_offset[1] = vp.viewports[0].yoffset;
ret.viewport_offset[2] = vp.viewports[0].zoffset;
ret.scissor_ltrb[0] = final_scissor.left;
ret.scissor_ltrb[1] = final_scissor.top;
ret.scissor_ltrb[2] = final_scissor.right;
ret.scissor_ltrb[3] = final_scissor.bottom;
ret.line_width = ctx.GetLineWidth();
ret.stencil_front = depth.stencil_dynamic_front;
ret.stencil_back = depth.stencil_dynamic_back;
const auto& mode = ctx.GetModeControl();
const auto& poly_offset = ctx.GetPolyOffset();
const bool use_front = mode.poly_offset_front_enable && !mode.cull_front;
const bool use_back = mode.poly_offset_back_enable && !mode.cull_back;
ret.depth_bias_enable = use_front || use_back;
if (ret.depth_bias_enable) {
// Vulkan has one bias for both faces. Prefer a visible front face when both are enabled.
const float guest_constant_factor =
use_front ? poly_offset.front_offset : poly_offset.back_offset;
ret.depth_bias_constant_factor =
ConvertPolygonOffsetConstantFactor(guest_constant_factor, poly_offset, depth.format);
ret.depth_bias_clamp = poly_offset.clamp;
ret.depth_bias_slope_factor =
(use_front ? poly_offset.front_scale : poly_offset.back_scale) / 16.0f;
}
// CB_COLOR_CONTROL.operation controls special CB operations, not the normal color component
// write mask. Use CB_TARGET_MASK here so scanout passes with mode=Disable do not go black.
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
ret.color_write_enable[i] =
(i < color_count &&
render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot) != 0);
}
return ret;
}
static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
const PipelineDynamicParameters& dynamic_params) {
KYTY_PROFILER_FUNCTION();
vk::Viewport viewport {};
viewport.x = dynamic_params.viewport_offset[0] - dynamic_params.viewport_scale[0];
viewport.y = dynamic_params.viewport_offset[1] - dynamic_params.viewport_scale[1];
viewport.width = dynamic_params.viewport_scale[0] * 2.0f;
viewport.height = dynamic_params.viewport_scale[1] * 2.0f;
viewport.minDepth = dynamic_params.viewport_offset[2];
viewport.maxDepth = dynamic_params.viewport_scale[2] + dynamic_params.viewport_offset[2];
viewport.x = vp.viewports[0].xoffset - vp.viewports[0].xscale;
viewport.y = vp.viewports[0].yoffset - vp.viewports[0].yscale;
viewport.width = vp.viewports[0].xscale * 2.0f;
viewport.height = vp.viewports[0].yscale * 2.0f;
viewport.minDepth = vp.viewports[0].zoffset;
viewport.maxDepth = vp.viewports[0].zscale + vp.viewports[0].zoffset;
vk_buffer.setViewport(0, 1, &viewport);
vk::Rect2D scissor {};
scissor.offset = {dynamic_params.scissor_ltrb[0], dynamic_params.scissor_ltrb[1]};
scissor.extent = {
static_cast<uint32_t>(dynamic_params.scissor_ltrb[2] - dynamic_params.scissor_ltrb[0]),
static_cast<uint32_t>(dynamic_params.scissor_ltrb[3] - dynamic_params.scissor_ltrb[1])};
scissor.offset = {final_scissor.left, final_scissor.top};
scissor.extent = {static_cast<uint32_t>(final_scissor.right - final_scissor.left),
static_cast<uint32_t>(final_scissor.bottom - final_scissor.top)};
vk_buffer.setScissor(0, 1, &scissor);
float line_width = dynamic_params.line_width;
float line_width = ctx.GetLineWidth();
if (line_width != 1.0f) {
static bool logged = false;
if (!logged) {
@@ -415,26 +358,36 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
}
vk_buffer.setLineWidth(line_width);
vk_buffer.setDepthBiasEnable(dynamic_params.depth_bias_enable ? VK_TRUE : VK_FALSE);
if (dynamic_params.depth_bias_enable) {
vk_buffer.setDepthBias(dynamic_params.depth_bias_constant_factor,
dynamic_params.depth_bias_clamp,
dynamic_params.depth_bias_slope_factor);
const auto& mode = ctx.GetModeControl();
const auto& poly_offset = ctx.GetPolyOffset();
const bool use_front = mode.poly_offset_front_enable && !mode.cull_front;
const bool use_back = mode.poly_offset_back_enable && !mode.cull_back;
const bool depth_bias_enable = use_front || use_back;
vk_buffer.setDepthBiasEnable(depth_bias_enable ? VK_TRUE : VK_FALSE);
if (depth_bias_enable) {
// Vulkan has one bias for both faces. Prefer a visible front face when both are enabled.
const float guest_constant_factor =
use_front ? poly_offset.front_offset : poly_offset.back_offset;
const float constant_factor =
ConvertPolygonOffsetConstantFactor(guest_constant_factor, poly_offset, depth.format);
const float slope_factor =
(use_front ? poly_offset.front_scale : poly_offset.back_scale) / 16.0f;
vk_buffer.setDepthBias(constant_factor, poly_offset.clamp, slope_factor);
}
if (dynamic_params.stencil_test_enable) {
if (depth.stencil_test_enable) {
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eFront,
dynamic_params.stencil_front.compareMask);
depth.stencil_dynamic_front.compareMask);
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eBack,
dynamic_params.stencil_back.compareMask);
depth.stencil_dynamic_back.compareMask);
vk_buffer.setStencilWriteMask(vk::StencilFaceFlagBits::eFront,
dynamic_params.stencil_front.writeMask);
depth.stencil_dynamic_front.writeMask);
vk_buffer.setStencilWriteMask(vk::StencilFaceFlagBits::eBack,
dynamic_params.stencil_back.writeMask);
depth.stencil_dynamic_back.writeMask);
vk_buffer.setStencilReference(vk::StencilFaceFlagBits::eFront,
dynamic_params.stencil_front.reference);
depth.stencil_dynamic_front.reference);
vk_buffer.setStencilReference(vk::StencilFaceFlagBits::eBack,
dynamic_params.stencil_back.reference);
depth.stencil_dynamic_back.reference);
}
#if defined(__APPLE__)
@@ -442,11 +395,15 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
// eColorWriteEnableEXT dynamic state and relies on the static colorWriteMask instead.
#else
vk::Bool32 enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < dynamic_params.color_write_count; i++) {
enable[i] = (dynamic_params.color_write_enable[i] ? VK_TRUE : VK_FALSE);
// Color-control operation selects special color-buffer paths, not the normal component write
// mask. Attachment availability therefore follows the target write mask.
for (uint32_t i = 0; i < color_count; i++) {
enable[i] = render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot) != 0
? VK_TRUE
: VK_FALSE;
}
if (dynamic_params.color_write_count != 0) {
vk_buffer.setColorWriteEnableEXT(dynamic_params.color_write_count, enable);
if (color_count != 0) {
vk_buffer.setColorWriteEnableEXT(color_count, enable);
}
#endif
}
@@ -464,7 +421,7 @@ static bool PixelShaderHasDepthOrCoverageSideEffects(const HW::ShaderRegisters&
db.shader_execute_on_noop;
}
static bool ShouldSkipGeShader(const RenderCommandBuffer& buffer) {
static bool ShouldSkipGeShader(const CommandBuffer& buffer) {
const auto& ctx = buffer.GetRegisters();
const auto& ucfg = buffer.GetUserConfig();
const auto& sh_ctx = buffer.GetShaders();
@@ -502,6 +459,12 @@ static bool ShouldSkipGeShader(const RenderCommandBuffer& buffer) {
sh_regs.m_geMaxOutputPerSubgroup > 0x00000040;
if (unsupported_stage_mask || unsupported_gs_stage || ge_group_size || ge_shader_regs) {
static std::once_flag warning_once;
std::call_once(warning_once, [] {
std::printf("Warning: game uses unsupported graphics pipelines; some draw calls were "
"skipped.\n");
});
const auto log_id = g_shader_stage_log_count.fetch_add(1);
if (log_id < 32) {
LOGF("Skipping unsupported GE shader draw: stages=0x%08" PRIx32
@@ -520,15 +483,15 @@ static bool ShouldSkipGeShader(const RenderCommandBuffer& buffer) {
}
struct DrawRenderState {
RenderDepthInfo depth_info;
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint32_t color_count = 0;
bool ps_active = true;
RenderState rendering;
ShaderVertexInputInfo vs_input_info;
ShaderPixelInputInfo ps_input_info;
std::span<const uint32_t> vs_shader;
std::span<const uint32_t> ps_shader;
RenderDepthInfo depth_info;
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint32_t color_count = 0;
bool ps_active = true;
RenderState rendering;
ShaderVertexInputInfo vs_input_info;
ShaderPixelInputInfo ps_input_info;
ShaderProgram vertex_program;
ShaderProgram pixel_program;
};
struct DrawCallInfo {
@@ -540,6 +503,70 @@ struct DrawCallInfo {
uint32_t first_instance = 0;
};
static bool ResolveDccAttachmentClear(TextureCache& cache, const RenderColorInfo& target,
const ImageViewInfo& view, vk::ClearColorValue& clear_value) {
if (target.desc.info.metadata.kind != ImageMetadataKind::Dcc) {
return false;
}
// A DCC fast clear may update metadata only and leave the color allocation stale. Vulkan has
// no guest DCC state, so materialize the deferred value when the surface is next bound.
const auto metadata_address = target.desc.info.metadata.range.address;
uint32_t metadata_value = 0xffffffffu;
if (!cache.IsMetaCleared(metadata_address, view.base_layer, &metadata_value)) {
return false;
}
// Translate recognized constant and register-backed DCC states into a host clear value.
clear_value = {};
switch (static_cast<uint8_t>(metadata_value)) {
case 0x00: break;
case 0x20:
if (!target.metadata_clear_supported) {
return false;
}
clear_value = target.color_clear_value;
break;
case 0x40:
if (!target.metadata_fixed_clear_supported) {
return false;
}
clear_value.float32[3] = 1.0f;
break;
case 0x80:
if (!target.metadata_fixed_clear_supported) {
return false;
}
clear_value.float32[0] = 1.0f;
clear_value.float32[1] = 1.0f;
clear_value.float32[2] = 1.0f;
break;
case 0xc0:
if (!target.metadata_fixed_clear_supported) {
return false;
}
clear_value.float32[0] = 1.0f;
clear_value.float32[1] = 1.0f;
clear_value.float32[2] = 1.0f;
clear_value.float32[3] = 1.0f;
break;
default: return false;
}
for (uint32_t layer = 1; layer < view.layer_count; layer++) {
if (!cache.IsMetaCleared(metadata_address, view.base_layer + layer)) {
return false;
}
}
// Consume only after every layer in this Vulkan view can be materialized together.
for (uint32_t layer = 0; layer < view.layer_count; layer++) {
if (!cache.TouchMeta(metadata_address, view.base_layer + layer, false)) {
EXIT("failed to consume DCC clear state\n");
}
}
return true;
}
RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth) {
EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX);
@@ -553,7 +580,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
for (uint32_t i = 0; i < color_count; i++) {
auto& target = colors[i];
EXIT_IF(!target.image_id);
const auto old_image = cache.ResolveOwner(target.image_id);
const auto old_image = cache.m_slot_images.try_get(target.image_id);
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) {
if (old_image != nullptr) {
@@ -564,6 +591,15 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
}
target.image_view = cache.FindRenderTarget(target.image_id, target.desc);
auto& image = cache.GetImage(target.image_id);
SetVulkanObjectNameF(m_context.GetGraphics().device, image.backing.image,
"Kyty.MRT{}.Image[guest=0x{:016x} size=0x{:x} format={}]",
target.target_slot, image.info.data.address, image.info.data.size,
static_cast<uint32_t>(image.info.pixel_format));
SetVulkanObjectNameF(m_context.GetGraphics().device, target.image_view,
"Kyty.MRT{}.View[guest=0x{:016x} mip={} layer={}+{}]",
target.target_slot, image.info.data.address,
target.desc.view_info.base_level, target.desc.view_info.base_layer,
target.desc.view_info.layer_count);
EXIT_IF(image.backing.samples != target.samples || target.image_view == nullptr);
if (attachment_samples == 0) {
attachment_samples = target.samples;
@@ -587,10 +623,16 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
attachment.image_view = target.image_view;
attachment.image_layout = layout;
attachment.clear_value = target.color_clear_value.uint32;
attachment.is_clear = target.color_clear_enable;
vk::ClearColorValue metadata_clear_value {};
const bool metadata_clear =
ResolveDccAttachmentClear(cache, target, view, metadata_clear_value);
if (metadata_clear) {
attachment.clear_value = metadata_clear_value.uint32;
}
attachment.is_clear = target.color_clear_enable || metadata_clear;
}
if (depth.image_id) {
const auto owner = cache.ResolveOwner(depth.image_id);
const auto owner = cache.m_slot_images.try_get(depth.image_id);
if (owner == nullptr || !owner->registered || owner->binding.needs_rebind) {
EXIT("depth target changed after render-state discovery\n");
}
@@ -607,6 +649,14 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
EXIT("failed to consume HTile clear state\n");
}
auto& image = cache.GetImage(depth.image_id);
SetVulkanObjectNameF(m_context.GetGraphics().device, image.backing.image,
"Kyty.DepthTarget.Image[guest=0x{:016x} size=0x{:x} format={}]",
image.info.data.address, image.info.data.size,
static_cast<uint32_t>(image.info.pixel_format));
SetVulkanObjectNameF(m_context.GetGraphics().device, depth.image_view,
"Kyty.DepthTarget.View[guest=0x{:016x} layer={}+{}]",
image.info.data.address, depth.desc.view_info.base_layer,
depth.desc.view_info.layer_count);
EXIT_IF(depth.image_view == nullptr || image.backing.samples != depth.samples);
if (attachment_samples == 0) {
attachment_samples = depth.samples;
@@ -639,8 +689,12 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
attachment.has_stencil = static_cast<bool>(aspects & vk::ImageAspectFlagBits::eStencil);
attachment.stencil_clear = depth.stencil_clear_enable;
}
if (attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
if (color_count == 0 && !depth.image_id) {
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
state.width = limits.maxFramebufferWidth;
state.height = limits.maxFramebufferHeight;
} else if (attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
EXIT("render state has no valid attachments\n");
}
if (state.num_layers == std::numeric_limits<uint32_t>::max()) {
@@ -652,22 +706,12 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
return state;
}
static bool DrawHasActivePixelShader(const RenderCommandBuffer& buffer,
const DrawRenderState& state, const DrawCallInfo& draw) {
EXIT_IF(draw.name == nullptr);
const auto& ctx = buffer.GetRegisters();
const auto& sh_ctx = buffer.GetShaders();
const bool with_depth = (state.depth_info.format != vk::Format::eUndefined &&
static_cast<bool>(state.depth_info.image_id));
if (state.color_count != 0 || !with_depth) {
return true;
}
const auto& sh_regs = ctx.GetShaderRegisters();
const auto& ps = sh_ctx.GetPs();
return ShaderAddressValid(ps.ps_regs.data_addr) &&
PixelShaderHasDepthOrCoverageSideEffects(sh_regs);
static bool DrawHasActivePixelShader(const CommandBuffer& buffer) {
const auto& ctx = buffer.GetRegisters();
const auto& sh_regs = ctx.GetShaderRegisters();
const bool has_color_output = (ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask) != 0;
return ShaderAddressValid(buffer.GetShaders().GetPs().ps_regs.data_addr) &&
(has_color_output || PixelShaderHasDepthOrCoverageSideEffects(sh_regs));
}
enum class CbColorMode : uint8_t {
@@ -679,13 +723,14 @@ enum class CbColorMode : uint8_t {
DccDecompress = 6,
};
static bool ConsumeMetadataColorOperation(const RenderCommandBuffer& buffer) {
static bool ConsumeMetadataColorOperation(const CommandBuffer& buffer) {
const auto& ctx = buffer.GetRegisters();
const auto mode = ctx.GetColorControl().mode;
// These AGC CB modes run color-buffer metadata/decompression operations. The shader is a
// dummy vehicle for the CB, and its exported color must not be applied as a normal draw.
// Kyty currently stores host images as expanded Vulkan images and does not track CMASK/DCC
// metadata state, so the matching host operation is a no-op.
// These special modes run color-buffer metadata or decompression operations. The shader is a
// vehicle for that operation, and its exported color must not be applied as a normal draw.
// Kyty stores expanded Vulkan images rather than compressed guest surfaces, so no equivalent
// hardware pass is emitted. Tracked DCC clear state is materialized on attachment bind;
// future CMask/FMask support can consume its state through the same TextureCache path.
return mode == static_cast<uint8_t>(CbColorMode::EliminateFastClear) ||
mode == static_cast<uint8_t>(CbColorMode::FmaskDecompress) ||
mode == static_cast<uint8_t>(CbColorMode::DccDecompress);
@@ -706,11 +751,10 @@ struct DrawIndexBufferSource {
};
struct PreparedIndexBuffer {
std::shared_ptr<void> owner;
vk::Buffer buffer = nullptr;
uint64_t size = 0;
vk::DeviceSize offset = 0;
vk::IndexType type = vk::IndexType::eUint16;
vk::Buffer buffer = nullptr;
uint64_t size = 0;
vk::DeviceSize offset = 0;
vk::IndexType type = vk::IndexType::eUint16;
};
static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer) {
@@ -719,10 +763,10 @@ static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer
}
struct VertexBufferRange {
uint64_t base_address = 0;
uint64_t requested_end = 0;
uint64_t acquired_end = 0;
BufferBinding binding;
uint64_t base_address = 0;
uint64_t requested_end = 0;
uint64_t acquired_end = 0;
std::pair<Buffer*, uint64_t> binding;
[[nodiscard]] uint64_t RequestedSize() const { return requested_end - base_address; }
};
@@ -730,14 +774,12 @@ struct VertexBufferRange {
struct PreparedVertexBuffers {
static constexpr uint32_t MaxBuffers = ShaderVertexInputInfo::RES_MAX;
std::array<vk::Buffer, MaxBuffers> buffers {};
std::array<vk::DeviceSize, MaxBuffers> offsets {};
std::array<std::shared_ptr<void>, MaxBuffers> owners {};
uint32_t count = 0;
uint32_t owner_count = 0;
std::array<vk::Buffer, MaxBuffers> buffers {};
std::array<vk::DeviceSize, MaxBuffers> offsets {};
uint32_t count = 0;
};
static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& buffer,
static PreparedVertexBuffers AcquireVertexBuffers(CommandBuffer& buffer,
const ShaderVertexInputInfo& vs_input_info) {
EXIT_IF(vs_input_info.buffers_num < 0 ||
vs_input_info.buffers_num > ShaderVertexInputInfo::RES_MAX);
@@ -784,21 +826,22 @@ static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& b
const auto size =
Libs::LibKernel::Memory::ClampRangeSize(range.base_address, range.RequestedSize());
range.acquired_end = range.base_address + size;
range.binding = cache.ObtainBuffer(buffer, range.base_address, size);
range.binding = cache.ObtainBuffer(range.base_address, size, false);
SetVulkanObjectNameF(
buffer.GetContext().GetGraphics().device, range.binding.first->Handle(),
"Kyty.VertexBufferRange[guest=0x{:016x} size=0x{:x}]", range.base_address, size);
}
// Rebuild slot bindings, offsetting non-empty slots into their acquired merged range.
PreparedVertexBuffers prepared;
prepared.count = static_cast<uint32_t>(vs_input_info.buffers_num);
std::shared_ptr<Buffer> null_owner;
vk::Buffer null_buffer = nullptr;
prepared.count = static_cast<uint32_t>(vs_input_info.buffers_num);
vk::Buffer null_buffer = nullptr;
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex);
if (size == 0) {
if (null_owner == nullptr) {
null_owner = cache.ObtainNullBuffer();
null_buffer = null_owner->Handle();
if (null_buffer == nullptr) {
null_buffer = cache.GetBuffer(NULL_BUFFER_ID).Handle();
}
prepared.buffers[i] = null_buffer;
prepared.offsets[i] = 0;
@@ -815,24 +858,19 @@ static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& b
vertex.addr);
}
prepared.buffers[i] = range->binding.buffer;
prepared.offsets[i] = range->binding.offset + vertex.addr - range->base_address;
prepared.buffers[i] = range->binding.first->Handle();
prepared.offsets[i] = range->binding.second + vertex.addr - range->base_address;
SetVulkanObjectNameF(
buffer.GetContext().GetGraphics().device, prepared.buffers[i],
"Kyty.VertexBuffer[slot={} guest=0x{:016x} size=0x{:x} stride={} records={}]", i,
vertex.addr, size, vertex.stride, vertex.num_records);
}
if (null_owner != nullptr) {
prepared.owners[prepared.owner_count++] = std::move(null_owner);
}
for (uint32_t i = 0; i < merged_count; i++) {
if (merged_ranges[i].binding.owner != nullptr) {
EXIT_IF(prepared.owner_count >= PreparedVertexBuffers::MaxBuffers);
prepared.owners[prepared.owner_count++] = std::move(merged_ranges[i].binding.owner);
}
}
return prepared;
}
static void SetDrawDebugPhase(RenderCommandBuffer& buffer, uint64_t submit_id,
const DrawCallInfo& draw, uint32_t phase) {
static void SetDrawDebugPhase(CommandBuffer& buffer, uint64_t submit_id, const DrawCallInfo& draw,
uint32_t phase) {
EXIT_IF(draw.name == nullptr);
buffer.SetDebugInfo(static_cast<uint32_t>(draw.debug_op), submit_id, phase, draw.index_count,
@@ -844,7 +882,7 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
topology = vk::PrimitiveTopology::ePointList;
switch (static_cast<Prospero::PrimitiveType>(ucfg.GetPrimType())) {
switch (ucfg.GetPrimType()) {
case Prospero::PrimitiveType::kNone: return false;
case Prospero::PrimitiveType::kPointList:
topology = vk::PrimitiveTopology::ePointList;
@@ -867,20 +905,55 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
break;
case Prospero::PrimitiveType::kRectListLegacy:
if (!auto_draw) {
EXIT("unknown primitive type: %u\n", ucfg.GetPrimType());
EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
}
topology = vk::PrimitiveTopology::eTriangleStrip;
break;
case Prospero::PrimitiveType::kQuadListLegacy:
topology = vk::PrimitiveTopology::eTriangleFan;
break;
default: EXIT("unknown primitive type: %u\n", ucfg.GetPrimType());
default: EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
}
return true;
}
bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
static bool ResolvePrimitiveRestart(const CommandBuffer& buffer, vk::PrimitiveTopology topology,
uint32_t index_type_and_size) {
const auto control = buffer.GetUserConfig().GetPrimitiveResetControl();
EXIT_NOT_IMPLEMENTED((control & ~0x3u) != 0);
if ((control & 0x1u) == 0) {
return false;
}
switch (buffer.GetUserConfig().GetPrimType()) {
case Prospero::PrimitiveType::kLineStrip:
case Prospero::PrimitiveType::kTriFan:
case Prospero::PrimitiveType::kTriStrip: break;
default: return false;
}
if (topology != vk::PrimitiveTopology::eLineStrip &&
topology != vk::PrimitiveTopology::eTriangleStrip &&
topology != vk::PrimitiveTopology::eTriangleFan) {
return false;
}
uint32_t index_mask = 0;
switch (static_cast<Prospero::IndexType>(index_type_and_size)) {
case Prospero::IndexType::kIndex8: index_mask = 0xffu; break;
case Prospero::IndexType::kIndex16: index_mask = 0xffffu; break;
case Prospero::IndexType::kIndex32: index_mask = 0xffffffffu; break;
default: EXIT("unknown index_type_and_size: %u\n", index_type_and_size);
}
const auto reset_index = buffer.GetRegisters().GetPrimitiveResetIndex();
if ((control & 0x2u) != 0 && (reset_index & ~index_mask) != 0) {
return false;
}
EXIT_NOT_IMPLEMENTED((reset_index & index_mask) != index_mask);
return true;
}
bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, CommandBuffer& buffer,
const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
bool log_setup_phases, DrawRenderState& state) {
@@ -908,19 +981,19 @@ bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuf
}
ResolveRenderDepthTarget(submit_id, buffer, state.depth_info);
state.ps_active = DrawHasActivePixelShader(buffer);
const bool with_depth = (state.depth_info.format != vk::Format::eUndefined &&
static_cast<bool>(state.depth_info.image_id));
if (state.color_count == 0 && !with_depth) {
if (state.color_count == 0 && !with_depth && !state.ps_active) {
LogFramebufferSkip(draw.name, state.color_info[0], state.depth_info, buffer,
draw.index_count, draw.flags);
return false;
}
state.ps_active = DrawHasActivePixelShader(buffer, state, draw);
return true;
}
static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw, bool log_phases,
static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw, bool log_phases,
DrawRenderState& state) {
EXIT_IF(draw.name == nullptr);
auto& ctx = buffer.GetRegisters();
@@ -930,37 +1003,33 @@ static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw
const auto& pixel_shader_info = sh_ctx.GetPs();
const auto& shader_regs = ctx.GetShaderRegisters();
state.vs_shader = {};
state.ps_shader = {};
state.ps_input_info = {};
state.vertex_program = {};
state.pixel_program = {};
state.ps_input_info = {};
std::array<Prospero::ColorComponentMapping, RENDER_COLOR_ATTACHMENTS_MAX>
target_export_mapping {};
for (uint32_t i = 0; i < state.color_count; i++) {
target_export_mapping[state.color_info[i].target_slot] = state.color_info[i].export_mapping;
}
const auto lane_mask_mode = SelectGraphicsLaneMaskMode(64u);
if (log_phases) {
LogDrawPhase(draw.name, "ShaderCompileInfoVS");
}
if (!ShaderCompileInfoVS(vertex_shader_info, shader_regs, lane_mask_mode, state.vs_input_info,
state.vs_shader)) {
EXIT("ShaderCompileInfoVS failed for draw %s\n", draw.name);
LogDrawPhase(draw.name, "GetVertexProgram");
}
auto& pipeline_cache = buffer.GetContext().GetPipelineCache();
state.vertex_program =
pipeline_cache.GetVertexProgram(vertex_shader_info, shader_regs, state.vs_input_info);
if (!state.ps_active) {
return;
}
if (log_phases) {
LogDrawPhase(draw.name, "ShaderCompileInfoPS");
}
if (!ShaderCompileInfoPS(pixel_shader_info, shader_regs, lane_mask_mode, state.vs_input_info,
target_export_mapping, state.ps_input_info, state.ps_shader)) {
EXIT("ShaderCompileInfoPS failed for draw %s\n", draw.name);
LogDrawPhase(draw.name, "GetPixelProgram");
}
state.pixel_program =
pipeline_cache.GetPixelProgram(pixel_shader_info, shader_regs, state.vs_input_info,
target_export_mapping, state.ps_input_info);
}
static PreparedVertexBuffers PrepareVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer,
static PreparedVertexBuffers PrepareVertexBuffers(uint64_t submit_id, CommandBuffer& buffer,
const DrawCallInfo& draw,
const ShaderVertexInputInfo& vs_input_info) {
EXIT_IF(draw.name == nullptr);
@@ -970,7 +1039,7 @@ static PreparedVertexBuffers PrepareVertexBuffers(uint64_t submit_id, RenderComm
return AcquireVertexBuffers(buffer, vs_input_info);
}
static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer,
static PreparedIndexBuffer PrepareIndexBuffer(CommandBuffer& buffer,
const DrawIndexBufferSource& source) {
PreparedIndexBuffer prepared;
if (!source.enabled) {
@@ -980,28 +1049,29 @@ static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffe
prepared.size = source.size;
prepared.type = source.type;
if (source.host_data != nullptr) {
auto binding =
buffer.GetContext().GetBufferCache().UploadTransient(source.host_data, source.size, 16);
prepared.owner = std::move(binding.owner);
prepared.buffer = binding.buffer;
prepared.offset = binding.offset;
auto& stream = buffer.GetContext().GetBufferCache().GetUtilityBuffer(MemoryUsage::Stream);
prepared.offset = stream.Copy(source.host_data, source.size, 16);
prepared.buffer = stream.Handle();
} else {
auto binding =
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, source.address, source.size);
prepared.owner = std::move(binding.owner);
prepared.buffer = binding.buffer;
prepared.offset = binding.offset;
auto [buffer_ptr, offset] =
buffer.GetContext().GetBufferCache().ObtainBuffer(source.address, source.size, false);
prepared.buffer = buffer_ptr->Handle();
prepared.offset = offset;
}
if (source.host_data != nullptr) {
SetVulkanObjectNameF(buffer.GetContext().GetGraphics().device, prepared.buffer,
"Kyty.IndexBuffer[guest=transient size=0x{:x} type={}]", source.size,
static_cast<uint32_t>(source.type));
} else {
SetVulkanObjectNameF(buffer.GetContext().GetGraphics().device, prepared.buffer,
"Kyty.IndexBuffer[guest=0x{:016x} size=0x{:x} type={}]",
source.address, source.size, static_cast<uint32_t>(source.type));
}
return prepared;
}
static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
PreparedVertexBuffers& prepared) {
for (uint32_t i = 0; i < prepared.owner_count; i++) {
if (prepared.owners[i] != nullptr) {
buffer.RetainResourceUntilFence(std::move(prepared.owners[i]));
}
}
static void CommitVertexBuffers(vk::CommandBuffer vk_buffer,
const PreparedVertexBuffers& prepared) {
for (uint32_t i = 0; i < prepared.count; i++) {
EXIT_IF(prepared.buffers[i] == nullptr);
}
@@ -1011,19 +1081,15 @@ static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer v
}
}
static void CommitIndexBuffer(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
PreparedIndexBuffer& prepared) {
static void CommitIndexBuffer(vk::CommandBuffer vk_buffer, const PreparedIndexBuffer& prepared) {
if (prepared.size == 0) {
return;
}
if (prepared.owner != nullptr) {
buffer.RetainResourceUntilFence(std::move(prepared.owner));
}
EXIT_IF(prepared.buffer == nullptr);
vk_buffer.bindIndexBuffer(prepared.buffer, prepared.offset, prepared.type);
}
static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCallInfo& draw,
static void LogDrawStateIfNeeded(const CommandBuffer& buffer, const DrawCallInfo& draw,
const DrawRenderState& state, bool always_log,
bool force_legacy_rect_log, uint32_t index_type_and_size,
const void* index_addr) {
@@ -1051,7 +1117,7 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
const DrawEmitInfo& emit) {
EXIT_IF(draw.name == nullptr);
switch (static_cast<Prospero::PrimitiveType>(ucfg.GetPrimType())) {
switch (ucfg.GetPrimType()) {
case Prospero::PrimitiveType::kPointList:
case Prospero::PrimitiveType::kLineList:
case Prospero::PrimitiveType::kLineStrip:
@@ -1069,7 +1135,7 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
break;
case Prospero::PrimitiveType::kRectListLegacy:
if (emit.indexed) {
EXIT("unknown primitive type: %u\n", ucfg.GetPrimType());
EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
}
// Sarah
EXIT_NOT_IMPLEMENTED(!(draw.index_count == 3 && vs_input_info.buffers_num == 0));
@@ -1087,22 +1153,22 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
}
}
break;
default: EXIT("unknown primitive type: %u\n", ucfg.GetPrimType());
default: EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
}
}
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buffer,
const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source,
bool log_pipeline_phase, bool set_bind_debug,
bool set_auto_debug) {
bool primitive_restart_enable, bool log_pipeline_phase,
bool set_bind_debug, bool set_auto_debug) {
EXIT_IF(draw.name == nullptr);
auto& ucfg = buffer.GetUserConfig();
LogDrawPhase(draw.name, "PrepareBindings");
auto bindings = PrepareGraphicsBindings(buffer, state.vs_input_info.stage,
state.ps_input_info.stage, state.ps_active);
auto bindings = PrepareGraphicsBindings(state.vs_input_info.stage, state.ps_input_info.stage,
state.ps_active);
auto vertex_bindings = PrepareVertexBuffers(submit_id, buffer, draw, state.vs_input_info);
auto index_binding = PrepareIndexBuffer(buffer, index_source);
state.rendering =
@@ -1112,8 +1178,9 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
LogDrawPhase(draw.name, "CreatePipeline");
}
auto& pipeline = m_context.GetPipelineCache().CreateGraphicsPipeline(
state.color_info, state.color_count, state.depth_info, state.vs_input_info, buffer,
&state.ps_input_info, topology, state.ps_active, state.vs_shader, state.ps_shader);
std::span {state.color_info, state.color_count}, state.depth_info, state.vs_input_info, buffer,
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
state.vertex_program, state.pixel_program);
// Resource preparation above may synchronously finish and restart the scheduler. From this
// point onward, every operation targets the current command buffer and cannot touch guest
@@ -1125,21 +1192,23 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x200u);
}
CommitVertexBuffers(buffer, vk_buffer, vertex_bindings);
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
bindings.vertex);
CommitVertexBuffers(vk_buffer, vertex_bindings);
if (bindings.pixel.has_value()) {
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
}
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
*bindings.pixel);
}
CommitIndexBuffer(buffer, vk_buffer, index_binding);
std::array<PreparedBindings*, 2> descriptor_stages {&bindings.vertex, nullptr};
const size_t descriptor_stage_count = bindings.pixel.has_value() ? 2u : 1u;
if (bindings.pixel) {
descriptor_stages[1] = &*bindings.pixel;
}
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline,
std::span {descriptor_stages.data(), descriptor_stage_count});
CommitIndexBuffer(vk_buffer, index_binding);
const auto dynamic_params =
BuildGraphicsDynamicParams(buffer, state.color_info, state.color_count, state.depth_info);
SetDynamicParams(buffer, vk_buffer, dynamic_params);
SetGraphicsDynamicParams(buffer, vk_buffer, state.color_info, state.color_count,
state.depth_info);
LogDrawPhase(draw.name, "BeginRendering");
if (set_auto_debug) {
@@ -1172,7 +1241,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
}
}
void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
uint32_t index_type_and_size, uint32_t index_count,
const void* index_addr, uint32_t flags, uint32_t type,
uint32_t instance_count, uint32_t render_target_slice_offset,
@@ -1180,6 +1249,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
KYTY_PROFILER_FUNCTION();
EXIT_IF(buffer.IsInvalid());
m_context.GetCommandScheduler().PopPendingOperations();
auto& ucfg = buffer.GetUserConfig();
auto& sh_ctx = buffer.GetShaders();
@@ -1237,6 +1307,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
vk::IndexType index_type = vk::IndexType::eUint16;
uint64_t index_size = 0;
bool expand_index8_to_u16 = false;
const bool primitive_restart = ResolvePrimitiveRestart(buffer, topology, index_type_and_size);
switch (static_cast<Prospero::IndexType>(index_type_and_size)) {
case Prospero::IndexType::kIndex16:
@@ -1267,7 +1338,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
const auto* src = static_cast<const uint8_t*>(index_addr);
expanded_indices.resize(index_count);
for (uint32_t i = 0; i < index_count; i++) {
expanded_indices[i] = src[i];
expanded_indices[i] = primitive_restart && src[i] == 0xffu ? 0xffffu : src[i];
}
}
@@ -1297,19 +1368,20 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
emit.indexed = true;
emit.vertex_offset = vertex_offset;
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, true, true,
false);
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source,
primitive_restart, true, true, false);
ResetBindings();
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_count,
uint32_t flags, uint32_t render_target_slice_offset,
uint32_t instance_count, uint32_t first_vertex,
uint32_t first_instance) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(buffer.IsInvalid());
m_context.GetCommandScheduler().PopPendingOperations();
auto& ucfg = buffer.GetUserConfig();
auto& sh_ctx = buffer.GetShaders();
@@ -1387,9 +1459,8 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
}
LogDrawStateIfNeeded(buffer, draw, state, false,
ucfg.GetPrimType() ==
Prospero::GpuEnumValue(Prospero::PrimitiveType::kRectListLegacy),
0, nullptr);
ucfg.GetPrimType() == Prospero::PrimitiveType::kRectListLegacy, 0,
nullptr);
const auto vertex_offset = ResolveVertexOffset(ucfg.GetIndexOffset(), state.vs_input_info) +
static_cast<int32_t>(first_vertex);
@@ -1398,11 +1469,11 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
DrawIndexBufferSource index_source {};
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false, false,
true);
false, true);
ResetBindings();
}
bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, CommandBuffer& buffer,
uint32_t render_target_slice_offset) {
const auto& hw = buffer.GetRegisters();
if (hw.GetColorControl().mode != 3) {
@@ -1,43 +1,12 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERDRAW_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERDRAW_H_
#include "graphics/host_gpu/renderer/renderTarget.h"
#include <cstdint>
#include <type_traits>
namespace Libs::Graphics {
struct RenderColorInfo;
struct ShaderVertexInputInfo;
#pragma pack(push, 1)
struct PipelineDynamicParameters {
bool stencil_test_enable = false;
bool depth_bias_enable = false;
float viewport_scale[3] = {};
float viewport_offset[3] = {};
int scissor_ltrb[4] = {0};
float line_width = 1.0f;
float depth_bias_constant_factor = 0.0f;
float depth_bias_clamp = 0.0f;
float depth_bias_slope_factor = 0.0f;
uint32_t color_write_count = 1;
bool color_write_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {true, true};
PipelineStencilDynamicState stencil_front;
PipelineStencilDynamicState stencil_back;
};
#pragma pack(pop)
static_assert(std::is_trivially_copyable_v<PipelineDynamicParameters>);
static_assert(std::is_standard_layout_v<PipelineDynamicParameters>);
static_assert(alignof(PipelineDynamicParameters) == 1);
[[nodiscard]] int32_t ResolveVertexOffset(uint32_t index_offset,
const ShaderVertexInputInfo& vs_input_info);
+59 -68
View File
@@ -20,9 +20,7 @@
namespace Libs::Graphics::Sync {
constexpr int GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT = 0x00;
constexpr int GRAPHICS_EVENT_EOP = 0x40;
constexpr uint64_t GRAPHICS_REFERENCE_CLOCK_FREQUENCY = 100000000;
constexpr uint64_t GRAPHICS_REFERENCE_CLOCK_FREQUENCY = 100000000;
bool ScaleReferenceClock(uint64_t host_ticks, uint64_t host_frequency, uint64_t& value) {
if (host_frequency == 0) {
@@ -66,8 +64,9 @@ struct EndOfPipeSignal {
std::array<uint32_t, 4> debug_args = {};
uint64_t debug_data = 0;
std::optional<uint64_t> destination;
EndOfPipeCompletion completion = EndOfPipeCompletion::None;
uint64_t completion_data = 0;
EndOfPipeCompletion completion = EndOfPipeCompletion::None;
uint64_t completion_data = 0;
int interrupt_event_id = 0;
};
enum class EndOfPipeWriteSize : uint32_t { Dword = 4, Qword = 8 };
@@ -96,8 +95,10 @@ static void RecordEndOfPipeSignal(const EndOfPipeSignal& signal) {
case EndOfPipeCompletion::None: return;
case EndOfPipeCompletion::Interrupt: {
const auto context_id = static_cast<uint32_t>(signal.completion_data);
scheduler.DeferPriorityOperation(
[&renderer, context_id] { renderer.TriggerEopEvent(context_id); });
const auto event_id = signal.interrupt_event_id;
scheduler.DeferPriorityOperation([&renderer, event_id, context_id] {
renderer.TriggerInterrupt(event_id, context_id);
});
return;
}
case EndOfPipeCompletion::Flip: {
@@ -108,9 +109,10 @@ static void RecordEndOfPipeSignal(const EndOfPipeSignal& signal) {
}
case EndOfPipeCompletion::FlipAndInterrupt: {
const auto request_id = signal.completion_data;
scheduler.DeferPriorityOperation([&renderer, request_id] {
const auto event_id = signal.interrupt_event_id;
scheduler.DeferPriorityOperation([&renderer, event_id, request_id] {
renderer.GetVideoOut().CompleteFlip(request_id);
renderer.TriggerEopEvent(0);
renderer.TriggerInterrupt(event_id, 0);
});
return;
}
@@ -135,7 +137,8 @@ static bool TriggersInterrupt(EndOfPipeWriteAction action) {
static void RecordEndOfPipeWrite(uint64_t submit_id, CommandBuffer& buffer, uint64_t destination,
uint64_t value, EndOfPipeWriteSize size,
EndOfPipeWriteAction action, uint32_t context_id = 0) {
EndOfPipeWriteAction action, int interrupt_event_id = 0,
uint32_t context_id = 0) {
const auto width = static_cast<uint32_t>(size);
const auto value_low = static_cast<uint32_t>(value);
const auto value_high = static_cast<uint32_t>(value >> 32u);
@@ -150,18 +153,12 @@ static void RecordEndOfPipeWrite(uint64_t submit_id, CommandBuffer& buffer, uint
.debug_data = destination,
.destination = destination,
.completion = interrupt ? EndOfPipeCompletion::Interrupt : EndOfPipeCompletion::None,
.completion_data = context_id != 0 ? context_id : value,
.completion_data = interrupt ? context_id : value,
.interrupt_event_id = interrupt_event_id,
};
RecordEndOfPipeSignal(signal);
}
void TriggerAgcUserInterrupt() {
auto tsc = LibKernel::KernelReadTsc();
auto result = LibKernel::EventQueue::KernelTriggerUserEventForAll(AGC_USER_INTERRUPT_EVENT,
reinterpret_cast<void*>(tsc));
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_ENOENT);
}
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
uint32_t value) {
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
@@ -220,31 +217,35 @@ void WriteAtEndOfPipeWithWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
}
void WriteAtEndOfPipeWithInterruptWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
uint64_t* dst_gpu_addr, uint64_t value,
uint64_t* dst_gpu_addr, uint64_t value, int event_id,
uint32_t context_id) {
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::InterruptWriteBack,
context_id);
event_id, context_id);
}
void WriteAtEndOfPipeWithInterruptWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
uint32_t* dst_gpu_addr, uint32_t value,
uint32_t* dst_gpu_addr, uint32_t value, int event_id,
uint32_t context_id) {
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
EndOfPipeWriteSize::Dword, EndOfPipeWriteAction::InterruptWriteBack,
context_id);
event_id, context_id);
}
void WriteAtEndOfPipeWithInterrupt64(uint64_t submit_id, CommandBuffer& buffer,
uint64_t* dst_gpu_addr, uint64_t value, uint32_t context_id) {
uint64_t* dst_gpu_addr, uint64_t value, int event_id,
uint32_t context_id) {
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::Interrupt, context_id);
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::Interrupt, event_id,
context_id);
}
void WriteAtEndOfPipeWithInterrupt32(uint64_t submit_id, CommandBuffer& buffer,
uint32_t* dst_gpu_addr, uint32_t value, uint32_t context_id) {
uint32_t* dst_gpu_addr, uint32_t value, int event_id,
uint32_t context_id) {
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
EndOfPipeWriteSize::Dword, EndOfPipeWriteAction::Interrupt, context_id);
EndOfPipeWriteSize::Dword, EndOfPipeWriteAction::Interrupt, event_id,
context_id);
}
uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index, int flip_mode,
@@ -270,18 +271,20 @@ uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index, int f
void WriteAtEndOfPipeWithInterruptWriteBackFlip32(uint64_t submit_id, CommandBuffer& buffer,
uint32_t* dst_gpu_addr, uint32_t value,
int handle, int index, int flip_mode,
int64_t flip_arg, uint64_t request_id) {
int64_t flip_arg, uint64_t request_id,
int event_id) {
const auto destination = reinterpret_cast<uint64_t>(dst_gpu_addr);
RecordEndOfPipeSignal({
.buffer = &buffer,
.submit_id = submit_id,
.debug_operation = CommandBufferDebugOp::EopWriteBackFlip,
.debug_args = {static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
static_cast<uint32_t>(flip_mode), value},
.debug_data = static_cast<uint64_t>(flip_arg),
.destination = destination,
.completion = EndOfPipeCompletion::FlipAndInterrupt,
.completion_data = request_id,
.buffer = &buffer,
.submit_id = submit_id,
.debug_operation = CommandBufferDebugOp::EopWriteBackFlip,
.debug_args = {static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
static_cast<uint32_t>(flip_mode), value},
.debug_data = static_cast<uint64_t>(flip_arg),
.destination = destination,
.completion = EndOfPipeCompletion::FlipAndInterrupt,
.completion_data = request_id,
.interrupt_event_id = event_id,
});
}
@@ -316,36 +319,24 @@ void WriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer& buffer, int han
});
}
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, uint32_t context_id) {
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, int event_id, uint32_t context_id) {
ValidateEndOfPipeSignal({.buffer = &buffer});
auto& renderer = buffer.GetContext();
auto& scheduler = renderer.GetCommandScheduler();
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
scheduler.DeferPriorityOperation(
[&renderer, context_id] { renderer.TriggerEopEvent(context_id); });
[&renderer, event_id, context_id] { renderer.TriggerInterrupt(event_id, context_id); });
}
static void EopEventResetFunc(LibKernel::EventQueue::KernelEqueueEvent* event) {
static void InterruptEventResetFunc(LibKernel::EventQueue::KernelEqueueEvent* event) {
EXIT_IF(event == nullptr);
event->triggered = false;
event->event.fflags = 0;
event->event.data = 0;
}
static void EopEventDeleteFunc(LibKernel::EventQueue::KernelEqueue eq,
LibKernel::EventQueue::KernelEqueueEvent* event) {
EXIT_IF(event == nullptr);
EXIT_NOT_IMPLEMENTED(event->event.filter != LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS);
if (event->event.ident == GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT ||
event->event.ident == GRAPHICS_EVENT_EOP) {
auto* renderer = static_cast<RenderContext*>(event->filter.data);
EXIT_IF(renderer == nullptr);
renderer->DeleteEopEq(eq, static_cast<int>(event->event.ident));
}
}
static void EopEventTriggerFunc(LibKernel::EventQueue::KernelEqueueEvent* event,
void* trigger_data) {
static void InterruptEventTriggerFunc(LibKernel::EventQueue::KernelEqueueEvent* event,
void* trigger_data) {
EXIT_IF(event == nullptr);
auto triggered_event = event->event;
@@ -362,35 +353,35 @@ static void EopEventTriggerFunc(LibKernel::EventQueue::KernelEqueueEvent* event,
int AddEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id,
void* udata) {
LibKernel::EventQueue::KernelEqueueEvent event;
event.triggered = false;
event.event.ident = static_cast<uintptr_t>(id);
event.event.filter = LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS;
event.event.udata = udata;
event.event.fflags = 0;
event.event.data = id;
event.filter.delete_event_func = EopEventDeleteFunc;
event.filter.reset_func = EopEventResetFunc;
event.filter.trigger_func = EopEventTriggerFunc;
event.filter.data = &renderer;
event.triggered = false;
event.event.ident = static_cast<uintptr_t>(id);
event.event.filter = LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS;
event.event.udata = udata;
event.event.fflags = 0;
event.event.data = id;
event.filter.reset_func = InterruptEventResetFunc;
event.filter.trigger_func = InterruptEventTriggerFunc;
int result = LibKernel::EventQueue::KernelAddEvent(eq, event);
if (result == 0 &&
(id == GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT || id == GRAPHICS_EVENT_EOP)) {
renderer.AddEopEq(eq, id);
if (result == 0) {
renderer.AddInterruptEq(eq, id);
}
return result;
}
int DeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id) {
int DeleteEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id) {
int result = LibKernel::EventQueue::KernelDeleteEvent(
eq, static_cast<uintptr_t>(id), LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS);
if (result == OK || result == LibKernel::KERNEL_ERROR_ENOENT) {
renderer.DeleteInterruptEq(eq, id);
}
return result;
}
void ReadGds(Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
void ReadGds(const Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
const auto offset = uint64_t {dw_offset} * sizeof(uint32_t);
const auto size = uint64_t {dw_size} * sizeof(uint32_t);
EXIT_IF(dst == nullptr || offset > gds.Size() || size > gds.Size() - offset ||
+9 -9
View File
@@ -17,8 +17,7 @@ namespace Sync {
uint64_t& value);
[[nodiscard]] uint64_t ReadReferenceClock();
void TriggerAgcUserInterrupt();
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, uint32_t context_id);
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, int event_id, uint32_t context_id);
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
uint32_t value);
@@ -35,16 +34,16 @@ void WriteAtEndOfPipeWithWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
void WriteAtEndOfPipeWithWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
uint64_t* dst_gpu_addr, uint64_t value);
void WriteAtEndOfPipeWithInterrupt32(uint64_t submit_id, CommandBuffer& buffer,
uint32_t* dst_gpu_addr, uint32_t value,
uint32_t* dst_gpu_addr, uint32_t value, int event_id,
uint32_t context_id = 0);
void WriteAtEndOfPipeWithInterrupt64(uint64_t submit_id, CommandBuffer& buffer,
uint64_t* dst_gpu_addr, uint64_t value,
uint64_t* dst_gpu_addr, uint64_t value, int event_id,
uint32_t context_id = 0);
void WriteAtEndOfPipeWithInterruptWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
uint32_t* dst_gpu_addr, uint32_t value,
uint32_t* dst_gpu_addr, uint32_t value, int event_id,
uint32_t context_id = 0);
void WriteAtEndOfPipeWithInterruptWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
uint64_t* dst_gpu_addr, uint64_t value,
uint64_t* dst_gpu_addr, uint64_t value, int event_id,
uint32_t context_id = 0);
[[nodiscard]] uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index,
@@ -57,12 +56,13 @@ void WriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer& buffer, uint3
void WriteAtEndOfPipeWithInterruptWriteBackFlip32(uint64_t submit_id, CommandBuffer& buffer,
uint32_t* dst_gpu_addr, uint32_t value,
int handle, int index, int flip_mode,
int64_t flip_arg, uint64_t request_id);
int64_t flip_arg, uint64_t request_id,
int event_id);
int AddEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id,
void* udata);
int DeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id);
void ReadGds(Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
int DeleteEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id);
void ReadGds(const Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
} // namespace Sync
} // namespace Libs::Graphics
@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#version 450
#extension GL_ARB_gpu_shader_int64 : enable
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout(std430, binding = 0) buffer input_buf {
uint fault_buffer[];
};
layout(std430, binding = 1) buffer output_buf {
uint64_t download_buffer[];
};
layout(std430, binding = 1) buffer output_buf32 {
uint download_buffer32[];
};
void main() {
const uint id = gl_GlobalInvocationID.x;
uint word = fault_buffer[id];
fault_buffer[id] = 0u;
const uint base_bit = id * 32u;
while (word != 0u) {
const uint store_index = atomicAdd(download_buffer32[0], 1u) + 1u;
if (store_index >= MAX_PAGE_FAULTS) {
return;
}
const uint bit = findLSB(word);
word &= word - 1;
const uint page = base_bit + bit;
download_buffer[store_index] = uint64_t(page) << CACHING_PAGEBITS;
}
}
+4 -15
View File
@@ -72,7 +72,10 @@ bool GraphicContext::CreateAllocator() {
info.device = device;
info.pVulkanFunctions = &functions;
info.vulkanApiVersion = VULKAN_TARGET_API_VERSION;
info.flags = memory_budget_ext_enabled ? VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT : 0;
info.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
if (memory_budget_ext_enabled) {
info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
}
const auto result = static_cast<vk::Result>(vmaCreateAllocator(&info, &allocator));
if (result != vk::Result::eSuccess) {
@@ -199,20 +202,6 @@ void GraphicContext::CreateBuffer(uint64_t size, VulkanBuffer& buffer) {
VulkanTrackAllocation(buffer.memory);
}
void GraphicContext::DeleteBuffer(VulkanBuffer& buffer) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(allocator == nullptr || buffer.buffer == nullptr ||
buffer.memory.allocation == nullptr);
VulkanUntrackAllocation(buffer.memory);
vmaDestroyBuffer(allocator, buffer.buffer, buffer.memory.allocation);
buffer.buffer = nullptr;
buffer.memory.memory = nullptr;
buffer.memory.allocation = nullptr;
buffer.memory.allocation_info = {};
buffer.memory.offset = 0;
}
bool GraphicContext::CreateImage(const vk::ImageCreateInfo& image_info, VulkanImage& image) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(allocator == nullptr || image.image != nullptr || image.memory.allocation != nullptr);
+5 -5
View File
@@ -83,11 +83,11 @@ constexpr FormatMapping kFormatMappings[] = {
};
constexpr auto MakeFormatLookup() {
constexpr auto kMaxFormat = Prospero::GpuEnumValue(Prospero::BufferFormat::kBc7Srgb);
constexpr auto kMaxFormat = static_cast<size_t>(Prospero::BufferFormat::kBc7Srgb);
std::array<vk::Format, kMaxFormat + 1> lookup {};
lookup.fill(vk::Format::eUndefined);
for (const auto& mapping: kFormatMappings) {
lookup[Prospero::GpuEnumValue(mapping.guest)] = mapping.host;
lookup[static_cast<size_t>(mapping.guest)] = mapping.host;
}
return lookup;
}
@@ -112,9 +112,9 @@ std::string VulkanToString(vk::QueueFlags value) {
return vk::to_string(value);
}
vk::Format VulkanFormat(uint32_t guest_format) {
return guest_format < kFormatLookup.size() ? kFormatLookup[guest_format]
: vk::Format::eUndefined;
vk::Format VulkanFormat(Prospero::BufferFormat guest_format) {
const auto index = static_cast<size_t>(guest_format);
return index < kFormatLookup.size() ? kFormatLookup[index] : vk::Format::eUndefined;
}
void RequireVulkanSuccess(vk::Result result, const char* operation) {
+24 -6
View File
@@ -10,8 +10,13 @@
#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
#include "common/emulatorConfig.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include <cstdint>
#include <fmt/format.h>
#include <string>
#include <utility>
#include <vector>
#include <vulkan/vulkan.hpp>
#include <vulkan/vulkan_hash.hpp>
@@ -20,18 +25,31 @@ namespace Libs::Graphics {
using VulkanMemoryBarrier = vk::MemoryBarrier;
template <typename Handle>
[[nodiscard]] void* VulkanHandleToPointer(Handle handle) {
return reinterpret_cast<void*>(static_cast<typename Handle::CType>(handle));
}
std::string VulkanToString(vk::Result value);
std::string VulkanToString(vk::Format value);
std::string VulkanToString(vk::ImageLayout value);
std::string VulkanToString(vk::QueueFlags value);
vk::Format VulkanFormat(uint32_t guest_format);
vk::Format VulkanFormat(Prospero::BufferFormat guest_format);
void RequireVulkanSuccess(vk::Result result, const char* operation);
template <typename Handle, typename... Args>
void SetVulkanObjectNameF(vk::Device device, Handle handle, fmt::format_string<Args...> format,
Args&&... args) {
if (!Config::GraphicsDebugDumpEnabled() || device == nullptr || handle == nullptr ||
VULKAN_HPP_DEFAULT_DISPATCHER.vkSetDebugUtilsObjectNameEXT == nullptr) {
return;
}
const auto name = fmt::format(format, std::forward<Args>(args)...);
vk::DebugUtilsObjectNameInfoEXT info {};
info.sType = vk::StructureType::eDebugUtilsObjectNameInfoEXT;
info.objectType = Handle::objectType;
info.objectHandle = static_cast<uint64_t>(
reinterpret_cast<uintptr_t>(static_cast<typename Handle::CType>(handle)));
info.pObjectName = name.c_str();
(void)device.setDebugUtilsObjectNameEXT(&info);
}
template <typename T, typename Enumerator>
[[nodiscard]] std::vector<T> EnumerateVulkan(const char* operation, Enumerator&& enumerate) {
for (;;) {
-93
View File
@@ -1,93 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_VULKANINSTANCE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_VULKANINSTANCE_H_
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <map>
#include <mutex>
#include <tuple>
#include <vk_mem_alloc.h>
namespace Libs::Graphics {
inline constexpr uint32_t VULKAN_TARGET_API_VERSION = VK_API_VERSION_1_3;
struct VulkanInstance {
vk::Instance instance = nullptr;
vk::DebugUtilsMessengerEXT debug_messenger = nullptr;
vk::PhysicalDevice physical_device = nullptr;
vk::PhysicalDeviceProperties physical_device_properties = {};
vk::PhysicalDeviceMemoryProperties physical_device_memory_properties = {};
vk::Device device = nullptr;
VmaAllocator allocator = nullptr;
bool memory_budget_ext_enabled = false;
bool rt_extensions_enabled = false;
bool subgroup_size_control_enabled = false;
bool sample_rate_shading_enabled = false;
uint32_t subgroup_size = 0;
uint32_t min_subgroup_size = 0;
uint32_t max_subgroup_size = 0;
vk::ShaderStageFlags required_subgroup_size_stages = {};
Common::Mutex queue_mutex;
uint32_t queue_family = static_cast<uint32_t>(-1);
vk::Queue queue = nullptr;
[[nodiscard]] const vk::PhysicalDeviceProperties& GetPhysicalDeviceProperties() const {
return physical_device_properties;
}
[[nodiscard]] const vk::PhysicalDeviceMemoryProperties&
GetPhysicalDeviceMemoryProperties() const {
return physical_device_memory_properties;
}
[[nodiscard]] vk::FormatProperties GetFormatProperties(vk::Format format) const {
std::scoped_lock lock(m_format_properties_mutex);
auto [it, inserted] = m_format_properties.try_emplace(format);
if (inserted) {
physical_device.getFormatProperties(format, &it->second);
}
return it->second;
}
[[nodiscard]] vk::Result GetImageFormatProperties(vk::Format format, vk::ImageType type,
vk::ImageTiling tiling,
vk::ImageUsageFlags usage,
vk::ImageCreateFlags flags,
vk::ImageFormatProperties* properties) const {
using Key = std::tuple<vk::Format, vk::ImageType, vk::ImageTiling, vk::ImageUsageFlags,
vk::ImageCreateFlags>;
std::scoped_lock lock(m_image_format_properties_mutex);
auto [it, inserted] =
m_image_format_properties.try_emplace(Key {format, type, tiling, usage, flags});
if (inserted) {
it->second.first = physical_device.getImageFormatProperties(format, type, tiling, usage,
flags, &it->second.second);
}
if (properties != nullptr) {
*properties = it->second.second;
}
return it->second.first;
}
[[nodiscard]] vk::DeviceSize StorageMinAlignment() const {
const auto alignment = physical_device_properties.limits.minStorageBufferOffsetAlignment;
return alignment != 0 ? alignment : 1;
}
private:
mutable std::mutex m_format_properties_mutex;
mutable std::map<vk::Format, vk::FormatProperties> m_format_properties;
mutable std::mutex m_image_format_properties_mutex;
mutable std::map<std::tuple<vk::Format, vk::ImageType, vk::ImageTiling, vk::ImageUsageFlags,
vk::ImageCreateFlags>,
std::pair<vk::Result, vk::ImageFormatProperties>>
m_image_format_properties;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_VULKANINSTANCE_H_
@@ -1,36 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEDIALOGOVERLAY_H_
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEDIALOGOVERLAY_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <memory>
union SDL_Event;
namespace Libs::Graphics {
struct GraphicContext;
void InitializeImeDialogInput();
void ShutdownImeDialogInput();
bool ProcessImeDialogInput(const SDL_Event& event);
class ImeDialogOverlay final {
public:
explicit ImeDialogOverlay(GraphicContext& graphics);
~ImeDialogOverlay();
KYTY_CLASS_NO_COPY(ImeDialogOverlay);
[[nodiscard]] bool PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count);
void Record(vk::CommandBuffer command, vk::ImageView target);
void ReleaseVulkan();
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEDIALOGOVERLAY_H_
@@ -1,4 +1,4 @@
#include "graphics/presentation/imeDialogOverlay.h"
#include "graphics/presentation/imeOverlay.h"
#include "SDL.h"
#include "common/assert.h"
@@ -7,6 +7,7 @@
#include "imgui.h"
#include "imgui_impl_vulkan.h"
#include "libs/controller.h"
#include "libs/ime.h"
#include "libs/imeDialog.h"
#include <algorithm>
@@ -26,7 +27,79 @@ namespace Libs::Graphics {
namespace {
namespace Ime = Libs::Dialog::ImeDialog;
namespace CoreIme = Libs::Ime;
namespace DialogIme = Libs::Dialog::ImeDialog;
namespace Ime {
using Type = ImeCommon::Type;
using EnterLabel = ImeCommon::EnterLabel;
using Alignment = ImeCommon::Alignment;
using ExternalAction = ImeCommon::ExternalAction;
using ExternalInput = ImeCommon::ExternalInput;
using HostSnapshot = ImeCommon::HostSnapshot;
constexpr uint32_t OPTION_MULTILINE = ImeCommon::OPTION_MULTILINE;
constexpr uint32_t OPTION_NO_AUTO_CAPITALIZE = ImeCommon::OPTION_NO_AUTO_CAPITALIZE;
constexpr uint32_t OPTION_PASSWORD = ImeCommon::OPTION_PASSWORD;
constexpr uint32_t OPTION_FIXED_POSITION = ImeCommon::OPTION_FIXED_POSITION;
constexpr uint32_t OPTION_DISABLE_POSITION_ADJ = ImeCommon::OPTION_DISABLE_POSITION_ADJUST;
constexpr uint32_t OPTION_USE_OVER_2K = ImeCommon::OPTION_USE_OVER_2K;
constexpr uint32_t DISABLE_DEVICE_CONTROLLER = ImeCommon::DISABLE_DEVICE_CONTROLLER;
constexpr uint32_t DISABLE_DEVICE_EXT_KEYBOARD = ImeCommon::DISABLE_DEVICE_EXT_KEYBOARD;
constexpr uint64_t CORE_GENERATION_BIT = uint64_t {1} << 63;
uint64_t PackCoreGeneration(uint64_t generation) {
return generation | CORE_GENERATION_BIT;
}
bool IsCoreGeneration(uint64_t generation) {
return (generation & CORE_GENERATION_BIT) != 0;
}
uint64_t UnpackGeneration(uint64_t generation) {
return generation & ~CORE_GENERATION_BIT;
}
bool GetHostSnapshot(HostSnapshot* snapshot) {
if (snapshot == nullptr) {
return false;
}
if (CoreIme::GetHostSnapshot(snapshot)) {
snapshot->generation = PackCoreGeneration(snapshot->generation);
return true;
}
return DialogIme::GetHostSnapshot(snapshot);
}
bool HostInsertText(uint64_t generation, std::u16string_view text) {
return IsCoreGeneration(generation)
? CoreIme::HostInsertText(UnpackGeneration(generation), text)
: DialogIme::HostInsertText(generation, text);
}
bool HostBackspace(uint64_t generation) {
return IsCoreGeneration(generation) ? CoreIme::HostBackspace(UnpackGeneration(generation))
: DialogIme::HostBackspace(generation);
}
bool HostAccept(uint64_t generation) {
return IsCoreGeneration(generation) ? CoreIme::HostAccept(UnpackGeneration(generation))
: DialogIme::HostAccept(generation);
}
bool HostCancel(uint64_t generation) {
return IsCoreGeneration(generation) ? CoreIme::HostCancel(UnpackGeneration(generation))
: DialogIme::HostCancel(generation);
}
bool HostQueueExternalInput(uint64_t generation, ExternalInput input) {
return IsCoreGeneration(generation)
? CoreIme::HostQueueExternalInput(UnpackGeneration(generation), std::move(input))
: DialogIme::HostQueueExternalInput(generation, std::move(input));
}
} // namespace Ime
constexpr size_t INPUT_QUEUE_CAPACITY = 128;
@@ -114,28 +187,25 @@ void RetryVisibilityWakeup() {
}
}
void OnVisibilityChanged(bool visible, uint64_t generation) {
void RefreshVisibility() {
std::scoped_lock visibility_lock(g_visibility_mutex);
if (!g_input_lifecycle_active) {
return;
}
if (generation < g_generation || (generation == g_generation && visible == g_active)) {
Ime::HostSnapshot snapshot;
const bool visible = Ime::GetHostSnapshot(&snapshot);
const uint64_t generation = visible ? snapshot.generation : g_generation;
if (visible == g_active && (!visible || generation == g_generation)) {
return;
}
g_generation = generation;
bool capture_controller = false;
bool capture_keyboard = false;
bool multiline = false;
if (visible) {
Ime::HostSnapshot snapshot;
if (Ime::GetHostSnapshot(&snapshot) && snapshot.generation == generation) {
capture_controller = (snapshot.disable_device & Ime::DISABLE_DEVICE_CONTROLLER) == 0;
capture_keyboard = (snapshot.disable_device & Ime::DISABLE_DEVICE_EXT_KEYBOARD) == 0;
multiline = (snapshot.option & Ime::OPTION_MULTILINE) != 0;
} else {
visible = false;
}
capture_controller = (snapshot.disable_device & Ime::DISABLE_DEVICE_CONTROLLER) == 0;
capture_keyboard = (snapshot.disable_device & Ime::DISABLE_DEVICE_EXT_KEYBOARD) == 0;
multiline = (snapshot.option & Ime::OPTION_MULTILINE) != 0;
}
const bool was_controller = std::exchange(g_controller_captured, capture_controller);
if (capture_controller || was_controller) {
@@ -156,6 +226,14 @@ void OnVisibilityChanged(bool visible, uint64_t generation) {
}
}
void OnCoreVisibilityChanged(bool, uint64_t) {
RefreshVisibility();
}
void OnDialogVisibilityChanged(bool, uint64_t) {
RefreshVisibility();
}
uint32_t ExternalKeyStatus(SDL_Keymod modifiers, bool character_valid) {
uint32_t status = 0x00000001 | (character_valid ? 0x00000002 : 0);
if ((modifiers & KMOD_LCTRL) != 0) status |= 0x00000100;
@@ -305,7 +383,7 @@ void CheckVulkanResult(VkResult result) {
} // namespace
void InitializeImeDialogInput() {
void InitializeImeInput() {
const Uint32 type = SDL_RegisterEvents(1);
EXIT_IF(type == static_cast<Uint32>(-1));
{
@@ -313,15 +391,14 @@ void InitializeImeDialogInput() {
g_input_lifecycle_active = true;
g_visibility_event.store(type, std::memory_order_release);
}
Ime::SetVisibilityCallback(OnVisibilityChanged);
Ime::HostSnapshot snapshot;
if (Ime::GetHostSnapshot(&snapshot)) {
OnVisibilityChanged(true, snapshot.generation);
}
CoreIme::SetVisibilityCallback(OnCoreVisibilityChanged);
DialogIme::SetVisibilityCallback(OnDialogVisibilityChanged);
RefreshVisibility();
}
void ShutdownImeDialogInput() {
Ime::SetVisibilityCallback(nullptr);
void ShutdownImeInput() {
CoreIme::SetVisibilityCallback(nullptr);
DialogIme::SetVisibilityCallback(nullptr);
{
std::scoped_lock lock(g_visibility_mutex);
g_input_lifecycle_active = false;
@@ -346,7 +423,13 @@ void ShutdownImeDialogInput() {
}
}
bool ProcessImeDialogInput(const SDL_Event& event) {
ImeVisualState GetImeVisualState() noexcept {
const auto core = CoreIme::GetVisualState();
const auto dialog = DialogIme::GetVisualState();
return {core.active || dialog.active, core.revision + dialog.revision};
}
bool ProcessImeInput(const SDL_Event& event) {
RetryVisibilityWakeup();
if (event.type == g_visibility_event.load(std::memory_order_acquire)) {
VisibilityUpdate update {};
@@ -465,7 +548,7 @@ bool ProcessImeDialogInput(const SDL_Event& event) {
}
}
struct ImeDialogOverlay::Impl {
struct ImeOverlay::Impl {
explicit Impl(GraphicContext& context): graphics(context) {}
~Impl() {
@@ -488,7 +571,7 @@ struct ImeDialogOverlay::Impl {
io.LogFilename = nullptr;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
io.BackendPlatformName = "Kyty ImeDialog input";
io.BackendPlatformName = "Kyty IME input";
ImGui::StyleColorsDark();
auto& style = ImGui::GetStyle();
style.WindowRounding = 10.0f;
@@ -680,7 +763,7 @@ struct ImeDialogOverlay::Impl {
ImGui::SetNextWindowSize({width, height}, ImGuiCond_Always);
constexpr ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings;
ImGui::Begin("##ImeDialog", nullptr, flags);
ImGui::Begin("##Ime", nullptr, flags);
const std::u16string title = snapshot.title.empty() ? u"Enter text" : snapshot.title;
if (!snapshot.key_panel_visible) {
@@ -856,20 +939,19 @@ struct ImeDialogOverlay::Impl {
std::chrono::steady_clock::time_point last_frame;
};
ImeDialogOverlay::ImeDialogOverlay(GraphicContext& graphics)
: m_impl(std::make_unique<Impl>(graphics)) {}
ImeOverlay::ImeOverlay(GraphicContext& graphics): m_impl(std::make_unique<Impl>(graphics)) {}
ImeDialogOverlay::~ImeDialogOverlay() = default;
ImeOverlay::~ImeOverlay() = default;
bool ImeDialogOverlay::PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count) {
bool ImeOverlay::PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count) {
return m_impl->PrepareFrame(extent, format, image_count);
}
void ImeDialogOverlay::Record(vk::CommandBuffer command, vk::ImageView target) {
void ImeOverlay::Record(vk::CommandBuffer command, vk::ImageView target) {
m_impl->Record(command, target);
}
void ImeDialogOverlay::ReleaseVulkan() {
void ImeOverlay::ReleaseVulkan() {
m_impl->ReleaseVulkan();
}
+42
View File
@@ -0,0 +1,42 @@
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEOVERLAY_H_
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEOVERLAY_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <memory>
union SDL_Event;
namespace Libs::Graphics {
struct GraphicContext;
struct ImeVisualState {
bool active;
uint64_t revision;
};
void InitializeImeInput();
void ShutdownImeInput();
bool ProcessImeInput(const SDL_Event& event);
ImeVisualState GetImeVisualState() noexcept;
class ImeOverlay final {
public:
explicit ImeOverlay(GraphicContext& graphics);
~ImeOverlay();
KYTY_CLASS_NO_COPY(ImeOverlay);
[[nodiscard]] bool PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count);
void Record(vk::CommandBuffer command, vk::ImageView target);
void ReleaseVulkan();
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEOVERLAY_H_
+84 -273
View File
@@ -1,15 +1,12 @@
#include "graphics/presentation/renderDoc.h"
#include "SDL_syswm.h"
#include "SDL_version.h"
#include "SDL_video.h"
#include "common/logging/log.h"
#include <array>
#include <atomic>
#include <cstdint>
#include <cstring>
#include <chrono>
#include <filesystem>
#include <renderdoc_app.h>
#include <string>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
@@ -21,361 +18,175 @@
#undef max
#else
#include <dlfcn.h>
// RenderDoc uses Windows-style names in its cross-platform API.
#define __cdecl
using HMODULE = void*;
#endif
namespace Libs::Graphics {
using RenderDocDevicePointer = void*;
using RenderDocWindowHandle = void*;
enum RenderDocVersion {
eRENDERDOC_API_Version_1_4_2 = 10402,
};
enum RenderDocInputButton {
eRENDERDOC_Key_NonPrintable = 0x100,
eRENDERDOC_Key_Divide,
eRENDERDOC_Key_Multiply,
eRENDERDOC_Key_Subtract,
eRENDERDOC_Key_Plus,
eRENDERDOC_Key_F1,
};
using pRENDERDOC_SetCaptureKeys = void(__cdecl*)(RenderDocInputButton* keys, int num);
using pRENDERDOC_SetCaptureFilePathTemplate = void(__cdecl*)(const char* pathtemplate);
using pRENDERDOC_GetCaptureFilePathTemplate = const char*(__cdecl*)();
using pRENDERDOC_GetNumCaptures = uint32_t(__cdecl*)();
using pRENDERDOC_GetCapture = uint32_t(__cdecl*)(uint32_t idx, char* filename, uint32_t* pathlength,
uint64_t* timestamp);
using pRENDERDOC_UnloadCrashHandler = void(__cdecl*)();
using pRENDERDOC_SetActiveWindow = void(__cdecl*)(RenderDocDevicePointer device,
RenderDocWindowHandle wndHandle);
using pRENDERDOC_StartFrameCapture = void(__cdecl*)(RenderDocDevicePointer device,
RenderDocWindowHandle wndHandle);
using pRENDERDOC_IsFrameCapturing = uint32_t(__cdecl*)();
using pRENDERDOC_EndFrameCapture = uint32_t(__cdecl*)(RenderDocDevicePointer device,
RenderDocWindowHandle wndHandle);
using pRENDERDOC_GetAPI = int(__cdecl*)(RenderDocVersion version, void** out_api_pointers);
struct RenderDocApi {
void* GetAPIVersion;
void* SetCaptureOptionU32;
void* SetCaptureOptionF32;
void* GetCaptureOptionU32;
void* GetCaptureOptionF32;
void* SetFocusToggleKeys;
pRENDERDOC_SetCaptureKeys SetCaptureKeys;
void* GetOverlayBits;
void* MaskOverlayBits;
void* RemoveHooks;
pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;
pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;
pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;
pRENDERDOC_GetNumCaptures GetNumCaptures;
pRENDERDOC_GetCapture GetCapture;
void* TriggerCapture;
void* IsTargetControlConnected;
void* LaunchReplayUI;
pRENDERDOC_SetActiveWindow SetActiveWindow;
pRENDERDOC_StartFrameCapture StartFrameCapture;
pRENDERDOC_IsFrameCapturing IsFrameCapturing;
pRENDERDOC_EndFrameCapture EndFrameCapture;
void* TriggerMultiFrameCapture;
void* SetCaptureFileComments;
void* DiscardFrameCapture;
};
enum class RenderDocState : uint32_t {
Idle,
Requested,
Starting,
Capturing,
};
static RenderDocApi* g_api = nullptr;
static HMODULE g_module = nullptr;
static RenderDocDevicePointer g_device = nullptr;
static RenderDocWindowHandle g_window = nullptr;
static RENDERDOC_API_1_6_0* g_api = nullptr;
static std::atomic<RenderDocState> g_state = RenderDocState::Idle;
static std::atomic_bool g_init_done = false;
static std::atomic_uint32_t g_captured_flips = 0;
static std::atomic_bool g_unavailable_log = false;
static RenderDocDevicePointer GetRenderDocDevicePointer(vk::Instance instance) {
if (instance == nullptr) {
return nullptr;
}
return VulkanHandleToPointer(instance);
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static bool BindRenderDocApi(HMODULE module) {
auto* get_api = reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(module, "RENDERDOC_GetAPI"));
if (get_api == nullptr) {
return false;
}
void* api = nullptr;
if (get_api(eRENDERDOC_API_Version_1_4_2, &api) == 0 || api == nullptr) {
return false;
}
g_module = module;
g_api = static_cast<RenderDocApi*>(api);
g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty");
g_api->SetCaptureKeys(nullptr, 0);
g_api->UnloadCrashHandler();
char module_path[MAX_PATH] = {};
GetModuleFileNameA(module, module_path, sizeof(module_path));
LOGF("RenderDoc: bound API from %s\n", module_path);
return true;
}
static RenderDocWindowHandle GetRenderDocWindowHandle(SDL_Window* window) {
if (window == nullptr) {
return nullptr;
}
SDL_SysWMinfo info {};
SDL_VERSION(&info.version);
if (SDL_GetWindowWMInfo(window, &info) != SDL_TRUE || info.subsystem != SDL_SYSWM_WINDOWS) {
return nullptr;
}
return info.info.win.window;
}
#else
static bool BindRenderDocApi(HMODULE module) {
static bool BindRenderDocApi(void* module) {
auto* get_api = reinterpret_cast<pRENDERDOC_GetAPI>(::dlsym(module, "RENDERDOC_GetAPI"));
#endif
if (get_api == nullptr) {
return false;
}
void* api = nullptr;
if (get_api(eRENDERDOC_API_Version_1_4_2, &api) == 0 || api == nullptr) {
if (get_api(eRENDERDOC_API_Version_1_6_0, &api) != 1 || api == nullptr) {
return false;
}
g_module = module;
g_api = static_cast<RenderDocApi*>(api);
g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty");
g_api = static_cast<RENDERDOC_API_1_6_0*>(api);
g_api->SetCaptureKeys(nullptr, 0);
g_api->UnloadCrashHandler();
Dl_info info {};
if (::dladdr(reinterpret_cast<void*>(get_api), &info) != 0 && info.dli_fname != nullptr) {
LOGF("RenderDoc: bound API from %s\n", info.dli_fname);
} else {
LOGF("RenderDoc: bound API\n");
}
LOGF("RenderDoc: API 1.6.0 bound\n");
return true;
}
static RenderDocWindowHandle GetRenderDocWindowHandle(SDL_Window* window) {
if (window == nullptr) {
return nullptr;
}
SDL_SysWMinfo info {};
SDL_VERSION(&info.version);
if (SDL_GetWindowWMInfo(window, &info) != SDL_TRUE) {
return nullptr;
}
#if defined(SDL_VIDEO_DRIVER_X11)
if (info.subsystem == SDL_SYSWM_X11) {
// RenderDoc takes the raw xlib Window id in the pointer slot, not a Display*.
return reinterpret_cast<RenderDocWindowHandle>(
static_cast<uintptr_t>(info.info.x11.window));
}
#endif
// Wayland capture works without an active-window handle.
static std::atomic_bool logged = false;
if (!logged.exchange(true)) {
LOGF("RenderDoc: no native window handle for SDL subsystem %d (Wayland?); the in-app "
"overlay is unavailable, but --rd captures still work\n",
static_cast<int>(info.subsystem));
}
return nullptr;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static bool IsAvailable() {
return g_api != nullptr && g_device != nullptr && g_window != nullptr;
}
#else
static bool IsAvailable() {
return g_api != nullptr && g_device != nullptr;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
void RenderDocInit() {
bool expected = false;
if (!g_init_done.compare_exchange_strong(expected, true)) {
if (g_api != nullptr) {
return;
}
HKEY h_reg_key;
LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Classes\\RenderDoc.RDCCapture.1\\DefaultIcon\\", 0,
KEY_READ, &h_reg_key);
if (result != ERROR_SUCCESS) {
return;
}
std::array<wchar_t, MAX_PATH> key_str {};
DWORD str_sz_out {key_str.size()};
result = RegQueryValueExW(h_reg_key, L"", 0, NULL, (LPBYTE)key_str.data(), &str_sz_out);
RegCloseKey(h_reg_key);
if (result != ERROR_SUCCESS) {
return;
}
std::filesystem::path path {key_str.cbegin(), key_str.cend()};
path = path.parent_path().append("renderdoc.dll");
const auto path_to_lib = path.generic_string();
auto* module = LoadLibraryA(path_to_lib.c_str());
auto* module = GetModuleHandleA("renderdoc.dll");
if (module == nullptr) {
return;
HKEY key = nullptr;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Classes\\RenderDoc.RDCCapture.1\\DefaultIcon\\", 0, KEY_READ,
&key) != ERROR_SUCCESS) {
return;
}
std::array<wchar_t, MAX_PATH> path_buffer {};
DWORD path_size = static_cast<DWORD>(path_buffer.size() * sizeof(wchar_t));
const auto result = RegQueryValueExW(
key, L"", nullptr, nullptr, reinterpret_cast<LPBYTE>(path_buffer.data()), &path_size);
RegCloseKey(key);
if (result != ERROR_SUCCESS) {
return;
}
auto path = std::filesystem::path(path_buffer.data()).parent_path() / "renderdoc.dll";
module = LoadLibraryW(path.c_str());
if (module == nullptr) {
return;
}
}
if (!BindRenderDocApi(module)) {
LOGF("RenderDoc: API 1.4.2 is not available; in-app capture disabled\n");
FreeLibrary(module);
return;
LOGF("RenderDoc: API 1.6.0 is unavailable\n");
}
}
#else
void RenderDocInit() {
bool expected = false;
if (!g_init_done.compare_exchange_strong(expected, true)) {
if (g_api != nullptr) {
return;
}
// Prefer an injected RenderDoc instance.
auto* module = ::dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
if (module == nullptr) {
module = ::dlopen("librenderdoc.so", RTLD_NOW);
}
if (module == nullptr) {
LOGF("RenderDoc: librenderdoc.so was not found; in-app capture disabled\n");
return;
}
if (!BindRenderDocApi(module)) {
LOGF("RenderDoc: API 1.4.2 is not available; in-app capture disabled\n");
LOGF("RenderDoc: API 1.6.0 is unavailable\n");
::dlclose(module);
return;
}
}
#endif
void RenderDocSetActiveWindow(vk::Instance instance, SDL_Window* window) {
if (g_api == nullptr) {
return;
}
g_device = GetRenderDocDevicePointer(instance);
g_window = GetRenderDocWindowHandle(window);
if (g_device == nullptr || g_window == nullptr) {
LOGF("RenderDoc: active Vulkan window was not registered\n");
return;
}
g_api->SetActiveWindow(g_device, g_window);
LOGF("RenderDoc: active Vulkan window registered\n");
}
void RenderDocRequestCapture() {
if (!IsAvailable()) {
if (g_api == nullptr) {
if (!g_unavailable_log.exchange(true)) {
LOGF("RenderDoc: capture requested, but RenderDoc is not available\n");
LOGF("RenderDoc: capture requested, but RenderDoc is unavailable\n");
}
return;
}
RenderDocState expected = RenderDocState::Idle;
if (g_state.compare_exchange_strong(expected, RenderDocState::Requested)) {
LOGF("RenderDoc: capture requested; next complete presented frame will be captured\n");
} else {
LOGF("RenderDoc: capture request ignored because a capture is already pending\n");
LOGF("RenderDoc: capture requested\n");
}
}
static void LogNewestCapture() {
const auto count = g_api->GetNumCaptures();
if (count == 0) {
return;
}
char filename[4096] = {};
uint32_t path_length = sizeof(filename);
uint64_t timestamp = 0;
if (g_api->GetCapture(count - 1, filename, &path_length, &timestamp) != 0) {
filename[sizeof(filename) - 1] = '\0';
LOGF("RenderDoc: wrote capture %s\n", filename);
}
bool RenderDocCaptureRequested() {
return g_state.load(std::memory_order_acquire) == RenderDocState::Requested;
}
void RenderDocOnPresent() {
if (!IsAvailable()) {
bool RenderDocCaptureInProgress() {
return g_state.load(std::memory_order_acquire) == RenderDocState::Capturing;
}
void RenderDocStartCapture() {
RenderDocState expected = RenderDocState::Requested;
if (g_api == nullptr || !g_state.compare_exchange_strong(expected, RenderDocState::Starting,
std::memory_order_acq_rel)) {
return;
}
switch (g_state.load()) {
case RenderDocState::Idle: return;
case RenderDocState::Requested:
if (g_api->IsFrameCapturing() != 0) {
LOGF("RenderDoc: capture request ignored because RenderDoc is already capturing\n");
g_state.store(RenderDocState::Idle);
return;
}
if (g_api->IsFrameCapturing() != 0) {
g_state.store(RenderDocState::Idle, std::memory_order_release);
LOGF("RenderDoc: capture request ignored because a capture is already active\n");
return;
}
g_api->StartFrameCapture(nullptr, nullptr);
if (g_api->IsFrameCapturing() == 0) {
LOGF("RenderDoc: StartFrameCapture returned, but RenderDoc is not capturing\n");
g_state.store(RenderDocState::Idle);
return;
}
g_state.store(RenderDocState::Capturing);
LOGF("RenderDoc: capture started\n");
return;
case RenderDocState::Capturing: break;
const auto capture_id = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
const auto capture_path = "_RenderDoc/kyty_" + std::to_string(capture_id);
g_api->SetCaptureFilePathTemplate(capture_path.c_str());
g_api->StartFrameCapture(nullptr, nullptr);
if (g_api->IsFrameCapturing() == 0) {
g_state.store(RenderDocState::Idle, std::memory_order_release);
LOGF("RenderDoc: capture failed to start\n");
return;
}
g_captured_flips.store(0, std::memory_order_release);
g_state.store(RenderDocState::Capturing, std::memory_order_release);
LOGF("RenderDoc: capture started\n");
}
void RenderDocEndCapture() {
if (g_api == nullptr || !RenderDocCaptureInProgress()) {
return;
}
const auto ok = g_api->EndFrameCapture(nullptr, nullptr);
g_state.store(RenderDocState::Idle);
g_state.store(RenderDocState::Idle, std::memory_order_release);
LOGF(ok != 0 ? "RenderDoc: capture finished\n" : "RenderDoc: capture failed\n");
}
if (ok != 0) {
const auto count = g_api->GetNumCaptures();
LOGF("RenderDoc: capture finished, count=%u\n", count);
LogNewestCapture();
} else {
LOGF("RenderDoc: capture failed\n");
void RenderDocOnGuestFlip() {
if (!RenderDocCaptureInProgress()) {
return;
}
const auto flip = g_captured_flips.fetch_add(1, std::memory_order_acq_rel) + 1;
LOGF("RenderDoc: captured guest flip %u/2\n", flip);
if (flip >= 2) {
RenderDocEndCapture();
}
}
+6 -7
View File
@@ -1,17 +1,16 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_RENDERDOC_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_RENDERDOC_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
struct SDL_Window;
namespace Libs::Graphics {
void RenderDocInit();
void RenderDocSetActiveWindow(vk::Instance instance, SDL_Window* window);
void RenderDocRequestCapture();
void RenderDocOnPresent();
void RenderDocStartCapture();
void RenderDocEndCapture();
void RenderDocOnGuestFlip();
[[nodiscard]] bool RenderDocCaptureRequested();
[[nodiscard]] bool RenderDocCaptureInProgress();
} // namespace Libs::Graphics
+16 -17
View File
@@ -16,6 +16,7 @@
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/presentation/presenter.h"
#include "graphics/presentation/renderDoc.h"
#include "kernel/pthread.h"
#include "libs/errno.h"
#include "libs/libs.h"
@@ -42,6 +43,9 @@ constexpr int VIDEO_OUT_EVENT_PRE_VBLANK_START = 2;
constexpr int VIDEO_OUT_EVENT_SET_MODE = 8;
constexpr int VIDEO_OUT_TRUE = 1;
constexpr int VIDEO_OUT_FALSE = 0;
constexpr int VIDEO_OUT_BUS_TYPE_MAIN = 0;
constexpr int VIDEO_OUT_BUS_TYPE_OVERLAY = 1;
constexpr int VIDEO_OUT_BUS_TYPE_SUB = 2;
constexpr int VIDEO_OUT_FLIP_MODE_VSYNC = 1;
constexpr int VIDEO_OUT_FLIP_MODE_VSYNC_MULTI = 4;
constexpr int VIDEO_OUT_BUFFER_INDEX_BLACK = -2;
@@ -233,7 +237,7 @@ private:
struct VideoOutDriver::Impl {
public:
static constexpr int VIDEO_OUT_NUM_MAX = 2;
static constexpr int VIDEO_OUT_NUM_MAX = 4;
Impl(uint32_t width, uint32_t height, Graphics::Presenter& presenter)
: m_renderer(presenter.Renderer()), m_presenter(presenter), m_flip_queue(presenter) {
@@ -244,7 +248,7 @@ public:
~Impl();
KYTY_CLASS_NO_COPY(Impl);
int Open();
int Open(int bus_type);
bool Close(int handle);
VideoOutConfig* Get(int handle);
VideoOutConfig* Get(int handle, uint64_t& generation);
@@ -534,8 +538,7 @@ Graphics::ImageInfo BufferAttributeGroup::ImageInfo(const VideoOutBuffer& buffer
if (!Graphics::DecodeVideoOutPixelFormat(attribute.pixel_format, pixel_format)) {
EXIT("unsupported video-out pixel format: 0x%016" PRIx64 "\n", attribute.pixel_format);
}
const auto tile_mode =
Graphics::Prospero::GpuEnumValue(Graphics::Prospero::TileMode::kRenderTarget);
const auto tile_mode = Graphics::Prospero::TileMode::kRenderTarget;
const auto pitch =
Graphics::TileGetTexturePitch(pixel_format.guest_format, attribute.width, tile_mode);
Graphics::TileSizeAlign total {};
@@ -607,19 +610,11 @@ void VideoOutDriver::Impl::Init(uint32_t width, uint32_t height) {
}
}
int VideoOutDriver::Impl::Open() {
int VideoOutDriver::Impl::Open(int bus_type) {
Common::LockGuard lock(m_mutex);
int handle = -1;
for (int i = 1; i < VIDEO_OUT_NUM_MAX; i++) {
if (!m_video_out_ctx[i].opened) {
handle = i;
break;
}
}
if (handle < 0) {
const int handle = bus_type + 1;
if (m_video_out_ctx[handle].opened) {
return -1;
}
auto& config = m_video_out_ctx[handle];
@@ -1128,6 +1123,7 @@ bool FlipQueue::Flip(uint32_t micros) {
m_mutex.Unlock();
m_presenter.Present(*r.frame);
Graphics::RenderDocOnGuestFlip();
m_mutex.Lock();
if (m_requests.empty() || m_requests.front().id != r.id ||
@@ -1172,12 +1168,15 @@ KYTY_SYSV_ABI int VideoOutOpen(int user_id, int bus_type, int index, const void*
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(user_id != 255 && user_id != 0);
EXIT_NOT_IMPLEMENTED(bus_type != 0);
if (bus_type != VIDEO_OUT_BUS_TYPE_MAIN && bus_type != VIDEO_OUT_BUS_TYPE_OVERLAY &&
bus_type != VIDEO_OUT_BUS_TYPE_SUB) {
return VIDEO_OUT_ERROR_INVALID_VALUE;
}
EXIT_NOT_IMPLEMENTED(index != 0);
LOGF("\t param = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(param));
int handle = DriverState().Open();
int handle = DriverState().Open(bus_type);
if (handle < 0) {
return VIDEO_OUT_ERROR_RESOURCE_BUSY;
+86 -36
View File
@@ -18,35 +18,57 @@ namespace Libs::Graphics {
namespace {
struct ControlInfo {
std::string_view name;
uint32_t button = 0;
Controller::Axis axis = Controller::Axis::AxisMax;
bool positive = false;
};
static constexpr std::array CONTROL_INFO = {
ControlInfo {"L3", Controller::PAD_BUTTON_L3},
ControlInfo {"R3", Controller::PAD_BUTTON_R3},
ControlInfo {"Options", Controller::PAD_BUTTON_OPTIONS},
ControlInfo {"Up", Controller::PAD_BUTTON_UP},
ControlInfo {"Right", Controller::PAD_BUTTON_RIGHT},
ControlInfo {"Down", Controller::PAD_BUTTON_DOWN},
ControlInfo {"Left", Controller::PAD_BUTTON_LEFT},
ControlInfo {"L2", Controller::PAD_BUTTON_L2},
ControlInfo {"R2", Controller::PAD_BUTTON_R2},
ControlInfo {"L1", Controller::PAD_BUTTON_L1},
ControlInfo {"R1", Controller::PAD_BUTTON_R1},
ControlInfo {"Triangle", Controller::PAD_BUTTON_TRIANGLE},
ControlInfo {"Circle", Controller::PAD_BUTTON_CIRCLE},
ControlInfo {"Cross", Controller::PAD_BUTTON_CROSS},
ControlInfo {"Square", Controller::PAD_BUTTON_SQUARE},
ControlInfo {"TouchPad", Controller::PAD_BUTTON_TOUCH_PAD},
ControlInfo {"LeftStickLeft", 0, Controller::Axis::LeftX, false},
ControlInfo {"LeftStickRight", 0, Controller::Axis::LeftX, true},
ControlInfo {"LeftStickUp", 0, Controller::Axis::LeftY, false},
ControlInfo {"LeftStickDown", 0, Controller::Axis::LeftY, true},
ControlInfo {"RightStickLeft", 0, Controller::Axis::RightX, false},
ControlInfo {"RightStickRight", 0, Controller::Axis::RightX, true},
ControlInfo {"RightStickUp", 0, Controller::Axis::RightY, false},
ControlInfo {"RightStickDown", 0, Controller::Axis::RightY, true},
};
constexpr std::size_t INVALID_CONTROL = CONTROL_INFO.size();
struct Binding {
SDL_Keycode key = SDLK_UNKNOWN;
uint8_t mouse_button = 0;
uint32_t pad_button = 0;
std::size_t control = INVALID_CONTROL;
};
static constexpr std::array PAD_BUTTONS = {
std::pair {std::string_view("L3"), Controller::PAD_BUTTON_L3},
std::pair {std::string_view("R3"), Controller::PAD_BUTTON_R3},
std::pair {std::string_view("Options"), Controller::PAD_BUTTON_OPTIONS},
std::pair {std::string_view("Up"), Controller::PAD_BUTTON_UP},
std::pair {std::string_view("Right"), Controller::PAD_BUTTON_RIGHT},
std::pair {std::string_view("Down"), Controller::PAD_BUTTON_DOWN},
std::pair {std::string_view("Left"), Controller::PAD_BUTTON_LEFT},
std::pair {std::string_view("L2"), Controller::PAD_BUTTON_L2},
std::pair {std::string_view("R2"), Controller::PAD_BUTTON_R2},
std::pair {std::string_view("L1"), Controller::PAD_BUTTON_L1},
std::pair {std::string_view("R1"), Controller::PAD_BUTTON_R1},
std::pair {std::string_view("Triangle"), Controller::PAD_BUTTON_TRIANGLE},
std::pair {std::string_view("Circle"), Controller::PAD_BUTTON_CIRCLE},
std::pair {std::string_view("Cross"), Controller::PAD_BUTTON_CROSS},
std::pair {std::string_view("Square"), Controller::PAD_BUTTON_SQUARE},
std::pair {std::string_view("TouchPad"), Controller::PAD_BUTTON_TOUCH_PAD},
};
std::size_t ControlFromName(std::string_view name) {
const auto info = std::find_if(CONTROL_INFO.begin(), CONTROL_INFO.end(),
[name](const auto& item) { return item.name == name; });
return static_cast<std::size_t>(std::distance(CONTROL_INFO.begin(), info));
}
uint32_t PadButtonFromName(std::string_view name) {
const auto button = std::find_if(PAD_BUTTONS.begin(), PAD_BUTTONS.end(),
[name](const auto& item) { return item.first == name; });
return button != PAD_BUTTONS.end() ? button->second : 0;
const ControlInfo& Info(std::size_t control) {
EXIT_IF(control >= CONTROL_INFO.size());
return CONTROL_INFO[control];
}
SDL_Keycode NormalizeKey(SDL_Keycode key) {
@@ -74,7 +96,7 @@ uint8_t MouseButtonFromName(std::string_view name) {
}
bool Conflicts(const Binding& first, const Binding& second) {
return first.pad_button == second.pad_button ||
return first.control == second.control ||
(first.key != SDLK_UNKNOWN && first.key == second.key) ||
(first.mouse_button != 0 && first.mouse_button == second.mouse_button);
}
@@ -88,7 +110,7 @@ public:
Binding binding;
if (split != std::string_view::npos) {
binding.pad_button = PadButtonFromName(entry.substr(0, split));
binding.control = ControlFromName(entry.substr(0, split));
const auto host_input = entry.substr(split + 1);
binding.mouse_button = MouseButtonFromName(host_input);
if (binding.mouse_button == 0) {
@@ -97,8 +119,9 @@ public:
}
const bool reserved =
binding.key == SDLK_ESCAPE || binding.key == SDLK_SPACE || binding.key == SDLK_F1;
if (binding.pad_button == 0 || reserved ||
binding.key == SDLK_ESCAPE || binding.key == SDLK_SPACE || binding.key == SDLK_F1 ||
binding.key == SDLK_F11;
if (binding.control == INVALID_CONTROL || reserved ||
(binding.key == SDLK_UNKNOWN && binding.mouse_button == 0)) {
EXIT("Invalid input mapping: %s\n", value.c_str());
}
@@ -107,19 +130,19 @@ public:
}
[[nodiscard]] bool Custom() const { return m_custom; }
[[nodiscard]] uint32_t FindKey(int key_code) const {
[[nodiscard]] std::size_t FindKey(int key_code) const {
key_code = NormalizeKey(static_cast<SDL_Keycode>(key_code));
const auto binding =
std::find_if(m_bindings.begin(), m_bindings.begin() + m_size,
[key_code](const auto& item) { return item.key == key_code; });
return binding != m_bindings.begin() + m_size ? binding->pad_button : 0;
return binding != m_bindings.begin() + m_size ? binding->control : INVALID_CONTROL;
}
[[nodiscard]] uint32_t FindMouseButton(uint8_t mouse_button) const {
[[nodiscard]] std::size_t FindMouseButton(uint8_t mouse_button) const {
const auto binding = std::find_if(
m_bindings.begin(), m_bindings.begin() + m_size,
[mouse_button](const auto& item) { return item.mouse_button == mouse_button; });
return binding != m_bindings.begin() + m_size ? binding->pad_button : 0;
return binding != m_bindings.begin() + m_size ? binding->control : INVALID_CONTROL;
}
private:
@@ -135,9 +158,9 @@ private:
m_bindings[m_size++] = binding;
}
std::array<Binding, PAD_BUTTONS.size()> m_bindings {};
std::size_t m_size = 0;
bool m_custom;
std::array<Binding, CONTROL_INFO.size()> m_bindings {};
std::size_t m_size = 0;
bool m_custom;
};
const InputMap& GetInputMap() {
@@ -191,6 +214,33 @@ void SetStickAxis(Controller::Axis axis, bool negative, bool positive) {
Controller::ControllerAxis(Controller::HOST_INPUT_CONTROLLER_ID, axis, value);
}
void SetControl(std::size_t control, bool down) {
if (control == INVALID_CONTROL) {
return;
}
const auto& info = Info(control);
if (info.button != 0) {
SetButton(info.button, down);
return;
}
struct AxisKeys {
bool negative = false;
bool positive = false;
};
static std::array<AxisKeys, 4> axes;
const auto axis = static_cast<std::size_t>(info.axis);
EXIT_IF(axis >= axes.size());
if (info.positive) {
axes[axis].positive = down;
} else {
axes[axis].negative = down;
}
SetStickAxis(info.axis, axes[axis].negative, axes[axis].positive);
}
void DefaultKeyboardInput(int key_code, bool down) {
static StickKeys left;
static StickKeys right;
@@ -240,7 +290,7 @@ void HostInputInit() {
void HostInputKey(int key_code, bool down) {
const auto& map = GetInputMap();
if (map.Custom()) {
SetButton(map.FindKey(key_code), down);
SetControl(map.FindKey(key_code), down);
} else {
DefaultKeyboardInput(key_code, down);
}
@@ -249,7 +299,7 @@ void HostInputKey(int key_code, bool down) {
void HostInputMouseButton(uint8_t mouse_button, bool down) {
const auto& map = GetInputMap();
if (map.Custom() && mouse_button != 0) {
SetButton(map.FindMouseButton(mouse_button), down);
SetControl(map.FindMouseButton(mouse_button), down);
}
}
+33 -55
View File
@@ -22,7 +22,6 @@
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/subsystems.h"
#include "common/systemInfo.h"
#include "common/threads.h"
#include "common/timer.h"
@@ -31,13 +30,11 @@
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/imeDialogOverlay.h"
#include "graphics/presentation/imeOverlay.h"
#include "graphics/presentation/presenter.h"
#include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/videoOut.h"
#include "graphics/presentation/window/windowInternal.h"
#include "libs/controller.h"
#include "libs/imeDialog.h"
#include "loader/systemContent.h"
#include <algorithm>
@@ -61,10 +58,10 @@
namespace Libs::Graphics {
struct Presenter::Frame {
VulkanImage image;
std::unique_ptr<CommandBuffer> present_commands;
bool busy = false;
bool reusing_last = false;
VulkanImage image;
uint64_t present_tick = 0;
bool busy = false;
bool reusing_last = false;
void Configure(GraphicContext& graphics, vk::Extent2D extent, vk::Format format);
void Transit(vk::CommandBuffer command, vk::ImageLayout layout, vk::AccessFlags2 access);
@@ -74,12 +71,11 @@ struct Presenter::Frame {
class FramePool final {
public:
explicit FramePool(WindowContext& window): m_window(window) {}
FramePool(WindowContext& window, CommandScheduler& scheduler)
: m_window(window), m_scheduler(scheduler) {}
~FramePool() {
m_scheduler.Wait(m_scheduler.CurrentTick() - 1);
for (auto& frame: m_frames) {
if (frame->present_commands != nullptr) {
frame->present_commands->WaitForFenceOnly();
}
if (frame->image.image != nullptr) {
m_window.graphic_ctx.DeleteImage(frame->image);
}
@@ -191,15 +187,10 @@ public:
}
private:
static void WaitForFrame(Presenter::Frame& frame) {
// The producer only waits here. Reset stays on the presentation thread that owns the
// allocating Vulkan command pool.
if (frame.present_commands != nullptr) {
frame.present_commands->WaitForFenceOnly();
}
}
void WaitForFrame(Presenter::Frame& frame) { m_scheduler.Wait(frame.present_tick); }
WindowContext& m_window;
CommandScheduler& m_scheduler;
Common::Mutex m_mutex;
Common::CondVar m_available;
std::vector<std::unique_ptr<Presenter::Frame>> m_frames;
@@ -333,7 +324,7 @@ public:
[[nodiscard]] Status AcquireNextImage();
[[nodiscard]] bool PrepareImeOverlay();
void RecordPresentCommands(CommandBuffer& command, VulkanImage& source, bool draw_ime_overlay);
void Submit(CommandBuffer& command);
uint64_t Submit(CommandScheduler& scheduler);
[[nodiscard]] Status Present();
[[nodiscard]] uint32_t ImageCount() const noexcept {
@@ -345,23 +336,23 @@ private:
void Destroy();
void RefreshSurfaceSize();
WindowContext& m_window;
vk::SwapchainKHR m_handle = nullptr;
vk::Format m_format = vk::Format::eUndefined;
vk::Extent2D m_extent {};
std::vector<vk::Image> m_images;
std::vector<vk::ImageView> m_image_views;
std::vector<vk::Semaphore> m_image_acquired;
std::vector<vk::Semaphore> m_render_complete;
std::unique_ptr<ImeDialogOverlay> m_ime_overlay;
uint32_t m_image_index = static_cast<uint32_t>(-1);
uint32_t m_frame_index = 0;
WindowContext& m_window;
vk::SwapchainKHR m_handle = nullptr;
vk::Format m_format = vk::Format::eUndefined;
vk::Extent2D m_extent {};
std::vector<vk::Image> m_images;
std::vector<vk::ImageView> m_image_views;
std::vector<vk::Semaphore> m_image_acquired;
std::vector<vk::Semaphore> m_render_complete;
std::unique_ptr<ImeOverlay> m_ime_overlay;
uint32_t m_image_index = static_cast<uint32_t>(-1);
uint32_t m_frame_index = 0;
};
struct Presenter::Impl {
explicit Impl(WindowContext& owner)
: renderer(*owner.render_context), window(owner), swapchain(owner),
present_scheduler(renderer, owner.graphic_ctx), frames(owner) {
present_scheduler(renderer, owner.graphic_ctx), frames(owner, present_scheduler) {
EXIT_IF(owner.render_context == nullptr);
swapchain.Create();
frames.Initialize(swapchain.ImageCount(), swapchain.Format());
@@ -618,7 +609,7 @@ Swapchain::Status Swapchain::AcquireNextImage() {
bool Swapchain::PrepareImeOverlay() {
if (m_ime_overlay == nullptr) {
m_ime_overlay = std::make_unique<ImeDialogOverlay>(m_window.graphic_ctx);
m_ime_overlay = std::make_unique<ImeOverlay>(m_window.graphic_ctx);
}
return m_ime_overlay->PrepareFrame(m_extent, m_format, ImageCount());
}
@@ -631,7 +622,6 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
}
EXIT_IF(m_image_index >= m_images.size());
auto vk_command = command.Handle();
command.Begin();
vk::ImageMemoryBarrier to_transfer {};
to_transfer.sType = vk::StructureType::eImageMemoryBarrier;
@@ -702,15 +692,14 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1,
&to_present);
}
command.End();
}
void Swapchain::Submit(CommandBuffer& command) {
uint64_t Swapchain::Submit(CommandScheduler& scheduler) {
EXIT_IF(m_frame_index >= m_image_acquired.size() || m_image_index >= m_render_complete.size());
SubmitInfo submit;
submit.AddWait(m_image_acquired[m_frame_index], 1, vk::PipelineStageFlagBits::eTransfer);
submit.AddSignal(m_render_complete[m_image_index]);
command.Execute(submit);
return scheduler.Submit(submit);
}
Swapchain::Status Swapchain::Present() {
@@ -785,15 +774,9 @@ Presenter::Frame& Presenter::PrepareBlankFrame(uint32_t width, uint32_t height,
EXIT_IF(producer->IsInvalid());
frame->Clear(*producer, clear);
} else {
if (frame->present_commands == nullptr) {
frame->present_commands = std::make_unique<CommandBuffer>(m_impl->present_scheduler);
}
auto& command = *frame->present_commands;
command.WaitForFenceAndReset();
command.Begin();
auto& command = m_impl->present_scheduler.BeginCommand();
frame->Clear(command, clear);
command.End();
command.Execute();
frame->present_tick = m_impl->present_scheduler.Submit();
}
return *frame;
}
@@ -807,7 +790,7 @@ bool Presenter::IsGuestPaused() const noexcept {
}
bool Presenter::NeedsImeRefresh() const noexcept {
const auto visual = Dialog::ImeDialog::GetVisualState();
const auto visual = GetImeVisualState();
return visual.active ||
visual.revision != m_impl->presented_ime_revision.load(std::memory_order_acquire);
}
@@ -843,7 +826,7 @@ void Presenter::Present(Frame& frame, bool reuse) {
m_impl->RecoverSwapchain(Swapchain::Status::Recreate);
}
const auto ime_visual = Dialog::ImeDialog::GetVisualState();
const auto ime_visual = GetImeVisualState();
auto& swapchain = m_impl->swapchain;
for (uint32_t attempt = 0; attempt < 2; attempt++) {
auto status = swapchain.AcquireNextImage();
@@ -851,16 +834,12 @@ void Presenter::Present(Frame& frame, bool reuse) {
m_impl->RecoverSwapchain(status);
continue;
}
if (frame.present_commands == nullptr) {
frame.present_commands = std::make_unique<CommandBuffer>(m_impl->present_scheduler);
}
{
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
frame.present_commands->WaitForFenceAndReset();
auto& command = *frame.present_commands;
const bool draw_ime_overlay = ime_visual.active && swapchain.PrepareImeOverlay();
auto& command = m_impl->present_scheduler.BeginCommand();
const bool draw_ime_overlay = ime_visual.active && swapchain.PrepareImeOverlay();
swapchain.RecordPresentCommands(command, frame.image, draw_ime_overlay);
swapchain.Submit(command);
frame.present_tick = swapchain.Submit(m_impl->present_scheduler);
}
status = swapchain.Present();
if (status != Swapchain::Status::Success) {
@@ -868,7 +847,6 @@ void Presenter::Present(Frame& frame, bool reuse) {
continue;
}
RenderDocOnPresent();
m_impl->presented_ime_revision.store(ime_visual.revision, std::memory_order_release);
window.UpdateTitle();
m_impl->frames.Release(&frame, true);
+124 -56
View File
@@ -22,7 +22,6 @@
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/subsystems.h"
#include "common/systemInfo.h"
#include "common/threads.h"
#include "common/timer.h"
@@ -31,9 +30,8 @@
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/imeDialogOverlay.h"
#include "graphics/presentation/imeOverlay.h"
#include "graphics/presentation/presenter.h"
#include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/videoOut.h"
#include "graphics/presentation/window.h"
#include "graphics/presentation/window/windowInternal.h"
@@ -201,7 +199,15 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
vk::PhysicalDeviceVulkan12Features features12 {};
features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
#if defined(__APPLE__)
features12.pNext = &depth_clip_control;
#else
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR fragment_barycentric {};
fragment_barycentric.sType =
vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
fragment_barycentric.pNext = &depth_clip_control;
features12.pNext = &fragment_barycentric;
#endif
features13.pNext = &features12;
device_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2;
@@ -233,6 +239,12 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
skip_device = true;
#endif
}
#if !defined(__APPLE__)
if (fragment_barycentric.fragmentShaderBarycentric != VK_TRUE) {
LOGF("fragmentShaderBarycentric is not supported\n");
skip_device = true;
}
#endif
if (features12.samplerMirrorClampToEdge != VK_TRUE) {
LOGF("samplerMirrorClampToEdge is not supported\n");
@@ -264,6 +276,22 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
LOGF("depthBiasClamp is not supported\n");
skip_device = true;
}
if (device_features2.features.shaderClipDistance != VK_TRUE) {
LOGF("shaderClipDistance is not supported\n");
skip_device = true;
}
if (device_features2.features.shaderCullDistance != VK_TRUE) {
LOGF("shaderCullDistance is not supported\n");
skip_device = true;
}
if (device_features2.features.largePoints != VK_TRUE) {
LOGF("largePoints is not supported\n");
skip_device = true;
}
if (features12.shaderOutputLayer != VK_TRUE) {
LOGF("shaderOutputLayer is not supported\n");
skip_device = true;
}
if (device_features2.features.fragmentStoresAndAtomics != VK_TRUE) {
LOGF("fragmentStoresAndAtomics is not supported\n");
@@ -288,10 +316,6 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
LOGF("shaderStorageImageWriteWithoutFormat is not supported\n");
skip_device = true;
}
if (device_features2.features.shaderStorageImageReadWithoutFormat != VK_TRUE) {
LOGF("shaderStorageImageReadWithoutFormat is not supported\n");
skip_device = true;
}
if (device_features2.features.shaderImageGatherExtended != VK_TRUE) {
LOGF("shaderImageGatherExtended is not supported\n");
@@ -469,14 +493,19 @@ static void VulkanInitSubgroupSizeControl(vk::PhysicalDevice physical_device,
graphics.min_subgroup_size = subgroup_size_control.minSubgroupSize;
graphics.max_subgroup_size = subgroup_size_control.maxSubgroupSize;
graphics.required_subgroup_size_stages = subgroup_size_control.requiredSubgroupSizeStages;
graphics.subgroup_size_control_enabled = features13.subgroupSizeControl == VK_TRUE &&
subgroup_size_control.minSubgroupSize <= 64 &&
subgroup_size_control.maxSubgroupSize >= 64;
graphics.compute_subgroup_size_control_enabled =
features13.subgroupSizeControl == VK_TRUE &&
(graphics.required_subgroup_size_stages & vk::ShaderStageFlagBits::eCompute) &&
subgroup_size_control.minSubgroupSize <= 64 &&
subgroup_size_control.maxSubgroupSize >= 64;
graphics.compute_wave64_supported =
graphics.subgroup_size == 64u || graphics.compute_subgroup_size_control_enabled;
LOGF("Vulkan subgroup: default=%u min=%u max=%u stages=0x%08x size_control=%s\n",
LOGF("Vulkan subgroup: default=%u min=%u max=%u stages=0x%08x size_control=%s wave64=%s\n",
graphics.subgroup_size, graphics.min_subgroup_size, graphics.max_subgroup_size,
static_cast<vk::ShaderStageFlags::MaskType>(graphics.required_subgroup_size_stages),
graphics.subgroup_size_control_enabled ? "true" : "false");
graphics.compute_subgroup_size_control_enabled ? "true" : "false",
graphics.compute_wave64_supported ? "true" : "false");
}
static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const VulkanExtensions& r,
@@ -520,10 +549,6 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
features12.pNext = &depth_clip_control;
features12.samplerMirrorClampToEdge = VK_TRUE;
vk::PhysicalDeviceSubgroupSizeControlFeatures subgroup_size_control {};
subgroup_size_control.sType = vk::StructureType::ePhysicalDeviceSubgroupSizeControlFeatures;
subgroup_size_control.pNext = &features12;
vk::PhysicalDeviceVulkan13Features supported_features13 {};
supported_features13.sType = vk::StructureType::ePhysicalDeviceVulkan13Features;
@@ -532,6 +557,14 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
supported_features12.pNext = nullptr;
supported_features13.pNext = &supported_features12;
#if !defined(__APPLE__)
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR supported_fragment_barycentric {};
supported_fragment_barycentric.sType =
vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
supported_fragment_barycentric.pNext = nullptr;
supported_features12.pNext = &supported_fragment_barycentric;
#endif
const auto robustness2_ext_enabled =
HasExtension(device_extensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
@@ -539,7 +572,11 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
supported_robustness2.sType = vk::StructureType::ePhysicalDeviceRobustness2FeaturesEXT;
supported_robustness2.pNext = nullptr;
if (robustness2_ext_enabled) {
#if defined(__APPLE__)
supported_features12.pNext = &supported_robustness2;
#else
supported_fragment_barycentric.pNext = &supported_robustness2;
#endif
}
vk::PhysicalDeviceFeatures2 supported_features2 {};
@@ -554,7 +591,19 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
supported_features13.synchronization2 != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.sampleRateShading != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.depthBiasClamp != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderClipDistance != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderCullDistance != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.largePoints != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderInt64 != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.vertexPipelineStoresAndAtomics != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features12.shaderOutputLayer != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features12.bufferDeviceAddress != VK_TRUE);
#if !defined(__APPLE__)
EXIT_NOT_IMPLEMENTED(supported_fragment_barycentric.fragmentShaderBarycentric != VK_TRUE);
#endif
features12.timelineSemaphore = VK_TRUE;
features12.shaderOutputLayer = VK_TRUE;
features12.bufferDeviceAddress = VK_TRUE;
vk::PhysicalDeviceFeatures device_features {};
device_features.fragmentStoresAndAtomics = VK_TRUE;
@@ -564,53 +613,61 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
device_features.depthBounds = VK_TRUE; // unsupported by MoltenVK
#endif
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
device_features.shaderStorageImageReadWithoutFormat = VK_TRUE;
device_features.shaderImageGatherExtended = VK_TRUE;
device_features.independentBlend = VK_TRUE;
device_features.tessellationShader = VK_TRUE;
device_features.sampleRateShading = VK_TRUE;
device_features.depthBiasClamp = VK_TRUE;
device_features.shaderClipDistance = VK_TRUE;
device_features.shaderCullDistance = VK_TRUE;
device_features.largePoints = VK_TRUE;
device_features.vertexPipelineStoresAndAtomics = VK_TRUE;
graphics.sample_rate_shading_enabled = true;
device_features.vertexPipelineStoresAndAtomics =
supported_features2.features.vertexPipelineStoresAndAtomics;
if (graphics.subgroup_size_control_enabled &&
supported_features13.subgroupSizeControl == VK_TRUE) {
subgroup_size_control.subgroupSizeControl = VK_TRUE;
}
const auto* base_feature_chain = (subgroup_size_control.subgroupSizeControl == VK_TRUE
? static_cast<const void*>(&subgroup_size_control)
: static_cast<const void*>(&features12));
device_features.shaderInt64 = VK_TRUE;
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2 {};
robustness2.sType = vk::StructureType::ePhysicalDeviceRobustness2FeaturesEXT;
robustness2.pNext = const_cast<void*>(base_feature_chain);
#if defined(__APPLE__)
robustness2.pNext = &features12;
#else
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR fragment_barycentric {};
fragment_barycentric.sType =
vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
fragment_barycentric.pNext = &features12;
fragment_barycentric.fragmentShaderBarycentric = VK_TRUE;
robustness2.pNext = &fragment_barycentric;
#endif
if (robustness2_ext_enabled) {
robustness2.robustBufferAccess2 = supported_robustness2.robustBufferAccess2;
robustness2.robustImageAccess2 = supported_robustness2.robustImageAccess2;
robustness2.nullDescriptor = supported_robustness2.nullDescriptor;
}
const bool subgroup_size_control_enabled =
graphics.compute_subgroup_size_control_enabled &&
supported_features13.subgroupSizeControl == VK_TRUE;
auto features13 = required_features13;
features13.pNext =
robustness2_ext_enabled ? &robustness2 : const_cast<void*>(base_feature_chain);
features13.robustImageAccess = supported_features13.robustImageAccess;
#if defined(__APPLE__)
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2)
: static_cast<void*>(&features12);
#else
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2)
: static_cast<void*>(&fragment_barycentric);
#endif
features13.robustImageAccess = supported_features13.robustImageAccess;
features13.subgroupSizeControl = subgroup_size_control_enabled ? VK_TRUE : VK_FALSE;
LOGF("Vulkan robustness: robustImageAccess=%s robustImageAccess2=%s\n",
features13.robustImageAccess == VK_TRUE ? "true" : "false",
robustness2_ext_enabled && robustness2.robustImageAccess2 == VK_TRUE ? "true" : "false");
vk::DeviceCreateInfo create_info {};
create_info.sType = vk::StructureType::eDeviceCreateInfo;
create_info.pNext = &features13;
create_info.flags = {};
create_info.pQueueCreateInfos = &queue_create_info;
create_info.queueCreateInfoCount = 1;
create_info.enabledLayerCount =
(r.enable_validation_layers ? static_cast<uint32_t>(r.required_layers.size()) : 0);
create_info.ppEnabledLayerNames =
(r.enable_validation_layers ? r.required_layers.data() : nullptr);
create_info.sType = vk::StructureType::eDeviceCreateInfo;
create_info.pNext = &features13;
create_info.flags = {};
create_info.pQueueCreateInfos = &queue_create_info;
create_info.queueCreateInfoCount = 1;
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
create_info.ppEnabledExtensionNames = device_extensions.data();
create_info.pEnabledFeatures = &device_features;
@@ -839,23 +896,26 @@ void WindowContext::CreateVulkan() {
app_info.engineVersion = 1;
app_info.apiVersion = VULKAN_TARGET_API_VERSION; // NOLINT
vk::ValidationFeatureEnableEXT enabled_features[] = {
if (Config::SpirvDebugPrintfEnabled() && Config::GpuAssistedValidationEnabled()) {
EXIT("--spirv-debug-printf and --gpu-assisted-validation are mutually exclusive\n");
}
vk::ValidationFeatureEnableEXT enabled_features[3] = {};
uint32_t enabled_features_count = 0;
#ifdef KYTY_ENABLE_BEST_PRACTICES
vk::ValidationFeatureEnableEXT::eBestPractices,
enabled_features[enabled_features_count++] = vk::ValidationFeatureEnableEXT::eBestPractices;
#endif
#ifdef KYTY_ENABLE_DEBUG_PRINTF
vk::ValidationFeatureEnableEXT::eDebugPrintf,
#endif
};
uint32_t enabled_features_count =
sizeof(enabled_features) / sizeof(vk::ValidationFeatureEnableEXT);
#ifdef KYTY_ENABLE_DEBUG_PRINTF
if (!Config::SpirvDebugPrintfEnabled()) {
enabled_features_count--;
if (Config::SpirvDebugPrintfEnabled()) {
enabled_features[enabled_features_count++] = vk::ValidationFeatureEnableEXT::eDebugPrintf;
}
#endif
if (Config::GpuAssistedValidationEnabled()) {
enabled_features[enabled_features_count++] = vk::ValidationFeatureEnableEXT::eGpuAssisted;
LOGF("Vulkan GPU-assisted validation is enabled; expect a large slowdown\n");
std::printf("Vulkan GPU-assisted validation is enabled; expect a large slowdown\n");
std::fflush(stdout);
}
vk::ValidationFeaturesEXT validation_features {};
validation_features.sType = vk::StructureType::eValidationFeaturesEXT;
@@ -928,6 +988,7 @@ void WindowContext::CreateVulkan() {
#else
device_extensions.push_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME);
device_extensions.push_back(VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME);
device_extensions.push_back(VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME);
#endif
#ifdef KYTY_ENABLE_DEBUG_PRINTF
@@ -945,7 +1006,15 @@ void WindowContext::CreateVulkan() {
EXIT("Could not find suitable device");
}
graphic_ctx.physical_device.getProperties(&graphic_ctx.physical_device_properties);
vk::PhysicalDevicePushDescriptorProperties push_descriptor_properties {};
vk::PhysicalDeviceProperties2 physical_device_properties {};
push_descriptor_properties.sType = vk::StructureType::ePhysicalDevicePushDescriptorProperties;
push_descriptor_properties.pNext = nullptr;
physical_device_properties.sType = vk::StructureType::ePhysicalDeviceProperties2;
physical_device_properties.pNext = &push_descriptor_properties;
graphic_ctx.physical_device.getProperties2(&physical_device_properties);
graphic_ctx.physical_device_properties = physical_device_properties.properties;
graphic_ctx.max_push_descriptors = push_descriptor_properties.maxPushDescriptors;
graphic_ctx.physical_device.getMemoryProperties(&graphic_ctx.physical_device_memory_properties);
const auto& device_properties = graphic_ctx.GetPhysicalDeviceProperties();
@@ -991,7 +1060,6 @@ void WindowContext::CreateVulkan() {
render_context = std::make_unique<RenderContext>(graphic_ctx);
LibKernel::Memory::InstallGpuResources(&render_context->GetGpuResources());
presenter = std::make_unique<Presenter>(*this);
RenderDocSetActiveWindow(graphic_ctx.instance, window);
}
void WindowContext::RefreshSurfaceCapabilities() {
@@ -1015,7 +1083,7 @@ void WindowContext::RecreateSurface() {
}
WindowContext::~WindowContext() {
ShutdownImeDialogInput();
ShutdownImeInput();
presenter.reset();
LibKernel::Memory::InstallGpuResources(nullptr);
render_context.reset();
+43 -17
View File
@@ -31,7 +31,7 @@
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/imeDialogOverlay.h"
#include "graphics/presentation/imeOverlay.h"
#include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/window/hostInput.h"
#include "graphics/presentation/window/windowInternal.h"
@@ -219,7 +219,24 @@ static void GameEventTerminate(WindowLoopState& game) {
game.need_exit = true;
}
static void ToggleDesktopFullscreen() {
if (g_window == nullptr || g_window->window == nullptr) {
return;
}
const auto flags = static_cast<uint32_t>(SDL_GetWindowFlags(g_window->window));
const bool fullscreen =
(flags & static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0u;
const auto mode =
fullscreen ? 0u : static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
if (SDL_SetWindowFullscreen(g_window->window, mode) != 0) {
LOGF("Toggle fullscreen failed: %s\n", SDL_GetError());
}
}
static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
static SDL_Keycode fullscreen_key = SDLK_UNKNOWN;
#ifdef KYTY_DBG_INPUT
LOGF("Key: time = %.04f, %s%s, %s%s, %s, scan = %d, key = %d, mod = %04" PRIx16 "\n",
key.timestamp_seconds, (key.down ? "down" : ""), (key.up ? "up" : ""),
@@ -237,13 +254,30 @@ static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
RenderDocRequestCapture();
}
break;
case SDLK_F11:
if (!key.repeat) {
ToggleDesktopFullscreen();
}
break;
case SDLK_RETURN:
case SDLK_KP_ENTER:
if (!key.repeat && (key.mod & KMOD_ALT) != 0) {
ToggleDesktopFullscreen();
fullscreen_key = key.key_code;
}
break;
default: break;
}
}
#endif
if ((key.down || key.up) && !key.repeat) {
const bool fullscreen_key_event =
fullscreen_key != SDLK_UNKNOWN && key.key_code == fullscreen_key;
if (fullscreen_key_event && key.up) {
fullscreen_key = SDLK_UNKNOWN;
}
if ((key.down || key.up) && !key.repeat && !fullscreen_key_event) {
HostInputKey(key.key_code, key.down);
}
}
@@ -477,7 +511,7 @@ void WindowContext::ProcessEvent(double time_s) {
auto& game = loop;
auto* event = &game.event;
EXIT_IF(SDL_GetEventState(SDL_DISPLAYEVENT) != SDL_ENABLE);
if (ProcessImeDialogInput(*event)) {
if (ProcessImeInput(*event)) {
return;
}
@@ -783,7 +817,7 @@ static void WindowCreate(WindowContext& context) {
EXIT("%s\n", SDL_GetError());
}
HostInputInit();
InitializeImeDialogInput();
InitializeImeInput();
LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
@@ -938,14 +972,6 @@ void WindowContext::UpdateTitle() {
static constexpr auto build_type = "Unknown";
#endif
#if defined(KYTY_OFFICIAL_BUILD)
static constexpr auto build_label = "Official build " KYTY_RELEASE_TAG;
#elif defined(KYTY_FORK_BUILD)
static constexpr auto build_label = "Fork build " KYTY_BUILD_REPOSITORY " " KYTY_GIT_HASH;
#else
static constexpr auto build_label = "Source build " KYTY_GIT_HASH;
#endif
const auto now = Common::Timer::QueryPerformanceCounter();
const auto frequency = Common::Timer::QueryPerformanceFrequency();
frame_num++;
@@ -957,11 +983,11 @@ void WindowContext::UpdateTitle() {
fps_frames = 0;
}
auto fps = fmt::format("[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", build_label,
build_type, (has_title ? title : ""), (has_title ? ", " : ""),
(has_title_id ? title_id : ""), (has_title_id ? ", " : ""),
(has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""), device_name,
processor_name, frame_num, current_fps);
auto fps = fmt::format(
"[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", KYTY_BUILD_LABEL, build_type,
(has_title ? title : ""), (has_title ? ", " : ""), (has_title_id ? title_id : ""),
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""),
device_name, processor_name, frame_num, current_fps);
#if defined(__APPLE__)
// AppKit traps on title changes off the main thread; fire-and-forget keeps present pacing.
Loaded 100 of 286 files, more files were not shown because too many files have changed in this diff. Show more