Compare commits

...
Author SHA1 Message Date
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
nmzik 1d5f3c8f66 gpu emulation: remove unnecessary Vulkan synchronization 2026-08-08 01:21:03 +02:00
nmzik ec78afb966 Revert "gpu: queue AGC suspend points"
This reverts commit 90bf3c9236.
2026-08-07 23:58:09 +02:00
nmzik 3a7656336e fix regression properly (fix pthread init abi) 2026-08-07 21:39:16 +02:00
nmzik 90bf3c9236 gpu: queue AGC suspend points 2026-08-07 21:02:31 +02:00
nmzik d4f8338937 renderer: lower masked stencil xor exactly 2026-08-07 19:13:21 +02:00
nmzik 5125cd09c7 growable command buffer pool 2026-08-07 18:26:42 +02:00
nmzik 67cfec9063 shader: remove redundant runtime descriptor provenance walks (perf) 2026-08-07 18:08:40 +02:00
nmzik 81e52df7bf precompute scissor rules (optimization) 2026-08-07 17:54:00 +02:00
nmzik 4b26d03dba fix pthread init abi 2026-08-07 17:51:59 +02:00
nmzik 216c580d13 perf: limit high-resolution sleep busy-waiting 2026-08-07 17:44:55 +02:00
nmzik 7907a50817 minor optimizations 2026-08-07 09:26:37 +02:00
nmzik 769edf470b allow partial releases 2026-08-07 05:32:17 +02:00
nmzik 68fffdfb66 fix macos build 2026-08-07 05:32:12 +02:00
nmzik 26781e6fea fix(windows): add experimental guest red-zone protection.
Port the static instruction patcher from shadPS4.
2026-08-07 05:19:14 +02:00
nmzik d5c95bc3ee kernel: coalesce fragmented Windows placeholders before reuse 2026-08-07 03:13:01 +02:00
nmzik ace549ab21 reduce complexity of buffer overlap detection 2026-08-06 23:46:47 +02:00
nmzik 0cbae39e41 remove double locking. small perf boost 2026-08-06 23:23:28 +02:00
nmzikandPouare514 e9f5da27f8 gpu: implement polygon offset
Co-authored-by: Pouare514 <julienaulycee@gmail.com>
2026-08-06 22:02:19 +02:00
nmzik 3052dac82e gpu: correct HS, PS, GS and compute resource register decoding 2026-08-06 21:21:47 +02:00
nmzik 2bb0483473 shader: add missing attributes 2026-08-06 20:32:14 +02:00
nmzik 389fd1e5be refactor gpu: consolidate texture tile layout calculations 2026-08-06 20:03:42 +02:00
nmzik a76f3ac2fe gpu_defs: add missing layouts 2026-08-06 18:18:07 +02:00
nmzik 2fc4855f29 fix renderDoc 2026-08-06 16:55:53 +02:00
nmzik 869329ee68 renderer: minor optimizations 2026-08-05 19:08:18 +02:00
nmzik b2963aa6e0 graphics: make DispatchDirect asynchronous 2026-08-05 14:49:45 +02:00
nmzik 9859b3e1d6 defer image refresh to final acquisition 2026-08-05 12:52:36 +02:00
nmzik cd19fa283f Implement IME dialog overlay 2026-08-05 05:14:16 +02:00
nmzik b6af7c73ec graphics: support Standard256B storage textures 2026-08-05 05:09:28 +02:00
nmzik ac64d202af shader: implement BUFFER_ATOMIC_FMAX 2026-08-05 02:59:28 +02:00
nmzik df44ad3fac shader: implement BUFFER_ATOMIC_FMIN, unhandled IR opcode now fails 2026-08-05 02:59:28 +02:00
nmzik 0a2f4814ac refactor(shader): centralize opcode metadata and lookup paths 2026-08-05 02:59:28 +02:00
nmzik e6b4707a5d renderer: support R8 uint render targets 2026-08-05 02:59:28 +02:00
nmzik 9a3468e3a0 shader: implement V_MIN_U32 2026-08-05 02:59:28 +02:00
nmzik 73da43a7be shader: implement V_CNDMASK_B32 2026-08-05 02:59:28 +02:00
nmzik aa88d94755 shader: implement V_SUB_NC_U32 2026-08-05 02:59:27 +02:00
nmzik 51562cd8d2 tests: use valid image descriptors in shader CFG cases 2026-08-05 02:59:27 +02:00
nmzik 822ca3c10b shader: implement S_CMP_EQ_U64 2026-08-05 02:59:27 +02:00
nmzik 2b6fd699e7 shader: implement V_CMP_EQ_I64 2026-08-05 02:59:27 +02:00
nmzikandStefanos Costa b9aa0dc3b3 fix KernelLseek lock leak
Co-authored-by: Stefanos Costa <stefanos.costa1999@gmail.com>
2026-08-05 02:25:05 +02:00
nmzikandStefanos Costa f380d69b9d fix AudioOut pacing
Co-authored-by: Stefanos Costa <stefanos.costa1999@gmail.com>
2026-08-05 02:17:25 +02:00
DeathWhispers f06d7e6084 launcher: disable Vulkan validation by default
Keep validation opt-in while validation-layer errors remain fatal.

Refs #161
2026-08-04 22:09:40 +02:00
nmzik 00a5dd32c0 Implement syncOnAddress ABIs (perf improvement) 2026-08-04 21:24:12 +02:00
nmzik a9ddd44d73 CI - enable LTO, remove OPT:NOREF 2026-08-04 19:30:49 +02:00
nmzikandDeathWhispers 2f9fcc63c3 fix (agc): implement DCB context state operations
Co-authored-by: DeathWhispers <56944463+Death-Whispers-U@users.noreply.github.com>
2026-08-04 19:14:21 +02:00
nmzik 0bbd0bdd99 fix launcher regression 2026-08-04 03:35:30 +02:00
nmzikandDistantMyth 6396dc9a85 feat(input): add host input remapping
Add compact keyboard and mouse mapping with analog-stick defaults and integrate a global remapping editor into the Qt launcher.

Based on the host input remapping work from PR #109.

Co-authored-by: DistantMyth <thetarun3@gmail.com>
2026-08-04 03:24:34 +02:00
nmzik a3a00464bc fix(launcher): focus emulator window on launch 2026-08-04 02:51:11 +02:00
nmzik 1da99a0671 Add warning for Windows-forbidden filename characters 2026-08-04 02:26:50 +02:00
nmzik 9304921132 add fullscreen mode 2026-08-04 02:26:50 +02:00
nmzik c13a1d752b renderer: remove duplicate vertex and index buffer rebind 2026-08-04 02:26:50 +02:00
nmzik 51d0e02543 perf: sync only GPU-dirty readbacks 2026-08-04 02:26:50 +02:00
Ali Almohayaandnmzik e8752aca7e feat: identify build provenance in window title (#159)
* feat: identify build provenance in window title

* fix: propagate generated version dependency

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-04 01:04:10 +02:00
nmzik 7dd50ea127 change default flexible memory size 2026-08-03 18:06:43 +02:00
nmzik d09c81d1c6 Implement ftruncate abi 2026-08-03 08:18:23 +02:00
nmzik 26f0c7b0bf perf: reduce texture registration and lookup overhead 2026-08-03 08:18:05 +02:00
nmzik 2e9e2ae566 loader: preserve guest root stack frame 2026-08-03 04:02:35 +02:00
nmzik 53feb6cb19 graphics: log image descriptor specialization mismatches 2026-08-03 04:02:35 +02:00
nmzik 05d14f5421 system: add console language conf 2026-08-03 01:37:42 +02:00
Stefanos Costa 1a164c3628 kernel: preserve guest pthread priorities
Extracted from Stefanos Costa's pthread fix in #147.
2026-08-03 01:10:38 +02:00
nmzik 86a586025f renderer: expand rectangle lists with tessellation
Remove the legacy NGG rectangle workaround and its configuration toggle.
2026-08-03 00:35:02 +02:00
nmzik 06cbd602c1 graphics: preserve indirect instance state 2026-08-03 00:35:02 +02:00
nmzik 055d0920d7 renderer: allow array storage views at nonzero mips 2026-08-03 00:35:02 +02:00
nmzik f2ee98fe31 renderer: separate tiled and linear texture capacities 2026-08-03 00:35:02 +02:00
nmzik 207cef9602 renderer: report invalid texture upload layout 2026-08-03 00:35:02 +02:00
boykopovarandnmzik c4134a95e2 Use repository root as CMake source directory (#153)
* feat(project): add root cmakelists for ide auto-detect

* cmake: use repository root as source directory

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-03 00:34:21 +02:00
nmzik fb5ecec455 renderer: ignore stale stencil state for depth-only targets 2026-08-02 14:14:32 +02:00
nmzik 650d9c91a1 libNet: add missing abi 2026-08-02 13:49:07 +02:00
nmzik fc8d2a3b83 guest_gpu: remove memory-unmap submission deadlock + remove legacy agc buffering 2026-08-02 13:26:29 +02:00
nmzik 59b8fad341 graphics: support 3D color render targets 2026-08-02 09:38:57 +02:00
nmzik b877b4be9c graphics: perf - batch GPU page watcher updates at 4 MiB granularity 2026-08-02 08:39:50 +02:00
nmzik 9da7fc5dd6 renderer: minor optimizations 2026-08-02 08:34:44 +02:00
nmzik da0d33224d renderer: eliminate extra copy for small streaming buffers 2026-08-02 08:34:44 +02:00
nmzik f831e60412 agc: new abis 2026-08-02 07:59:23 +02:00
nmzik 84236d1f87 agc: new abi 2026-08-02 07:59:22 +02:00
Stefanos Costa 302b579779 loader: zero unresolved scalar floating-point returns
Extracted from 3db2b3c5c5e1a26a861df7ebcacd9ccb8c484420 in KytyPS5/KytyPS5#147.
2026-08-02 06:35:53 +02:00
Stefanos Costa 0b6bf01b36 kernel: preserve microsecond wall-clock resolution
Extracted from 3db2b3c5c5e1a26a861df7ebcacd9ccb8c484420 in KytyPS5/KytyPS5#147.
2026-08-02 06:35:52 +02:00
Stefanos Costa 66f640527d audio: fix pacing and AudioOut2 port lifetime
Extracted from 6a60f1b17481a0e5e14242c0fb4dc22f963545e1 in KytyPS5/KytyPS5#147.
2026-08-02 06:35:52 +02:00
nmzik 4631b96178 perf(gpu): run dirty-page validation only in debug builds 2026-08-02 04:58:56 +02:00
DafenxandDafenx 43f64e4ab4 Register remaining regression tests with CTest (#24)
Register regression tests with CTest

Co-authored-by: Dafenx <196083014+Dafenxz0@users.noreply.github.com>
2026-08-02 04:54:04 +02:00
Idyllize e63f5b7d5c cmake: preserve spaces in clang-cl linker paths (#26)
Pass linker flags as individual options so CMake keeps the PDB and lld map paths intact when the build directory contains spaces.
2026-08-02 04:45:28 +02:00
nikosszzz fa7c3c01bf fix: guard Linux memory fixes to only Linux 2026-08-02 03:43:56 +02:00
nikosszzz 89651f6f59 kernel/memory: reserve only available guest address ranges on Linux
Reserve only free guest address ranges
2026-08-02 03:43:56 +02:00
nmzik 44d7f2a3e8 shader cfg: handle shared early exits
Duplicate small shared exit tails so each selection gets its own merge block. This keeps overlapping early-exit ladders on structured SPIR-V and adds a regression test.
2026-08-02 03:16:08 +02:00
nmzik 2dcb90066c shader cfg: normalize loop structure
Give loops one header and one continue path before SPIR-V generation. This handles conditional headers and multiple latches without falling back to a dispatcher.
2026-08-02 03:15:26 +02:00
nmzik 51a33cc363 shader cfg: handle loop control branches
Keep simple break, continue, and repeat branches in structured control flow. Split conflicting merge blocks and add regression tests for nested loop exits.
2026-08-02 03:14:39 +02:00
nikosszzz ed84370786 fix(libc): run thread-local destructors
Why: Thread-atexit registrations were discarded, leaving objects alive after their guest TLS storage was released.

What: Store registrations per host thread and run them in LIFO order before pthread keys and guest TLS are destroyed.

Why safe: Only callbacks registered on the exiting thread run, once, before existing teardown continues.
2026-08-02 02:48:56 +02:00
Claxten 43f30d3ab2 graphics: shader: ignore unused sampler border state
* Sampler dword 3 only matters when a clamp mode uses border color
  (values >= 4). When no border mode is active, dword 3 is unused
  but can still vary across loop iterations due to wave-lane spills.
  This makes resource tracking think the descriptor is dynamic and
  fail with "unsupported GPU selection".

* Fix by zeroing dword 3 when all clamp modes are non-border.

Signed-off-by: Claxten <claxten10@gmail.com>
2026-08-02 02:42:15 +02:00
Stepz97 0838142abd macOS: anchor the guest address space in full-emulator test targets (#143)
fix(cmake): anchor the macOS guest address space for all full-emulator tests

Every target created by add_kyty_full_emulator_test links against the
full kyty_emulator sources, so it drags in the same 620 GiB .zerofill
guest address space segments as the emulator itself. Only the emulator
target and virtual_memory_allocation_tests had the linker flags that
anchor those segments; every other full-emulator test target got the
segments without the anchoring, and the kernel killed them on exec
(posix_spawn EIO / SIGKILL) before main() ever ran.

Move the configure_macos_guest_address_space() call into
add_kyty_full_emulator_test() itself so every target it creates gets
it automatically, and drop the now-redundant explicit call on
virtual_memory_allocation_tests.
2026-08-02 02:27:32 +02:00
M. Abdullahandnmzik 0f550d1fd0 fix: keep hint-less guest mappings at the canonical PS5 base (fixes the #135 macOS regression) (#138)
* fix: keep hint-less guest mappings at the canonical PS5 base

FindGuestFreeRange searched the low system-managed range first for
mappings with no address hint, so the first hint-less direct-memory map
could land as low as 0x200000. The PS5 kernel never places hint-less
user mappings below 0x200000000 and guest code relies on that: Sony's
libc maps 4 MiB of direct memory for its internal heap, fails its
mspace setup when the returned address is that low, and the first
malloc then dereferences a null mspace (a read at 0x38, the mspace
magic check). On macOS this made Raiden III crash on the main guest
thread a couple of seconds after boot, 100 percent reproducible with
--printf-direction Silent.

Search from the canonical base first, fall back to the user range, and
keep the low system-managed range only as a last resort. The mmap path
already anchored hint-less searches at 0x200000000; this aligns the
shared search helper with it.

Adds two regression tests: the libc-shaped allocation must come back at
or above the canonical base and hold writes, and direct-memory content
must survive an unmap and remap of the same physical range.

* macos: make the fatal-report memory dumps fault-safe

IsReadableRange returned true for any nonzero address on macOS, so the
fatal report's guest memory dumps dereferenced whatever the crashed
thread had in its registers. A fault inside the reporter re-enters the
signal handler and wedges the reporting thread, which hid real guest
crashes whenever logging was enabled: the game kept running with a dead
thread and the report was never completed.

Walk the Mach regions covering the range and require read permission
before dumping, the same contract the Linux implementation provides.

* do not fallthrough HOST_SYSTEM_MANAGED_MIN

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-02 02:23:12 +02:00
Claxten c1a5927036 graphics: pm4: accept trailing PM4 type-2 packets
* A one-dword type-2 NOP is a valid packet tail. Parse it normally instead of aborting command-buffer dumps.

Signed-off-by: Claxten <claxten10@gmail.com>
2026-08-02 02:07:37 +02:00
Claxten bc436548a9 graphics: support packed 10-10-10-2 uint buffers
Signed-off-by: Claxten <claxten10@gmail.com>
2026-08-02 01:44:21 +02:00
nmzik a65d17a5d6 renderer: skip debug checks when stencil&depth is not active 2026-08-01 00:22:24 +02:00
nmzik c690aeea62 add HiS PM4 handler, accept Ngs2CustomMastering 2026-08-01 00:12:42 +02:00
nmzik f830d6b2e4 renderer: remove legacy code left from refactoring 2026-07-31 23:13:48 +02:00
nmzik d68a477276 renderer: broaden compatibility 2026-07-31 22:24:56 +02:00
nmzik 8977d4d2f0 shader: add descriptor log 2026-07-31 21:29:26 +02:00
nmzik 4b4e3bf3cf pm4: implement missing selectors 2026-07-31 19:45:47 +02:00
nmzik a0bb129f02 SaveData: stop escaping root directory 2026-07-31 18:53:54 +02:00
nmzik 846002c5eb shader: fix invalid texture descriptors and add missing format 2026-07-31 17:30:31 +02:00
nmzik d8a4c83cc7 format src and tests with clang-format 2026-07-31 11:36:12 +02:00
nmzik 68be13345a fix(shader): support multisampled depth image loads 2026-07-31 11:36:12 +02:00
nmzik 167da0abe0 shader: fix readlane/writelane for inactive host lanes 2026-07-31 11:36:12 +02:00
nmzik 48c31d61ee Implement VideoDec2 2026-07-31 11:36:12 +02:00
nmzik 212282d693 fix(shader): preserve packed UINT16 MRT exports 2026-07-31 11:36:12 +02:00
nmzik 6bca35d1f5 renderer: broaden compatibility 2026-07-31 11:36:12 +02:00
M. Abdullah e4ad5fc988 docs: add macOS build and run instructions (#137)
The README had macOS badges and an experimental-support note but no build,
run, or system-requirement information for the platform. Document the
Rosetta 2 / MoltenVK setup, the x86-64 configure invocation, the Qt
universal-build requirement, MoltenVK installation and signing, and the
SDL_VULKAN_LIBRARY variable needed at run time.
2026-07-31 05:32:29 +02:00
nmzik c0d3d261ea add TextToSpeech2 stubs 2026-07-31 04:05:50 +02:00
Nikosandnmzik 3b75a5659a shader: specialize cube image descriptors (#134)
* shader: specialize cube image descriptors

Track whether image descriptors refer to cube maps during resource specialization, and apply the coordinate offset conversion when sampling cube maps as 2D image arrays in SPIR-V emission.

* shader: fix cube array coordinate lowering

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-07-31 03:59:10 +02:00
M. Abdullah d475387171 macOS: enable guest signal dispatch on the target thread (#136)
macos: enable guest signal dispatch on the target thread

The POSIX signal-dispatch path (pthread_kill based, added with the Linux
port) was compiled out on macOS, leaving KernelRaiseException to run the
guest handler on the calling thread. IL2CPP's garbage collector raises its
stop-the-world signal at every managed thread and each handler parks its
own thread until resume, so the collector parked itself and every Unity
title froze on the first collection.

Enable the same delivery path on macOS:
- translate between the Darwin mcontext (uc_mcontext->__ss) and the guest
  ucontext in CreateSignalUcontextFromHost/ApplySignalUcontextToHost
- use SIGUSR1 as the host dispatch signal (macOS has no realtime signals)
- block the dispatch signal inside the host fault handler so a suspend
  request cannot preempt fault resolution between the protection fix and
  the retry

Windows and Linux are unchanged.
2026-07-31 03:47:04 +02:00
nmzik 2f5396c6a5 Rework guest memory tracking/virtual address space/direct and flexible memory (#135)
* Rework guest memory tracking

* add unknwon flag

* Fix macOS guest address-space reservation
2026-07-31 03:07:17 +02:00
ecb48f90bb Emulate SHA-NI and fix SSE4a EXTRQ/INSERTQ register form (#126)
* Emulate SHA-NI on illegal instruction faults

* Fix SSE4a EXTRQ/INSERTQ register form

* Fix SHA-NI memory operand emulation

* Revert "Fix SSE4a EXTRQ/INSERTQ register form"

This reverts commit ea2b54a4d0.

---------

Co-authored-by: neobugs1 <neobugs1@users.noreply.github.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-07-30 16:21:36 +02:00
nmzik 77aa28b27c update README 2026-07-30 05:16:37 +02:00
nmzik d04938c88c Embedded fetch shader: Fix overlapping buffer loads (#133)
Fix overlapping buffer loads. Fixes many games
2026-07-30 05:08:38 +02:00
nmzik 85622befb8 Fix fabricated HTTP2 success (#129)
@StefanosCosta Thanks!
2026-07-30 00:48:08 +02:00
nmzik 3965d41d36 texture_cache: fix exact-match reuse across different tile modes 2026-07-30 00:10:40 +02:00
nmzik c508c4a9c0 shader_recompiler: allow GDS append/consume offsets 2026-07-30 00:10:40 +02:00
Claxten e91dd39cb0 Drop redundant PROT_NONE tracking in reserve paths for Linux (#122)
src: platform: Linux: Drop redundant PROT_NONE tracking in reserve paths

* Some UE4 games, such as The Pathless, reserve a 512 GiB virtual address range during libc startup.
  Tracking every 4 KiB page causes a long delay and is unnecessary since the range is already PROT_NONE,
  and untracked pages are treated as NoAccess.

Signed-off-by: Claxten <claxten10@gmail.com>
2026-07-30 00:00:21 +02:00
nmzik cc76827e63 Fix vertex buffer ranges crossing memory mappings 2026-07-29 21:05:24 +02:00
nmzik 832bc84100 fix(shader): stabilize scalar provenance phis in cyclic CFGs 2026-07-29 21:05:24 +02:00
nmzik 65a0f0baa7 NpManager ABI 2026-07-29 21:05:24 +02:00
nmzik b9ae2537ef renderer: broaden compatibility 2026-07-29 18:47:26 +02:00
nmzik 0b9edaa721 graphics: broaden storage image atomic compatibility 2026-07-29 18:47:24 +02:00
nmzik 8a244677d7 fix(renderer): resolve delayed GPU page faults through buffer and texture caches 2026-07-29 18:47:19 +02:00
nmzik f6e01e5403 Optimize bulk memory invalidation (#124)
* Per page -> per range search (optimization)
2026-07-29 05:09:36 +02:00
nmzik 861729fc6c Optimize texture cache tracking (#123)
Optimize texture cache page tracking
2026-07-29 03:37:09 +02:00
nmzik 687ce025c6 KernelOpen: minor fix 2026-07-29 00:31:09 +02:00
nmzik a21d1aaa47 fix 2026-07-29 00:31:09 +02:00
nmzik ec11f31aa6 fix graphical bug (PPSA17221) 2026-07-29 00:31:08 +02:00
nmzik aeceaff028 new ABIs + one stub 2026-07-29 00:31:08 +02:00
nmzik e76f2d1af8 new ABIs 2026-07-29 00:31:08 +02:00
nmzik 64845e2294 new ABI, broaden support 2026-07-29 00:31:08 +02:00
nmzik 4b9030bd3d ABI fixes: libNet, libSaveData (rewrite the legacy Kyty implementation) 2026-07-29 00:30:24 +02:00
nmzik a6b61d7aa0 refactor(graphics): split renderer and recompiler into focused modules 2026-07-29 00:30:24 +02:00
Stefanos Costa 2b9cba457e Linux: port the emulator to a working state on Linux (#117)
Linux: port the emulator to a working state
2026-07-29 00:25:58 +02:00
nmzik 3dc793d859 Fix badges (#120)
docs: fix build badges
2026-07-28 19:40:14 +02:00
nmzik 01d78b71fe CI: release Windows and macOS archives (#119)
* ci: release Windows and macOS archives

* docs: note experimental macOS support
2026-07-28 19:33:21 +02:00
368 changed files with 66106 additions and 46387 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
-82
View File
@@ -1,82 +0,0 @@
name: Build KytyPS5 (Linux)
on:
workflow_dispatch:
push:
branches: [ main, master ]
pull_request:
jobs:
build:
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install build dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends --yes \
clang \
glslang-tools \
libgl1-mesa-dev \
libx11-dev \
libxcursor-dev \
libxext-dev \
libxfixes-dev \
libxi-dev \
libxrandr-dev \
libxss-dev \
lld \
ninja-build
- name: Install Qt 6.10.3
uses: jurplel/install-qt-action@v4
with:
version: "6.10.3"
host: linux
target: desktop
arch: linux_gcc_64
cache: true
- name: Verify toolchain
shell: bash
run: |
git --version
cmake --version
ninja --version
clang++ --version
ld.lld --version
glslangValidator --version
echo "Qt6_DIR=$Qt6_DIR"
- name: Configure
shell: bash
run: |
cmake -S src -B _Build/linux \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
- name: Build
shell: bash
run: |
cmake --build _Build/linux --target launcher --parallel
- name: Install
shell: bash
run: |
cmake --install _Build/linux --prefix _Build/linux/install
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: KytyPS5-Linux
path: _Build/linux/install/**
if-no-files-found: error
-86
View File
@@ -1,86 +0,0 @@
name: Build KytyPS5 (macOS)
on:
workflow_dispatch:
push:
branches: [ main, master ]
pull_request:
jobs:
build:
runs-on: macos-15
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Select Xcode 26
shell: bash
run: |
xcode_path="$(ls -d /Applications/Xcode_26*.app | tail -n 1)"
test -n "$xcode_path"
sudo xcode-select --switch "$xcode_path/Contents/Developer"
xcodebuild -version
- name: Install build dependencies
shell: bash
run: brew install glslang ninja
- name: Install Qt 6.10.3
uses: jurplel/install-qt-action@v4
with:
version: "6.10.3"
host: mac
target: desktop
arch: clang_64
cache: true
- name: Verify toolchain
shell: bash
run: |
git --version
cmake --version
ninja --version
clang++ --version
glslangValidator --version
echo "Host architecture: $(uname -m)"
echo "QT_ROOT_DIR=$QT_ROOT_DIR"
- name: Configure
shell: bash
run: |
cmake -S src -B _Build/macos \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$QT_ROOT_DIR"
- name: Build
shell: bash
run: |
cmake --build _Build/macos --target launcher --parallel
- name: Install
shell: bash
run: |
cmake --install _Build/macos --prefix _Build/macos/install
- name: Verify artifacts
shell: bash
run: |
file _Build/macos/install/launcher
file _Build/macos/install/kyty_emulator
lipo _Build/macos/install/launcher -verify_arch x86_64
lipo _Build/macos/install/kyty_emulator -verify_arch x86_64
codesign --verify --strict _Build/macos/install/kyty_emulator
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: KytyPS5-macOS-x86_64
path: _Build/macos/install/**
if-no-files-found: error
+357 -17
View File
@@ -1,4 +1,4 @@
name: Build KytyPS5
name: Build and Release KytyPS5
on:
workflow_dispatch:
@@ -6,8 +6,27 @@ on:
branches: [ main, master ]
pull_request:
env:
KYTY_BUILD_ORIGIN: ${{ github.repository != 'KytyPS5/KytyPS5' && 'Fork' || github.event_name == 'push' && 'Official' || 'Source' }}
KYTY_BUILD_REPOSITORY: ${{ github.repository }}
jobs:
build:
metadata:
name: Prepare build metadata
runs-on: ubuntu-latest
outputs:
release_tag: ${{ steps.release_tag.outputs.value }}
steps:
- name: Generate release tag
id: release_tag
shell: bash
run: |
echo "value=KytyPS5-$(date -u +'%Y-%m-%d')-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
windows:
name: Build KytyPS5 (Windows)
needs: metadata
runs-on: windows-2022
steps:
@@ -72,9 +91,13 @@ jobs:
- name: Configure
shell: cmd
run: |
cmake -S src -B _Build/windows ^
cmake -S . -B _Build/windows ^
-G Ninja ^
-DCMAKE_BUILD_TYPE=Release ^
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON ^
-DKYTY_BUILD_ORIGIN=%KYTY_BUILD_ORIGIN% ^
-DKYTY_BUILD_REPOSITORY=%KYTY_BUILD_REPOSITORY% ^
-DKYTY_RELEASE_TAG=${{ needs.metadata.outputs.release_tag }} ^
-DCMAKE_C_COMPILER=clang-cl ^
-DCMAKE_CXX_COMPILER=clang-cl ^
-DCMAKE_PREFIX_PATH="%Qt6_DIR%"
@@ -82,52 +105,369 @@ jobs:
- name: Build
shell: cmd
run: |
cmake --build _Build/windows --target launcher --parallel
cmake --build _Build/windows --target launcher audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests --parallel
- name: Test
shell: cmd
run: |
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|ime_dialog|virtual_memory_allocation)$"
- name: Install
shell: cmd
run: |
cmake --install _Build/windows --prefix _Build/windows/install
- name: Upload Artifacts
- name: Upload Windows artifacts
uses: actions/upload-artifact@v4
with:
name: KytyPS5
name: KytyPS5-Windows-x64
path: _Build/windows/install/**
if-no-files-found: error
macos:
name: Build KytyPS5 (macOS)
needs: metadata
runs-on: macos-15
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Select Xcode 26
shell: bash
run: |
xcode_path="$(ls -d /Applications/Xcode_26*.app | tail -n 1)"
test -n "$xcode_path"
sudo xcode-select --switch "$xcode_path/Contents/Developer"
xcodebuild -version
- name: Install build dependencies
shell: bash
run: brew install glslang ninja
- name: Install Qt 6.10.3
uses: jurplel/install-qt-action@v4
with:
version: "6.10.3"
host: mac
target: desktop
arch: clang_64
cache: true
- name: Verify toolchain
shell: bash
run: |
git --version
cmake --version
ninja --version
clang++ --version
glslangValidator --version
echo "Host architecture: $(uname -m)"
echo "QT_ROOT_DIR=$QT_ROOT_DIR"
- name: Configure
shell: bash
run: |
cmake -S . -B _Build/macos \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \
-DKYTY_BUILD_ORIGIN="$KYTY_BUILD_ORIGIN" \
-DKYTY_BUILD_REPOSITORY="$KYTY_BUILD_REPOSITORY" \
-DKYTY_RELEASE_TAG="${{ needs.metadata.outputs.release_tag }}" \
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$QT_ROOT_DIR"
- name: Build
shell: bash
run: |
cmake --build _Build/macos \
--target launcher audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests \
--parallel
- name: Test
shell: bash
run: |
ctest --test-dir _Build/macos --output-on-failure \
-R '^(audio_out2_port|ime_dialog|virtual_memory_allocation)$'
- name: Install
shell: bash
run: |
cmake --install _Build/macos --prefix _Build/macos/install
- name: Bundle MoltenVK
shell: bash
env:
MOLTENVK_VERSION: v1.4.2
MOLTENVK_SHA256: f95765a6229cb7b915990a2890ce12ebe36a730b021545d3d52ae69ce4c4024e
run: |
archive="$RUNNER_TEMP/MoltenVK-macos.tar"
package="$RUNNER_TEMP/MoltenVK"
curl --fail --location --retry 3 \
--output "$archive" \
"https://github.com/KhronosGroup/MoltenVK/releases/download/$MOLTENVK_VERSION/MoltenVK-macos.tar"
echo "$MOLTENVK_SHA256 $archive" | shasum -a 256 --check
tar -xf "$archive" -C "$RUNNER_TEMP"
install -m 755 \
"$package/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" \
_Build/macos/install/libMoltenVK.dylib
install -m 644 "$package/LICENSE" _Build/macos/install/LICENSE.MoltenVK
codesign --force --sign - --timestamp=none _Build/macos/install/libMoltenVK.dylib
- name: Verify artifacts
shell: bash
run: |
file _Build/macos/install/launcher
file _Build/macos/install/kyty_emulator
file _Build/macos/install/libMoltenVK.dylib
lipo _Build/macos/install/launcher -verify_arch x86_64
lipo _Build/macos/install/kyty_emulator -verify_arch x86_64
lipo _Build/macos/install/libMoltenVK.dylib -verify_arch x86_64
codesign --verify --strict _Build/macos/install/kyty_emulator
codesign --verify --strict _Build/macos/install/libMoltenVK.dylib
- name: Upload macOS artifacts
uses: actions/upload-artifact@v4
with:
name: KytyPS5-macOS-x86_64
path: _Build/macos/install/**
if-no-files-found: error
linux:
name: Build KytyPS5 (Linux)
needs: metadata
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install build dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends --yes \
clang \
glslang-tools \
libasound2-dev \
libdbus-1-dev \
libgl1-mesa-dev \
libpulse-dev \
libudev-dev \
libwayland-dev \
libx11-dev \
libxcursor-dev \
libxext-dev \
libxfixes-dev \
libxi-dev \
libxkbcommon-dev \
libxrandr-dev \
libxss-dev \
lld \
ninja-build \
wayland-protocols
- name: Install Qt 6.10.3
uses: jurplel/install-qt-action@v4
with:
version: "6.10.3"
host: linux
target: desktop
arch: linux_gcc_64
cache: true
- name: Verify toolchain
shell: bash
run: |
git --version
cmake --version
ninja --version
clang++ --version
ld.lld --version
glslangValidator --version
echo "Qt6_DIR=$Qt6_DIR"
- name: Configure
shell: bash
run: |
mkdir -p _Build
cmake -S . -B _Build/linux \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \
-DKYTY_BUILD_ORIGIN="$KYTY_BUILD_ORIGIN" \
-DKYTY_BUILD_REPOSITORY="$KYTY_BUILD_REPOSITORY" \
-DKYTY_RELEASE_TAG="${{ needs.metadata.outputs.release_tag }}" \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
exit "${PIPESTATUS[0]}"
- name: Verify SDL2 backends
shell: bash
run: |
status=0
for feature in SDL_ALSA SDL_PULSEAUDIO SDL_WAYLAND SDL_X11 SDL_LIBUDEV SDL_DBUS; do
if grep -qE "^-- ${feature} +\\(Wanted: ON\\): ON" _Build/configure.log; then
echo "ok ${feature}"
else
echo "FAIL ${feature} is not enabled"
status=1
fi
done
exit "$status"
- name: Build
shell: bash
run: |
cmake --build _Build/linux \
--target launcher page_manager_tests memory_tracker_tests \
audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests \
--parallel
- name: Test
shell: bash
run: |
ctest --test-dir _Build/linux --output-on-failure \
-R '^(audio_out2_port|ime_dialog|page_manager|memory_tracker|virtual_memory_allocation)$'
- name: Install
shell: bash
run: |
cmake --install _Build/linux --prefix _Build/linux/install
- name: Verify artifacts
shell: bash
run: |
file _Build/linux/install/launcher
file _Build/linux/install/kyty_emulator
file _Build/linux/install/kyty_emulator | grep -q "ELF 64-bit LSB .*x86-64"
ldd _Build/linux/install/kyty_emulator > /dev/null
readelf -d _Build/linux/install/launcher | grep -q 'RPATH.*\$ORIGIN/lib'
while IFS= read -r -d '' binary; do
while read -r dependency; do
test -e "_Build/linux/install/lib/$dependency"
done < <(
readelf -d "$binary" |
sed -n 's/.*Shared library: \[\(libQt6[^]]*\|libicu[^]]*\)\].*/\1/p'
)
done < <(
find _Build/linux/install/launcher _Build/linux/install/plugins \
-type f \( -name launcher -o -name '*.so' \) -print0
)
_Build/linux/install/kyty_emulator --help > /dev/null
- name: Upload Linux artifacts
uses: actions/upload-artifact@v4
with:
name: KytyPS5-Linux-x86_64
path: _Build/linux/install/**
if-no-files-found: error
release:
if: github.event_name == 'push'
needs: build
name: Release KytyPS5
if: >-
always() &&
github.event_name == 'push' &&
github.repository == 'KytyPS5/KytyPS5' &&
(needs.windows.result == 'success' ||
needs.macos.result == 'success' ||
needs.linux.result == 'success')
needs: [metadata, windows, macos, linux]
runs-on: ubuntu-latest
permissions:
contents: write
env:
RELEASE_NAME: ${{ needs.metadata.outputs.release_tag }}
steps:
- name: Download build
- name: Download Windows build
if: needs.windows.result == 'success'
uses: actions/download-artifact@v4
with:
name: KytyPS5
path: KytyPS5
name: KytyPS5-Windows-x64
path: artifacts/KytyPS5-Windows-x64
- name: Set release name
- name: Download macOS build
if: needs.macos.result == 'success'
uses: actions/download-artifact@v4
with:
name: KytyPS5-macOS-x86_64
path: artifacts/KytyPS5-macOS-x86_64
- name: Download Linux build
if: needs.linux.result == 'success'
uses: actions/download-artifact@v4
with:
name: KytyPS5-Linux-x86_64
path: artifacts/KytyPS5-Linux-x86_64
- name: Package Windows build
if: needs.windows.result == 'success'
shell: bash
run: |
echo "RELEASE_NAME=KytyPS5-$(date -u +'%Y-%m-%d')-${GITHUB_SHA::7}" >> "$GITHUB_ENV"
windows_dir="artifacts/KytyPS5-Windows-x64"
test -d "$windows_dir"
(
cd "$windows_dir"
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-Windows-x64.zip" .
)
- name: Package build
- name: Package macOS build
if: needs.macos.result == 'success'
shell: bash
run: zip -r "$RELEASE_NAME.zip" KytyPS5
run: |
macos_dir="artifacts/KytyPS5-macOS-x86_64"
test -d "$macos_dir"
chmod a+x \
"$macos_dir/launcher" \
"$macos_dir/kyty_emulator" \
"$macos_dir/libMoltenVK.dylib"
(
cd "$macos_dir"
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-macOS-x86_64.zip" .
)
- name: Package Linux build
if: needs.linux.result == 'success'
shell: bash
run: |
linux_dir="artifacts/KytyPS5-Linux-x86_64"
test -d "$linux_dir"
chmod a+x \
"$linux_dir/launcher" \
"$linux_dir/kyty_emulator"
tar -C "$linux_dir" -czf \
"$GITHUB_WORKSPACE/$RELEASE_NAME-Linux-x86_64.tar.gz" .
- name: Create release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
assets=()
for artifact in \
"$RELEASE_NAME-Windows-x64.zip" \
"$RELEASE_NAME-macOS-x86_64.zip" \
"$RELEASE_NAME-Linux-x86_64.tar.gz"; do
if [[ -f "$artifact" ]]; then
assets+=("$artifact")
fi
done
test "${#assets[@]}" -gt 0
if gh release view "$RELEASE_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
gh release upload "$RELEASE_NAME" "$RELEASE_NAME.zip" --clobber --repo "$GITHUB_REPOSITORY"
gh release upload "$RELEASE_NAME" "${assets[@]}" \
--clobber \
--repo "$GITHUB_REPOSITORY"
else
gh release create "$RELEASE_NAME" "$RELEASE_NAME.zip" \
gh release create "$RELEASE_NAME" "${assets[@]}" \
--generate-notes \
--repo "$GITHUB_REPOSITORY" \
--target "$GITHUB_SHA" \
+2 -1
View File
@@ -3,4 +3,5 @@
.idea/
build/
_Build/vscode-clang/
_Build/
_Build/
cmake-build-debug/
+3
View File
@@ -39,3 +39,6 @@
[submodule "3rdparty/nlohmann_json"]
path = 3rdparty/nlohmann_json
url = https://github.com/nlohmann/json.git
[submodule "3rdparty/imgui"]
path = 3rdparty/imgui
url = https://github.com/ocornut/imgui.git
+1 -1
View File
@@ -1,5 +1,5 @@
{
"cmake.sourceDirectory": "${workspaceFolder}/src",
"cmake.sourceDirectory": "${workspaceFolder}",
"cmake.buildDirectory": "${workspaceFolder}/_Build/vscode-clang",
"cmake.generator": "Ninja",
"cmake.environment": {
+45
View File
@@ -2,6 +2,32 @@ set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
add_subdirectory(cpuinfo)
# The Windows guest red-zone patcher needs a decoder/encoder and a small
# trampoline assembler. Pin the same revisions used by shadPS4's implementation.
include(FetchContent)
set(XBYAK_BUILD_TESTS OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
xbyak
GIT_REPOSITORY https://github.com/herumi/xbyak.git
GIT_TAG 44a72f369268f7d552650891b296693e91db86bb
GIT_SHALLOW FALSE
)
set(ZYDIS_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(ZYDIS_BUILD_TOOLS OFF CACHE BOOL "" FORCE)
set(ZYDIS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(ZYDIS_BUILD_DOXYGEN OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
zydis
GIT_REPOSITORY https://github.com/zyantific/zydis.git
GIT_TAG 120e0e705f8e3b507dc49377ac2879979f0d545c
GIT_SUBMODULES_RECURSE TRUE
GIT_SHALLOW FALSE
)
FetchContent_MakeAvailable(xbyak zydis)
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
add_subdirectory(SDL2)
@@ -11,6 +37,25 @@ target_include_directories(Vulkan-Headers INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/Vulkan-Headers/include"
)
add_library(imgui STATIC
imgui/imgui.cpp
imgui/imgui_draw.cpp
imgui/imgui_tables.cpp
imgui/imgui_widgets.cpp
imgui/backends/imgui_impl_vulkan.cpp
)
target_include_directories(imgui PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/imgui"
"${CMAKE_CURRENT_SOURCE_DIR}/imgui/backends"
)
target_compile_definitions(imgui PRIVATE IMGUI_IMPL_VULKAN_NO_PROTOTYPES)
target_link_libraries(imgui PUBLIC Vulkan::Headers)
if(MSVC)
target_compile_options(imgui PRIVATE /W0)
else()
target_compile_options(imgui PRIVATE -w)
endif()
set(SPIRV_SKIP_TESTS ON CACHE BOOL "" FORCE)
set(SPIRV_SKIP_EXECUTABLES ON CACHE BOOL "" FORCE)
set(SKIP_SPIRV_TOOLS_INSTALL ON CACHE BOOL "" FORCE)
Vendored Submodule
+1
Submodule 3rdparty/imgui added at f1cc2ae15e.
+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
+1 -1
+643
View File
@@ -0,0 +1,643 @@
cmake_minimum_required(VERSION 3.12...4.3)
if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum policy version for bundled dependencies")
endif()
get_property(KYTY_GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(NOT KYTY_GENERATOR_IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel)
endif()
project(Kyty)
set(KYTY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
set(KYTY_TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests")
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty")
if(CMAKE_SYSTEM_NAME MATCHES ".*Linux")
set(LINUX TRUE)
endif()
if (NOT (WIN32 OR LINUX OR APPLE))
message(FATAL_ERROR "only Windows, Linux, and macOS builds are supported")
endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
include("${KYTY_SOURCE_DIR}/utils.cmake")
include(CTest)
option(KYTY_ENABLE_CLANG_TIDY "Run clang-tidy checks during builds" OFF)
set(KYTY_BUILD_ORIGIN "Source" CACHE STRING "Build provenance shown in diagnostics")
set_property(CACHE KYTY_BUILD_ORIGIN PROPERTY STRINGS Source Official Fork)
if(NOT KYTY_BUILD_ORIGIN STREQUAL "Source" AND NOT KYTY_BUILD_ORIGIN STREQUAL "Official" AND
NOT KYTY_BUILD_ORIGIN STREQUAL "Fork")
message(FATAL_ERROR "KYTY_BUILD_ORIGIN must be Source, Official, or Fork")
endif()
set(KYTY_RELEASE_TAG "" CACHE STRING "Release tag embedded in official builds")
set(KYTY_BUILD_REPOSITORY "" CACHE STRING "GitHub repository embedded in fork builds")
set(KYTY_OFFICIAL_BUILD OFF)
set(KYTY_FORK_BUILD OFF)
if(KYTY_BUILD_ORIGIN STREQUAL "Official")
if(KYTY_RELEASE_TAG STREQUAL "")
message(FATAL_ERROR "Official builds require KYTY_RELEASE_TAG")
endif()
set(KYTY_OFFICIAL_BUILD ON)
elseif(KYTY_BUILD_ORIGIN STREQUAL "Fork")
if(KYTY_BUILD_REPOSITORY STREQUAL "")
message(FATAL_ERROR "Fork builds require KYTY_BUILD_REPOSITORY")
endif()
set(KYTY_FORK_BUILD ON)
endif()
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
include(TestBigEndian)
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
message(FATAL_ERROR "only 64-bit builds are supported")
endif()
test_big_endian(BIG_ENDIAN)
if(BIG_ENDIAN)
set(KYTY_ENDIAN KYTY_ENDIAN_BIG)
else()
set(KYTY_ENDIAN KYTY_ENDIAN_LITTLE)
endif()
if(CMAKE_BUILD_TYPE MATCHES Debug)
set(KYTY_BUILD KYTY_BUILD_DEBUG)
else()
set(KYTY_BUILD KYTY_BUILD_RELEASE)
endif()
if(LINUX OR APPLE)
# macOS rides the POSIX/Linux code paths until it gets a dedicated platform
set(KYTY_PLATFORM KYTY_PLATFORM_LINUX)
else()
set(KYTY_PLATFORM KYTY_PLATFORM_WINDOWS)
endif()
set(CLANG FALSE)
set(GCC FALSE)
set(KYTY_CLANG_CL FALSE)
get_filename_component(KYTY_CXX_COMPILER_NAME "${CMAKE_CXX_COMPILER}" NAME_WE)
string(TOLOWER "${KYTY_CXX_COMPILER_NAME}" KYTY_CXX_COMPILER_NAME)
if(CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$")
set(CLANG TRUE)
set(KYTY_COMPILER CLANG)
if(WIN32 AND KYTY_CXX_COMPILER_NAME STREQUAL "clang-cl")
set(KYTY_CLANG_CL TRUE)
set(KYTY_LINKER LLD_LINK)
elseif(APPLE)
set(KYTY_LINKER LD64) # Apple's default linker; lld flags don't apply
else()
set(KYTY_LINKER LLD)
endif()
if ((CMAKE_AR MATCHES "llvm") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0.0) AND (CMAKE_CXX_COMPILER MATCHES "winlibs"))
string(REPLACE "llvm-ar.exe" "ar.exe" CMAKE_AR ${CMAKE_AR}) # llvm-ar.exe 11.0.0 from winlibs is broken
endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(GCC TRUE)
set(KYTY_COMPILER GCC)
set(KYTY_LINKER LD)
else()
message(FATAL_ERROR "only Clang and GCC are supported; found ${CMAKE_CXX_COMPILER_ID}")
endif()
set(KYTY_PROJECT_NAME "Emulator" CACHE STRING "Project name suffix")
string(TOLOWER ${KYTY_COMPILER} KYTY_COMPILER_ID)
string(TOLOWER ${KYTY_LINKER} KYTY_LINKER_ID)
if (CLANG AND (KYTY_LINKER STREQUAL LLD))
set(KYTY_LD_OPTIONS "-fuse-ld=lld")
endif()
if (KYTY_LINKER STREQUAL LD AND NOT LINUX)
set(KYTY_LD_OPTIONS "-Wl,--image-base=0x100000000000")
endif()
project(Kyty${KYTY_PROJECT_NAME}${CMAKE_BUILD_TYPE}${KYTY_COMPILER} VERSION 0.2.2)
include_directories(
${KYTY_THIRD_PARTY_DIR}/gtest/include
${KYTY_THIRD_PARTY_DIR}/gtest
${KYTY_THIRD_PARTY_DIR}/fmt/include
${KYTY_THIRD_PARTY_DIR}/magic_enum/include/magic_enum
${PROJECT_BINARY_DIR}
${KYTY_SOURCE_DIR}
)
set(KYTY_VERSION "${PROJECT_VERSION}")
configure_file(
${KYTY_SOURCE_DIR}/cmake_config.h.in
${PROJECT_BINARY_DIR}/cmake_config.h
)
find_package(Git)
add_custom_target(KytyGitVersion
COMMAND ${CMAKE_COMMAND}
-D INPUT_FILE=${KYTY_SOURCE_DIR}/kytyGitVersion.h.in
-D OUTPUT_FILE=${PROJECT_BINARY_DIR}/kytyGitVersion.h
-D GIT_EXECUTABLE=${GIT_EXECUTABLE}
-D GIT_WORKING_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}
-P ${KYTY_SOURCE_DIR}/generate_version.cmake
BYPRODUCTS ${PROJECT_BINARY_DIR}/kytyGitVersion.h
COMMENT "Generate kytyGitVersion.h"
)
add_library(kyty_git_version INTERFACE)
add_dependencies(kyty_git_version KytyGitVersion)
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)
list(APPEND KYTY_IWYU
kyty_emulator
common
#launcher
)
list(APPEND KYTY_CLANG_TIDY
kyty_emulator
#common
launcher
)
endif()
option(KYTY_BUILD_LAUNCHER "Build Qt launcher" ON)
config_compiler_and_linker()
add_subdirectory("${KYTY_THIRD_PARTY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/3rdparty")
add_subdirectory("${KYTY_SOURCE_DIR}/common" "${CMAKE_CURRENT_BINARY_DIR}/common")
file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
"${KYTY_SOURCE_DIR}/libs/*.cpp"
"${KYTY_SOURCE_DIR}/libs/*.h"
"${KYTY_SOURCE_DIR}/graphics/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/*.h"
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/*.h"
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/command_processor/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/command_processor/*.h"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/*.h"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/*.h"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/cache/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/cache/*.h"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/image/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/image/*.h"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/pipeline/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/pipeline/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/*.cpp"
"${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/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"
"${KYTY_SOURCE_DIR}/graphics/presentation/window/*.h"
"${KYTY_SOURCE_DIR}/kernel/*.cpp"
"${KYTY_SOURCE_DIR}/kernel/*.h"
"${KYTY_SOURCE_DIR}/loader/*.cpp"
"${KYTY_SOURCE_DIR}/loader/*.h"
)
find_program(KYTY_GLSLANG_VALIDATOR glslangValidator REQUIRED)
set(gpu_tiler_shader_dir "${KYTY_SOURCE_DIR}/graphics/host_gpu/shaders")
set(gpu_tiler_generated_dir "${PROJECT_BINARY_DIR}/gpu_tiler_shaders")
set(gpu_tiler_shader_names
standard256
standard4
standard4_3d
standard64
standard64_3d
prt
prt_3d
render_target
depth
promote_d16
demote_d16
swap_bgra16
)
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
foreach(shader_name IN LISTS gpu_tiler_shader_names)
set(shader_source "${gpu_tiler_shader_dir}/gpu_tiler_${shader_name}.comp")
set(shader_spv "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}.spv")
set(shader_header "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}_spv.h")
string(TOUPPER "GPU_TILER_${shader_name}_SPV" shader_symbol)
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
"-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"
DEPENDS "${shader_source}" ${gpu_tiler_shader_includes}
VERBATIM
)
list(APPEND gpu_tiler_shader_headers "${shader_header}")
endforeach()
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
set(gpu_blit_shader_sources
"${gpu_tiler_shader_dir}/gpu_blit_fs_triangle.vert"
"${gpu_tiler_shader_dir}/gpu_blit_color_to_ms_depth.frag"
)
foreach(shader_source IN LISTS gpu_blit_shader_sources)
get_filename_component(shader_name "${shader_source}" NAME_WE)
set(shader_spv "${gpu_blit_generated_dir}/${shader_name}.spv")
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
string(TOUPPER "${shader_name}_SPV" shader_symbol)
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
"-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"
DEPENDS "${shader_source}"
VERBATIM
)
list(APPEND gpu_blit_shader_headers "${shader_header}")
endforeach()
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
list(APPEND kyty_emulator_src
"${KYTY_SOURCE_DIR}/emulator.h"
"${KYTY_SOURCE_DIR}/emulator.cpp"
)
list(REMOVE_DUPLICATES kyty_emulator_src)
if (KYTY_CLANG_CL)
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
endif()
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak)
# Linux system libraries required by the static FFmpeg archive.
if(LINUX)
find_package(Threads REQUIRED)
list(APPEND kyty_emulator_link_libraries m ${CMAKE_DL_LIBS} Threads::Threads)
# Optional FFmpeg dependencies.
find_package(ZLIB)
if(ZLIB_FOUND)
list(APPEND kyty_emulator_link_libraries ZLIB::ZLIB)
endif()
find_library(KYTY_BZ2_LIBRARY bz2)
if(KYTY_BZ2_LIBRARY)
list(APPEND kyty_emulator_link_libraries ${KYTY_BZ2_LIBRARY})
endif()
find_library(KYTY_LZMA_LIBRARY lzma)
if(KYTY_LZMA_LIBRARY)
list(APPEND kyty_emulator_link_libraries ${KYTY_LZMA_LIBRARY})
endif()
endif()
set(inc_headers
${KYTY_SOURCE_DIR}
${KYTY_THIRD_PARTY_DIR}/SDL2/include
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
${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
)
if (KYTY_CLANG_CL)
list(APPEND kyty_emulator_link_libraries winpthread)
list(APPEND inc_headers
${KYTY_THIRD_PARTY_DIR}/winpthread/include
)
endif()
list(APPEND check_headers
${KYTY_SOURCE_DIR}
)
function(add_kyty_full_emulator_test target source)
add_executable(${target} EXCLUDE_FROM_ALL ${source} ${kyty_emulator_src})
target_link_libraries(${target} ${kyty_emulator_link_libraries})
target_include_directories(${target} PRIVATE ${inc_headers})
if (WIN32)
target_link_libraries(${target} iphlpapi)
endif()
if (KYTY_CLANG_CL)
target_link_libraries(${target} onecore)
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:${target}>/libwinpthread-1.dll)
endif()
# The macOS x86_64 guest address space needs its .zerofill segments anchored
# by linker flags, or the kernel kills the binary on load (posix_spawn EIO).
configure_macos_guest_address_space(${target})
endfunction()
function(configure_macos_guest_address_space target)
if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
(NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")))
target_sources(${target} PRIVATE "${KYTY_SOURCE_DIR}/kernel/macosGuestAddressSpace.cpp")
target_compile_definitions(${target} PRIVATE KYTY_LINKED_GUEST_ADDRESS_SPACE=1)
target_link_options(${target} PRIVATE
-Wl,-ld_classic,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x40000,-segaddr,SYSTEM_MANAGED,0x40000,-segaddr,SYSTEM_RESERVED,0x7ffffc000,-segaddr,USER_AREA,0x7000000000,-image_base,0x700000000000)
endif()
endfunction()
add_kyty_full_emulator_test(shader_cfg_tests "${KYTY_TESTS_DIR}/shaderCfgTests.cpp")
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/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})
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
"${KYTY_TESTS_DIR}/BitArrayTests.cpp"
)
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})
add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/MemoryTrackerTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/memoryTracker.cpp"
)
target_link_libraries(memory_tracker_tests fmt::fmt common)
target_include_directories(memory_tracker_tests PRIVATE ${inc_headers})
add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ShaderVertexMetadataTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/shaderVertexMetadata.cpp"
)
target_include_directories(shader_vertex_metadata_tests PRIVATE ${inc_headers})
add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ShaderStageRuntimeTests.cpp"
"${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/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})
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/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})
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/AudioOut2PortTests.cpp"
"${KYTY_SOURCE_DIR}/libs/libAudio2.cpp"
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
)
target_link_libraries(audio_out2_port_tests common fmt::fmt)
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/EventQueueLifetimeTests.cpp"
"${KYTY_SOURCE_DIR}/kernel/eventQueue.cpp"
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
)
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"
)
target_link_libraries(sync_on_address_tests common)
target_include_directories(sync_on_address_tests PRIVATE ${inc_headers})
add_executable(image_page_table_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ImagePageTableTests.cpp"
)
target_link_libraries(image_page_table_tests common fmt::fmt)
target_include_directories(image_page_table_tests PRIVATE ${inc_headers})
add_kyty_full_emulator_test(shader_recompiler_compute_tests "${KYTY_TESTS_DIR}/ShaderRecompilerComputeTests.cpp")
set(gpu_test_generated_dir "${PROJECT_BINARY_DIR}/gpu_test_shaders")
set(gpu_test_ms_depth_source
"${gpu_tiler_shader_dir}/gpu_test_ms_depth.comp")
set(gpu_test_ms_depth_spv
"${gpu_test_generated_dir}/gpu_test_ms_depth.spv")
set(gpu_test_ms_depth_header
"${gpu_test_generated_dir}/gpu_test_ms_depth_spv.h")
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
-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
-P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${gpu_test_ms_depth_source}"
VERBATIM
)
target_sources(shader_recompiler_compute_tests PRIVATE
"${gpu_test_ms_depth_header}")
add_kyty_full_emulator_test(virtual_memory_allocation_tests "${KYTY_TESTS_DIR}/VirtualMemoryAllocationTests.cpp")
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
# These tests use exceptions.
if(NOT KYTY_CLANG_CL)
foreach(kyty_exception_test scalar_provenance_tests resource_tracking_tests
virtual_memory_allocation_tests)
target_compile_options(${kyty_exception_test} PRIVATE -fexceptions)
endforeach()
endif()
if(BUILD_TESTING)
add_test(NAME ime_dialog COMMAND $<TARGET_FILE:ime_dialog_tests>)
add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>)
add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_tests>)
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
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
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests> --red-zone-patcher-only)
add_test(NAME command_scheduler_timeline
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --scheduler-only)
add_test(NAME stream_buffer_ring
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
add_test(NAME gpu_command_lane
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
add_test(NAME pm4_context_state
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --context-state-only)
add_test(NAME gpu_tiler
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only)
add_test(NAME texture_cache_layered_image
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
add_test(NAME texture_cache_image_views
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
add_test(NAME texture_cache_storage_sampled
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
add_test(NAME texture_cache_depth_readback
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
add_test(NAME buffer_cache_dirty_gc
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
if(WIN32)
# These tests still depend on the Windows multisample-depth path.
add_test(NAME texture_cache_image_overlap
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
add_test(NAME texture_cache_htile_clear
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
add_test(NAME buffer_cache_ranges
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
endif()
add_custom_target(kyty_tests DEPENDS
ime_dialog_tests
shader_cfg_tests
scalar_provenance_tests
image_page_table_tests
memory_tracker_tests
page_manager_tests
bit_array_tests
shader_vertex_metadata_tests
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()
add_executable(kyty_emulator "${KYTY_SOURCE_DIR}/main.cpp" ${kyty_emulator_src})
configure_macos_guest_address_space(kyty_emulator)
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
if (WIN32)
target_link_libraries(kyty_emulator opengl32)
target_link_libraries(kyty_emulator iphlpapi)
target_link_libraries(kyty_emulator setupapi)
target_link_libraries(kyty_emulator ws2_32)
endif()
if (KYTY_CLANG_CL)
target_link_libraries(kyty_emulator onecore)
endif()
if (CLANG AND NOT KYTY_CLANG_CL)
target_link_libraries(kyty_emulator pthread)
endif()
# dlopen/dlsym/dladdr for RenderDoc.
target_link_libraries(kyty_emulator ${CMAKE_DL_LIBS})
target_include_directories(kyty_emulator PRIVATE ${inc_headers})
clang_tidy_check(kyty_emulator "" "${check_headers}" "${inc_headers}")
include_what_you_use(kyty_emulator "${inc_headers}")
set(KYTY_EMULATOR_MAP_NAME "kyty_emulator_${KYTY_COMPILER_ID}_${KYTY_LINKER_ID}.map")
set(KYTY_EMULATOR_MAP_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/${KYTY_EMULATOR_MAP_NAME}")
set(KYTY_EMULATOR_PDB_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/kyty_emulator.pdb")
if(KYTY_CLANG_CL)
target_link_options(kyty_emulator PRIVATE
"/DYNAMICBASE:NO"
"/DEBUG:FULL"
"/PDB:${KYTY_EMULATOR_PDB_LINK_PATH}"
"/lldmap:${KYTY_EMULATOR_MAP_LINK_PATH}"
)
add_custom_command(TARGET kyty_emulator POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:kyty_emulator>/libwinpthread-1.dll)
elseif(WIN32 OR LINUX)
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
endif()
if(APPLE)
# The emulator writes x86-64 trampolines/PLT stubs into memory and executes them, and
# runs guest code under Rosetta. That requires the JIT / unsigned-executable-memory
# entitlements, so re-sign the binary after every link (an unsigned/relinked binary
# reverts to the hardened defaults and aborts when it first executes written code).
add_custom_command(TARGET kyty_emulator POST_BUILD
COMMAND codesign -s - --force --options runtime
--entitlements "${KYTY_SOURCE_DIR}/macos_jit.entitlements"
$<TARGET_FILE:kyty_emulator>
COMMENT "Codesign kyty_emulator with JIT entitlements (macOS)")
endif()
install(TARGETS kyty_emulator DESTINATION .)
if(KYTY_BUILD_LAUNCHER)
add_subdirectory("${KYTY_SOURCE_DIR}/launcher" "${CMAKE_CURRENT_BINARY_DIR}/launcher")
endif()
if(KYTY_CLANG_CL)
install(FILES "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" DESTINATION .)
endif()
+145 -20
View File
@@ -1,16 +1,16 @@
# KytyPS5
[![Windows Build](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml/badge.svg)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Linux Build](https://github.com/KytyPS5/KytyPS5/actions/workflows/build-linux.yml/badge.svg)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build-linux.yml)
[![macOS Build](https://github.com/KytyPS5/KytyPS5/actions/workflows/build-macos.yml/badge.svg)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build-macos.yml)
[![Platform](https://img.shields.io/badge/platform-Windows%20x64-0078D4.svg)](#system-requirements)
[![Build KytyPS5 (Windows)](https://img.shields.io/github/actions/workflow/status/KytyPS5/KytyPS5/build.yml?branch=main&event=push&label=Build%20KytyPS5%20%28Windows%29)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Build KytyPS5 (Linux)](https://img.shields.io/github/actions/workflow/status/KytyPS5/KytyPS5/build.yml?branch=main&event=push&label=Build%20KytyPS5%20%28Linux%29)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Build KytyPS5 (macOS)](https://img.shields.io/github/actions/workflow/status/KytyPS5/KytyPS5/build.yml?branch=main&event=push&label=Build%20KytyPS5%20%28macOS%29)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Platform](https://img.shields.io/badge/platform-Windows%20x64%20%7C%20Linux%20x64%20%7C%20macOS%20x86__64-0078D4.svg)](#system-requirements)
[![Status](https://img.shields.io/badge/status-early%20development-orange.svg)](#current-status)
[![License](https://img.shields.io/badge/license-GPL--2.0-blue.svg)](LICENSE)
KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows. It is based on
a heavily modified version of [Kyty](https://github.com/InoriRus/Kyty). The project is in an early
stage of development, so compatibility is limited and behavior may change significantly between
builds.
KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows and Linux,
with experimental macOS support. It is based on a heavily modified version of
[Kyty](https://github.com/InoriRus/Kyty). The project is in an early stage of development, so
compatibility is limited and behavior may change significantly between builds.
> [!IMPORTANT]
> KytyPS5 is not affiliated with Sony Interactive Entertainment or PlayStation. The project does
@@ -24,7 +24,15 @@ KytyPS5 can boot 2D games and a selection of 3D games, including titles built wi
Development is focused on compatibility and boot reliability.
Linux support is planned, but Windows is the only supported platform at this time.
Windows is the primary platform and receives the most testing. Linux builds and runs; see
[Building on Linux](#building-on-linux).
macOS support is experimental. The emulator is built for x86-64 and runs on Apple Silicon under
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
@@ -46,23 +54,36 @@ graphical glitches, low compatibility, and poor performance.
</tr>
<tr>
<td align="center">
<strong>Minecraft Legends</strong><br>
<img src="docs/screenshots/ps5-04.png" width="300" alt="Minecraft Legends running in KytyPS5">
<strong>Neptunia ReVerse</strong><br>
<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>
<img src="docs/screenshots/ps5-05.png" width="300" alt="SILENT HILL: The Short Message running in KytyPS5">
</td>
</tr>
<tr>
<td align="center">
<strong>Hellboy</strong><br>
<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="Paleo Pines running in KytyPS5">
</td>
</tr>
</table>
<p align="center"><em>And many more...</em></p>
## Contributing
Testing games and submitting detailed bug reports are useful ways to contribute. Search existing
issues first, then use the **Game Emulation Bug Report** template and attach the complete log file.
Code contributions should be focused, build successfully on Windows, and include relevant tests
where practical. Because KytyPS5 is still evolving quickly, consider opening an issue before
Code contributions should be focused, build successfully on the platforms they touch, and include
relevant tests where practical. Windows is the primary target, so a change that alters shared code
should not regress it; changes confined to a platform's own code paths only need to build there. Because KytyPS5 is still evolving quickly, consider opening an issue before
starting a large change.
### Formatting
@@ -97,11 +118,12 @@ the Vulkan/SPIR-V validation rules.
### System requirements
- Windows 10 version 1803
- A 64-bit x86 processor
- A Vulkan 1.3-capable GPU with current drivers
- Windows 10 version 1803, a current Linux distribution, or macOS on Apple Silicon
- A 64-bit x86 processor (on macOS, an Apple Silicon processor with Rosetta 2)
- A Vulkan 1.3-capable GPU with current drivers (on macOS, Vulkan is provided by the bundled
MoltenVK)
### Build requirements
### Build requirements (Windows)
- Git
- CMake 3.12 or newer
@@ -122,7 +144,7 @@ git submodule update --init --recursive
Configure the project. Replace the Qt path with the version installed on your system:
```powershell
cmake -S src -B _Build/windows -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_PREFIX_PATH="C:/Qt/6.x.x/msvc2022_64"
cmake -S . -B _Build/windows -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_PREFIX_PATH="C:/Qt/6.x.x/msvc2022_64"
```
Build the launcher and stage a runnable installation:
@@ -135,11 +157,98 @@ cmake --install _Build/windows --prefix _Build/windows/install
The finished application and its runtime dependencies will be placed in
`_Build/windows/install`.
### Building on Linux
Install the toolchain and the libraries the bundled SDL2 needs. Without the audio, Wayland and
udev development packages SDL2 quietly configures itself without those backends, and the resulting
build has no working sound and no gamepad hotplug:
```bash
sudo apt-get install --no-install-recommends \
clang lld ninja-build cmake git glslang-tools \
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \
libxi-dev libxrandr-dev libxss-dev libxkbcommon-dev \
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
```
Qt 6 (Concurrent, Network, Widgets) is also required — either the distribution packages
(`qt6-base-dev`) or an official Qt installation.
```bash
git submodule update --init --recursive
cmake -S . -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
cmake --build _Build/linux --target launcher --parallel
cmake --install _Build/linux --prefix _Build/linux/install
```
The install step copies the Qt libraries and plugins next to the binaries, so
`_Build/linux/install` runs without a matching system Qt.
As on Windows, the MSVC compiler is not used; Clang is required. `cl.exe` is rejected at configure
time.
The CMake source root is the repository root.
### Building on macOS
macOS builds target x86-64 and run under Rosetta 2 on Apple Silicon, so the PS5's x86-64 game
code executes through the same translation layer as the emulator itself. Prebuilt archives are
attached to releases; the steps below are for building from source.
Requirements:
- An Apple Silicon Mac with Rosetta 2 installed (`softwareupdate --install-rosetta`)
- Xcode (or the Command Line Tools)
- Homebrew packages: `brew install cmake ninja glslang`
- Qt 6 (Concurrent, Network, Widgets) with x86-64 support. The official Qt installation is
universal and works; Homebrew's Qt is arm64-only and will not link
```bash
git submodule update --init --recursive
cmake -S . -B _Build/macos -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
cmake --build _Build/macos --target launcher --parallel
cmake --install _Build/macos --prefix _Build/macos/install
```
The build re-signs `kyty_emulator` with the JIT entitlements it needs to execute translated
guest code; no manual signing step is required.
Vulkan comes from MoltenVK. Download `MoltenVK-macos.tar` from the
[MoltenVK releases](https://github.com/KhronosGroup/MoltenVK/releases), then copy
`MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib` next to `kyty_emulator` and ad-hoc sign it:
```bash
codesign --force --sign - _Build/macos/install/libMoltenVK.dylib
```
Release archives already include a signed `libMoltenVK.dylib`.
### Regression tests
Build every regression executable and run the registered tests with:
```powershell
cmake --build _Build/windows --target kyty_tests
ctest --test-dir _Build/windows --output-on-failure
```
Use `_Build/linux` instead of `_Build/windows` for a Linux build.
### Visual Studio Code
A ready-made Visual Studio Code setup is included in [`.vscode`](.vscode). It configures CMake
Tools to build the project with Ninja and `clang-cl` and provides launch profiles for both
`launcher.exe` and `kyty_emulator.exe`.
`launcher.exe` and `kyty_emulator.exe`. It is Windows-only: VS Code settings cannot select a
compiler per platform, so on Linux configure from the command line as shown above.
Before using it:
@@ -161,6 +270,10 @@ To use the graphical launcher:
.\_Build\windows\install\launcher.exe
```
```bash
./_Build/linux/install/launcher
```
On first launch, add one or more game folders in the global settings. The launcher searches those
folders recursively for game directories containing `eboot.bin`. Select a detected game and run it
from the game list.
@@ -171,7 +284,19 @@ The emulator can also be started directly with a legally obtained game directory
.\_Build\windows\install\kyty_emulator.exe --game "D:\Games\ExampleGame"
```
Run `kyty_emulator.exe --help` to see the available graphics, logging, validation, profiling, and
```bash
./_Build/linux/install/kyty_emulator --game "/games/ExampleGame"
```
On macOS, point SDL at the MoltenVK library explicitly; the hardened runtime prevents it from
being picked up from the executable's directory:
```bash
cd _Build/macos/install
SDL_VULKAN_LIBRARY="$PWD/libMoltenVK.dylib" ./kyty_emulator --game "/games/ExampleGame"
```
Run `kyty_emulator --help` to see the available graphics, logging, validation, profiling, and
debugging options.
### AI Use
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

-487
View File
@@ -1,487 +0,0 @@
cmake_minimum_required(VERSION 3.12...4.3)
if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum policy version for bundled dependencies")
endif()
project(Kyty)
if(CMAKE_SYSTEM_NAME MATCHES ".*Linux")
set(LINUX TRUE)
endif()
if (NOT (WIN32 OR LINUX OR APPLE))
message(FATAL_ERROR "only Windows, Linux, and macOS builds are supported")
endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
include(utils.cmake)
include(CTest)
option(KYTY_ENABLE_CLANG_TIDY "Run clang-tidy checks during builds" OFF)
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty")
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
include(TestBigEndian)
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
message(FATAL_ERROR "only 64-bit builds are supported")
endif()
test_big_endian(BIG_ENDIAN)
if(BIG_ENDIAN)
set(KYTY_ENDIAN KYTY_ENDIAN_BIG)
else()
set(KYTY_ENDIAN KYTY_ENDIAN_LITTLE)
endif()
if(CMAKE_BUILD_TYPE MATCHES Debug)
set(KYTY_BUILD KYTY_BUILD_DEBUG)
else()
set(KYTY_BUILD KYTY_BUILD_RELEASE)
endif()
if(LINUX OR APPLE)
# macOS rides the POSIX/Linux code paths until it gets a dedicated platform
set(KYTY_PLATFORM KYTY_PLATFORM_LINUX)
else()
set(KYTY_PLATFORM KYTY_PLATFORM_WINDOWS)
endif()
set(CLANG FALSE)
set(GCC FALSE)
set(KYTY_CLANG_CL FALSE)
get_filename_component(KYTY_CXX_COMPILER_NAME "${CMAKE_CXX_COMPILER}" NAME_WE)
string(TOLOWER "${KYTY_CXX_COMPILER_NAME}" KYTY_CXX_COMPILER_NAME)
if(CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$")
set(CLANG TRUE)
set(KYTY_COMPILER CLANG)
if(WIN32 AND KYTY_CXX_COMPILER_NAME STREQUAL "clang-cl")
set(KYTY_CLANG_CL TRUE)
set(KYTY_LINKER LLD_LINK)
elseif(APPLE)
set(KYTY_LINKER LD64) # Apple's default linker; lld flags don't apply
else()
set(KYTY_LINKER LLD)
endif()
if ((CMAKE_AR MATCHES "llvm") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0.0) AND (CMAKE_CXX_COMPILER MATCHES "winlibs"))
string(REPLACE "llvm-ar.exe" "ar.exe" CMAKE_AR ${CMAKE_AR}) # llvm-ar.exe 11.0.0 from winlibs is broken
endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(GCC TRUE)
set(KYTY_COMPILER GCC)
set(KYTY_LINKER LD)
else()
message(FATAL_ERROR "only Clang and GCC are supported; found ${CMAKE_CXX_COMPILER_ID}")
endif()
set(KYTY_PROJECT_NAME "Emulator" CACHE STRING "Project name suffix")
string(TOLOWER ${KYTY_COMPILER} KYTY_COMPILER_ID)
string(TOLOWER ${KYTY_LINKER} KYTY_LINKER_ID)
if (CLANG AND (KYTY_LINKER STREQUAL LLD))
set(KYTY_LD_OPTIONS "-fuse-ld=lld")
endif()
if (KYTY_LINKER STREQUAL LD AND NOT LINUX)
set(KYTY_LD_OPTIONS "-Wl,--image-base=0x100000000000")
endif()
project(Kyty${KYTY_PROJECT_NAME}${CMAKE_BUILD_TYPE}${KYTY_COMPILER} VERSION 0.2.2)
include_directories(
${KYTY_THIRD_PARTY_DIR}/gtest/include
${KYTY_THIRD_PARTY_DIR}/gtest
${KYTY_THIRD_PARTY_DIR}/fmt/include
${KYTY_THIRD_PARTY_DIR}/magic_enum/include/magic_enum
${PROJECT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
)
set(KYTY_VERSION "${PROJECT_VERSION}")
configure_file(
${PROJECT_SOURCE_DIR}/cmake_config.h.in
${PROJECT_BINARY_DIR}/cmake_config.h
)
find_package(Git)
add_custom_target( KytyGitVersion
COMMAND ${CMAKE_COMMAND}
-D INPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/kytyGitVersion.h.in
-D OUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/kytyGitVersion.h
-D GIT_EXECUTABLE=${GIT_EXECUTABLE}
-D GIT_WORKING_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_version.cmake
COMMENT "Generate kytyGitVersion.h"
)
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)
list(APPEND KYTY_IWYU
kyty_emulator
common
#launcher
)
list(APPEND KYTY_CLANG_TIDY
kyty_emulator
#common
launcher
)
endif()
option(KYTY_BUILD_LAUNCHER "Build Qt launcher" ON)
config_compiler_and_linker()
add_subdirectory("${KYTY_THIRD_PARTY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/3rdparty")
add_subdirectory(common)
file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
libs/*.cpp
libs/*.h
graphics/*.cpp
graphics/*.h
graphics/guest_gpu/*.cpp
graphics/guest_gpu/*.h
graphics/guest_gpu/command_processor/*.cpp
graphics/guest_gpu/command_processor/*.h
graphics/host_gpu/*.cpp
graphics/host_gpu/*.h
graphics/host_gpu/renderer/*.cpp
graphics/host_gpu/renderer/*.h
graphics/shader/*.cpp
graphics/shader/*.h
graphics/shader/recompiler/*.cpp
graphics/shader/recompiler/*.h
graphics/shader/recompiler/shaderIR/*.cpp
graphics/shader/recompiler/shaderIR/*.h
graphics/shader/recompiler/spirvEmitter/*.cpp
graphics/shader/recompiler/spirvEmitter/*.h
graphics/host_gpu/objects/*.cpp
graphics/host_gpu/objects/*.h
graphics/presentation/*.cpp
graphics/presentation/*.h
graphics/presentation/window/*.cpp
graphics/presentation/window/*.h
kernel/*.cpp
kernel/*.h
loader/*.cpp
loader/*.h
)
find_program(KYTY_GLSLANG_VALIDATOR glslangValidator REQUIRED)
set(gpu_tiler_shader_dir "${CMAKE_CURRENT_SOURCE_DIR}/graphics/host_gpu/shaders")
set(gpu_tiler_generated_dir "${PROJECT_BINARY_DIR}/gpu_tiler_shaders")
set(gpu_tiler_shader_names
standard256
standard4
standard4_3d
standard64
standard64_3d
prt
prt_3d
render_target
depth
promote_d16
demote_d16
swap_bgra16
)
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
foreach(shader_name IN LISTS gpu_tiler_shader_names)
set(shader_source "${gpu_tiler_shader_dir}/gpu_tiler_${shader_name}.comp")
set(shader_spv "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}.spv")
set(shader_header "${gpu_tiler_generated_dir}/gpu_tiler_${shader_name}_spv.h")
string(TOUPPER "GPU_TILER_${shader_name}_SPV" shader_symbol)
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
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${shader_source}" ${gpu_tiler_shader_includes}
VERBATIM
)
list(APPEND gpu_tiler_shader_headers "${shader_header}")
endforeach()
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
set(gpu_blit_shader_sources
"${gpu_tiler_shader_dir}/gpu_blit_fs_triangle.vert"
"${gpu_tiler_shader_dir}/gpu_blit_color_to_ms_depth.frag"
)
foreach(shader_source IN LISTS gpu_blit_shader_sources)
get_filename_component(shader_name "${shader_source}" NAME_WE)
set(shader_spv "${gpu_blit_generated_dir}/${shader_name}.spv")
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
string(TOUPPER "${shader_name}_SPV" shader_symbol)
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
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${shader_source}"
VERBATIM
)
list(APPEND gpu_blit_shader_headers "${shader_header}")
endforeach()
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
list(APPEND kyty_emulator_src
emulator.h
emulator.cpp
)
list(REMOVE_DUPLICATES kyty_emulator_src)
if (KYTY_CLANG_CL)
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
endif()
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9)
set(inc_headers
${CMAKE_CURRENT_SOURCE_DIR}
${KYTY_THIRD_PARTY_DIR}/SDL2/include
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
${KYTY_THIRD_PARTY_DIR}/fmt/include
${KYTY_THIRD_PARTY_DIR}/xxhash
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
${KYTY_THIRD_PARTY_DIR}/stb
)
if (KYTY_CLANG_CL)
list(APPEND kyty_emulator_link_libraries winpthread)
list(APPEND inc_headers
${KYTY_THIRD_PARTY_DIR}/winpthread/include
)
endif()
list(APPEND check_headers
${CMAKE_CURRENT_SOURCE_DIR}
)
function(add_kyty_full_emulator_test target source)
add_executable(${target} EXCLUDE_FROM_ALL ${source} ${kyty_emulator_src})
target_link_libraries(${target} ${kyty_emulator_link_libraries})
target_include_directories(${target} PRIVATE ${inc_headers})
if (WIN32)
target_link_libraries(${target} iphlpapi)
endif()
if (KYTY_CLANG_CL)
target_link_libraries(${target} onecore)
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:${target}>/libwinpthread-1.dll)
endif()
endfunction()
add_kyty_full_emulator_test(shader_cfg_tests ../tests/shaderCfgTests.cpp)
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
../tests/ScalarProvenanceTests.cpp
graphics/host_gpu/hostMemory.cpp
graphics/shader/recompiler/ScalarProvenance.cpp
graphics/shader/recompiler/SrtWalker.cpp
)
target_link_libraries(scalar_provenance_tests fmt::fmt)
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
add_executable(page_manager_tests EXCLUDE_FROM_ALL
../tests/PageManagerTests.cpp
graphics/host_gpu/pageManager.cpp
)
target_include_directories(page_manager_tests PRIVATE ${inc_headers})
add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
../tests/MemoryTrackerTests.cpp
graphics/host_gpu/pageManager.cpp
graphics/host_gpu/memoryTracker.cpp
)
target_link_libraries(memory_tracker_tests fmt::fmt common)
target_include_directories(memory_tracker_tests PRIVATE ${inc_headers})
target_compile_definitions(memory_tracker_tests PRIVATE KYTY_MEMORY_TRACKER_TESTS=1)
add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL
../tests/ShaderVertexMetadataTests.cpp
graphics/host_gpu/hostMemory.cpp
graphics/shader/shaderVertexMetadata.cpp
)
target_include_directories(shader_vertex_metadata_tests PRIVATE ${inc_headers})
add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
../tests/ShaderStageRuntimeTests.cpp
graphics/guest_gpu/gpu_format.cpp
graphics/host_gpu/hostMemory.cpp
graphics/shader/shaderStageRuntime.cpp
graphics/shader/recompiler/ResourceMaterialization.cpp
graphics/shader/recompiler/ScalarProvenance.cpp
graphics/shader/recompiler/SrtWalker.cpp
)
target_link_libraries(shader_stage_runtime_tests fmt::fmt)
target_include_directories(shader_stage_runtime_tests PRIVATE ${inc_headers})
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
../tests/ResourceTrackingTests.cpp
graphics/guest_gpu/gpu_format.cpp
graphics/host_gpu/hostMemory.cpp
graphics/shader/recompiler/ScalarProvenance.cpp
graphics/shader/recompiler/SrtWalker.cpp
graphics/shader/recompiler/SrtPatcher.cpp
graphics/shader/recompiler/ResourceTracking.cpp
graphics/shader/recompiler/ResourceMaterialization.cpp
graphics/shader/recompiler/ShaderInfoCollection.cpp
graphics/shader/recompiler/BindingLayout.cpp
)
target_link_libraries(resource_tracking_tests fmt::fmt)
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
add_executable(resource_mutex_tests EXCLUDE_FROM_ALL
../tests/ResourceMutexTests.cpp
graphics/host_gpu/renderer/resourceMutex.cpp
)
target_link_libraries(resource_mutex_tests common)
target_include_directories(resource_mutex_tests PRIVATE ${inc_headers})
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
../tests/EventQueueLifetimeTests.cpp
kernel/eventQueue.cpp
loader/timer.cpp
)
target_link_libraries(event_queue_lifetime_tests common fmt::fmt)
target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers})
add_executable(image_page_table_tests EXCLUDE_FROM_ALL
../tests/ImagePageTableTests.cpp
)
target_link_libraries(image_page_table_tests common fmt::fmt)
target_include_directories(image_page_table_tests PRIVATE ${inc_headers})
add_kyty_full_emulator_test(shader_recompiler_compute_tests ../tests/ShaderRecompilerComputeTests.cpp)
set(gpu_test_generated_dir "${PROJECT_BINARY_DIR}/gpu_test_shaders")
set(gpu_test_ms_depth_source
"${gpu_tiler_shader_dir}/gpu_test_ms_depth.comp")
set(gpu_test_ms_depth_spv
"${gpu_test_generated_dir}/gpu_test_ms_depth.spv")
set(gpu_test_ms_depth_header
"${gpu_test_generated_dir}/gpu_test_ms_depth_spv.h")
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
-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
-P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${gpu_test_ms_depth_source}"
VERBATIM
)
target_sources(shader_recompiler_compute_tests PRIVATE
"${gpu_test_ms_depth_header}")
add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemoryAllocationTests.cpp)
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
if(BUILD_TESTING)
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
add_test(NAME resource_mutex COMMAND $<TARGET_FILE:resource_mutex_tests>)
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
add_test(NAME command_scheduler_timeline
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --scheduler-only)
add_test(NAME stream_buffer_ring
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
add_test(NAME gpu_command_lane
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
add_test(NAME gpu_tiler
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only)
if(WIN32)
add_test(NAME texture_cache_image_overlap
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
add_test(NAME texture_cache_htile_clear
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
add_test(NAME texture_cache_layered_image
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
add_test(NAME texture_cache_image_views
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
add_test(NAME texture_cache_storage_sampled
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
add_test(NAME texture_cache_depth_readback
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
add_test(NAME buffer_cache_ranges
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
add_test(NAME buffer_cache_dirty_gc
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
endif()
endif()
add_executable(kyty_emulator main.cpp ${kyty_emulator_src})
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
if (WIN32)
target_link_libraries(kyty_emulator opengl32)
target_link_libraries(kyty_emulator iphlpapi)
target_link_libraries(kyty_emulator setupapi)
target_link_libraries(kyty_emulator ws2_32)
endif()
if (KYTY_CLANG_CL)
target_link_libraries(kyty_emulator onecore)
endif()
if (CLANG AND NOT KYTY_CLANG_CL)
target_link_libraries(kyty_emulator pthread)
endif()
target_include_directories(kyty_emulator PRIVATE ${inc_headers})
clang_tidy_check(kyty_emulator "" "${check_headers}" "${inc_headers}")
include_what_you_use(kyty_emulator "${inc_headers}")
set(KYTY_EMULATOR_MAP_NAME "kyty_emulator_${KYTY_COMPILER_ID}_${KYTY_LINKER_ID}.map")
set(KYTY_EMULATOR_MAP_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/${KYTY_EMULATOR_MAP_NAME}")
set(KYTY_EMULATOR_PDB_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/kyty_emulator.pdb")
if(KYTY_CLANG_CL)
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "/DYNAMICBASE:NO /DEBUG:FULL /PDB:${KYTY_EMULATOR_PDB_LINK_PATH} /lldmap:${KYTY_EMULATOR_MAP_LINK_PATH}")
add_custom_command(TARGET kyty_emulator POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:kyty_emulator>/libwinpthread-1.dll)
elseif(WIN32 OR LINUX)
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
endif()
add_dependencies(kyty_emulator KytyGitVersion)
if(APPLE)
# The emulator writes x86-64 trampolines/PLT stubs into memory and executes them, and
# runs guest code under Rosetta. That requires the JIT / unsigned-executable-memory
# entitlements, so re-sign the binary after every link (an unsigned/relinked binary
# reverts to the hardened defaults and aborts when it first executes written code).
add_custom_command(TARGET kyty_emulator POST_BUILD
COMMAND codesign -s - --force --options runtime
--entitlements "${CMAKE_CURRENT_SOURCE_DIR}/macos_jit.entitlements"
$<TARGET_FILE:kyty_emulator>
COMMENT "Codesign kyty_emulator with JIT entitlements (macOS)")
endif()
install(TARGETS kyty_emulator DESTINATION .)
if(KYTY_BUILD_LAUNCHER)
add_subdirectory(launcher)
endif()
if(KYTY_CLANG_CL)
install(FILES "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" DESTINATION .)
endif()
+4
View File
@@ -1,4 +1,8 @@
#define KYTY_VERSION "@KYTY_VERSION@"
#define KYTY_BUILD_REPOSITORY "@KYTY_BUILD_REPOSITORY@"
#define KYTY_RELEASE_TAG "@KYTY_RELEASE_TAG@"
#cmakedefine KYTY_OFFICIAL_BUILD
#cmakedefine KYTY_FORK_BUILD
#define KYTY_ENDIAN @KYTY_ENDIAN@
#define KYTY_PLATFORM @KYTY_PLATFORM@
#define KYTY_COMPILER KYTY_COMPILER_@KYTY_COMPILER@
+2 -1
View File
@@ -13,7 +13,7 @@ endif()
add_library(common STATIC ${common_src} ${common_headers})
target_include_directories(common
PUBLIC ${PROJECT_SOURCE_DIR}
PUBLIC ${KYTY_SOURCE_DIR}
PRIVATE
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
)
@@ -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;
}
+260
View File
@@ -0,0 +1,260 @@
#ifndef EMULATOR_SRC_COMMON_BITARRAY_H_
#define EMULATOR_SRC_COMMON_BITARRAY_H_
#include <array>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <utility>
namespace Common {
template <size_t N>
class BitArray final {
static_assert(N != 0, "BitArray size must be nonzero");
static_assert(N % 64 == 0, "BitArray size must be a multiple of 64 bits");
static constexpr size_t BITS_PER_WORD = 64;
static constexpr size_t WORD_COUNT = N / BITS_PER_WORD;
public:
using Range = std::pair<size_t, size_t>;
class Iterator final {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = Range;
using difference_type = std::ptrdiff_t;
using pointer = const Range*;
using reference = const Range&;
Iterator(const BitArray& bits, size_t start)
: m_bits(bits), m_range(bits.FirstRangeFrom(start)) {}
Iterator& operator++() {
m_range = m_bits.FirstRangeFrom(m_range.second);
return *this;
}
[[nodiscard]] bool operator==(const Iterator& other) const {
return &m_bits == &other.m_bits && m_range == other.m_range;
}
[[nodiscard]] bool operator!=(const Iterator& other) const { return !(*this == other); }
[[nodiscard]] reference operator*() const { return m_range; }
[[nodiscard]] pointer operator->() const { return &m_range; }
private:
const BitArray& m_bits;
Range m_range;
};
using const_iterator = Iterator;
constexpr BitArray() = default;
constexpr BitArray(const BitArray& other, size_t start, size_t end) {
if (start >= end || end > N) {
return;
}
const auto first_word = start / BITS_PER_WORD;
const auto last_word = (end - 1) / BITS_PER_WORD;
const auto start_bit = start % BITS_PER_WORD;
const auto end_bit = (end - 1) % BITS_PER_WORD;
const auto start_mask = ~uint64_t {0} << start_bit;
const auto end_mask =
end_bit == BITS_PER_WORD - 1 ? ~uint64_t {0} : (uint64_t {1} << (end_bit + 1)) - 1;
if (first_word == last_word) {
m_data[first_word] = other.m_data[first_word] & start_mask & end_mask;
return;
}
m_data[first_word] = other.m_data[first_word] & start_mask;
for (auto word = first_word + 1; word < last_word; word++) {
m_data[word] = other.m_data[word];
}
m_data[last_word] = other.m_data[last_word] & end_mask;
}
[[nodiscard]] constexpr bool Get(size_t index) const {
return (m_data[index / BITS_PER_WORD] & (uint64_t {1} << (index % BITS_PER_WORD))) != 0;
}
constexpr void Set(size_t index) {
m_data[index / BITS_PER_WORD] |= uint64_t {1} << (index % BITS_PER_WORD);
}
constexpr void Unset(size_t index) {
m_data[index / BITS_PER_WORD] &= ~(uint64_t {1} << (index % BITS_PER_WORD));
}
constexpr void SetRange(size_t start, size_t end) {
if (start >= end || end > N) {
return;
}
const auto first_word = start / BITS_PER_WORD;
const auto last_word = (end - 1) / BITS_PER_WORD;
const auto start_bit = start % BITS_PER_WORD;
const auto end_bit = (end - 1) % BITS_PER_WORD;
const auto start_mask = ~uint64_t {0} << start_bit;
const auto end_mask =
end_bit == BITS_PER_WORD - 1 ? ~uint64_t {0} : (uint64_t {1} << (end_bit + 1)) - 1;
if (first_word == last_word) {
m_data[first_word] |= start_mask & end_mask;
return;
}
m_data[first_word] |= start_mask;
for (auto word = first_word + 1; word < last_word; word++) {
m_data[word] = ~uint64_t {0};
}
m_data[last_word] |= end_mask;
}
constexpr void UnsetRange(size_t start, size_t end) {
if (start >= end || end > N) {
return;
}
const auto first_word = start / BITS_PER_WORD;
const auto last_word = (end - 1) / BITS_PER_WORD;
const auto start_bit = start % BITS_PER_WORD;
const auto end_bit = (end - 1) % BITS_PER_WORD;
const auto start_mask = (uint64_t {1} << start_bit) - 1;
const auto end_mask =
end_bit == BITS_PER_WORD - 1 ? uint64_t {0} : ~((uint64_t {1} << (end_bit + 1)) - 1);
if (first_word == last_word) {
m_data[first_word] &= start_mask | end_mask;
return;
}
m_data[first_word] &= start_mask;
for (auto word = first_word + 1; word < last_word; word++) {
m_data[word] = 0;
}
m_data[last_word] &= end_mask;
}
constexpr void Clear() { m_data.fill(0); }
constexpr void Fill() { m_data.fill(~uint64_t {0}); }
[[nodiscard]] constexpr bool None() const {
uint64_t combined = 0;
for (const auto word: m_data) {
combined |= word;
}
return combined == 0;
}
[[nodiscard]] constexpr bool Any() const { return !None(); }
[[nodiscard]] constexpr Range FirstRangeFrom(size_t start) const {
if (start >= N) {
return {N, N};
}
auto word_index = start / BITS_PER_WORD;
auto word = m_data[word_index] & (~uint64_t {0} << (start % BITS_PER_WORD));
while (word == 0) {
word_index++;
if (word_index == WORD_COUNT) {
return {N, N};
}
word = m_data[word_index];
}
const auto first = word_index * BITS_PER_WORD + std::countr_zero(word);
const auto first_bit = first % BITS_PER_WORD;
const auto first_ones =
static_cast<size_t>(std::countr_one(m_data[word_index] >> first_bit));
if (first_bit + first_ones < BITS_PER_WORD) {
return {first, first + first_ones};
}
for (word_index++; word_index < WORD_COUNT; word_index++) {
word = m_data[word_index];
if (word != ~uint64_t {0}) {
return {first, word_index * BITS_PER_WORD + std::countr_one(word)};
}
}
return {first, N};
}
[[nodiscard]] constexpr Range FirstRange() const { return FirstRangeFrom(0); }
[[nodiscard]] constexpr Range LastRangeFrom(size_t end) const {
if (end == 0) {
return {0, 0};
}
if (end > N) {
end = N;
}
auto word_index = (end - 1) / BITS_PER_WORD;
const auto end_bit = (end - 1) % BITS_PER_WORD;
const auto end_mask =
end_bit == BITS_PER_WORD - 1 ? ~uint64_t {0} : (uint64_t {1} << (end_bit + 1)) - 1;
auto word = m_data[word_index] & end_mask;
while (word == 0) {
if (word_index == 0) {
return {0, 0};
}
word = m_data[--word_index];
}
const auto empty_bits = static_cast<size_t>(std::countl_zero(word));
const auto ones = static_cast<size_t>(std::countl_one(word << empty_bits));
const auto last = (word_index + 1) * BITS_PER_WORD - empty_bits;
if (empty_bits + ones < BITS_PER_WORD) {
return {last - ones, last};
}
while (word_index != 0) {
word = m_data[--word_index];
if (word != ~uint64_t {0}) {
return {(word_index + 1) * BITS_PER_WORD - std::countl_one(word), last};
}
}
return {0, last};
}
[[nodiscard]] constexpr Range LastRange() const { return LastRangeFrom(N); }
[[nodiscard]] const_iterator begin() const { return Iterator(*this, 0); }
[[nodiscard]] const_iterator end() const { return Iterator(*this, N); }
constexpr BitArray& operator^=(const BitArray& other) {
for (size_t word = 0; word < WORD_COUNT; word++) {
m_data[word] ^= other.m_data[word];
}
return *this;
}
[[nodiscard]] constexpr BitArray operator^(const BitArray& other) const {
auto result = *this;
result ^= other;
return result;
}
[[nodiscard]] constexpr BitArray operator~() const {
auto result = *this;
for (auto& word: result.m_data) {
word = ~word;
}
return result;
}
private:
std::array<uint64_t, WORD_COUNT> m_data {};
};
} // namespace Common
#endif // EMULATOR_SRC_COMMON_BITARRAY_H_
-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_ */
+26 -8
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);
@@ -33,10 +33,18 @@ uint32_t GetScreenHeight() {
return g_config->screen_height;
}
bool FullscreenEnabled() {
return g_config->fullscreen_enabled;
}
uint32_t GetVblankFrequency() {
return std::clamp(g_config->vblank_frequency, 30u, 360u);
}
uint32_t GetConsoleLanguage() {
return g_config->console_language;
}
bool VulkanValidationEnabled() {
return g_config->vulkan_validation_enabled;
}
@@ -89,12 +97,22 @@ bool RenderDocEnabled() {
return g_config->renderdoc_enabled;
}
bool NggRectlistDrawEnabled() {
return g_config->ngg_rectlist_draw_enabled;
}
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;
}
#endif
const Keymap& GetKeymap() {
return g_config->keymap;
}
} // namespace Config
+30 -4
View File
@@ -2,13 +2,21 @@
#define KYTY_COMMON_EMULATOR_CONFIG_H_
#include "common/common.h"
#include "common/subsystems.h"
#include <filesystem>
#include <string>
#include <vector>
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 };
@@ -18,10 +26,17 @@ enum class ProfilerDirection { None, Network };
enum class OutputDirection { Silent, Console, File };
using Keymap = std::vector<std::string>;
constexpr uint32_t DEFAULT_CONSOLE_LANGUAGE = 1;
constexpr uint32_t MAX_CONSOLE_LANGUAGE = 29;
struct ConfigOptions {
uint32_t screen_width = 1280;
uint32_t screen_height = 720;
bool fullscreen_enabled = false;
uint32_t vblank_frequency = 60;
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
bool vulkan_validation_enabled = false;
bool shader_validation_enabled = false;
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::None;
@@ -35,15 +50,21 @@ struct ConfigOptions {
ProfilerDirection profiler_direction = ProfilerDirection::None;
bool spirv_debug_printf_enabled = false;
bool renderdoc_enabled = false;
bool ngg_rectlist_draw_enabled = true;
bool readback_linear_images = false;
bool playgo_hack_enabled = false;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool red_zone_protection_enabled = false;
#endif
Keymap keymap;
};
void Load(const ConfigOptions& cfg);
uint32_t GetScreenWidth();
uint32_t GetScreenHeight();
bool FullscreenEnabled();
uint32_t GetVblankFrequency();
uint32_t GetConsoleLanguage();
bool VulkanValidationEnabled();
bool ShaderValidationEnabled();
@@ -64,8 +85,13 @@ ProfilerDirection GetProfilerDirection();
bool SpirvDebugPrintfEnabled();
bool RenderDocEnabled();
bool NggRectlistDrawEnabled();
bool ReadbackLinearImagesEnabled();
bool PlayGoHackEnabled();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool RedZoneProtectionEnabled();
#endif
const Keymap& GetKeymap();
} // namespace Config
+105 -87
View File
@@ -2,13 +2,17 @@
#include <atomic>
#include <cstdio>
#include <cstdlib>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <windows.h> // IWYU pragma: keep
#elif defined(__APPLE__)
#include <csignal>
#include <sys/ucontext.h>
#else
#include <csignal>
#include <initializer_list>
#include <ucontext.h> // IWYU pragma: keep
#include <unistd.h>
#endif
// IWYU pragma: no_include <errhandlingapi.h>
@@ -19,41 +23,18 @@
namespace Common::HostException {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#if !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);
#endif
[[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);
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
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 LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
FilterScope filter_scope;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) noexcept {
auto* exception_record = exception->ExceptionRecord;
if (exception_record->ExceptionCode == DBG_PRINTEXCEPTION_C ||
@@ -83,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;
}
@@ -109,35 +84,21 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
info.r14 = exception->ContextRecord->R14;
info.r15 = exception->ContextRecord->R15;
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");
if (handler != nullptr && handler(info)) {
return EXCEPTION_CONTINUE_EXECUTION;
}
return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
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) {
@@ -155,14 +116,9 @@ 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;
auto* uc = static_cast<ucontext_t*>(uctx);
const auto* mc = uc->uc_mcontext;
const auto& ss = mc->__ss;
ExceptionInfo info {};
info.exception_address = ss.__rip;
@@ -195,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
}
@@ -213,10 +162,76 @@ static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
sigaction(sig, &dfl, nullptr);
}
#else
// x86-64 page-fault error bits.
constexpr uint64_t PAGE_FAULT_ERROR_WRITE = 0x02;
constexpr uint64_t PAGE_FAULT_ERROR_INSTRUCTION = 0x10;
// Let the kernel handle an unresolved fault on retry.
static void ChainToDefault(int signal_number) noexcept {
struct sigaction restore {};
restore.sa_handler = SIG_DFL;
sigemptyset(&restore.sa_mask);
restore.sa_flags = 0;
::sigaction(signal_number, &restore, nullptr);
}
static void SignalHandler(int signal_number, siginfo_t* signal_info, void* native_context) {
auto* context = static_cast<ucontext_t*>(native_context);
auto* gregs = context->uc_mcontext.gregs;
ExceptionInfo info {};
info.exception_address = static_cast<uint64_t>(gregs[REG_RIP]);
info.native_code = static_cast<uint32_t>(signal_number);
info.native_context = context;
if (signal_number == SIGSEGV || signal_number == SIGBUS) {
info.type = ExceptionType::AccessViolation;
const auto error_code = static_cast<uint64_t>(gregs[REG_ERR]);
if ((error_code & PAGE_FAULT_ERROR_INSTRUCTION) != 0) {
info.access_violation_type = AccessViolationType::Execute;
} else if ((error_code & PAGE_FAULT_ERROR_WRITE) != 0) {
info.access_violation_type = AccessViolationType::Write;
} else {
info.access_violation_type = AccessViolationType::Read;
}
info.access_violation_vaddr = reinterpret_cast<uint64_t>(signal_info->si_addr);
} else if (signal_number == SIGILL) {
info.type = ExceptionType::IllegalInstruction;
} else {
ChainToDefault(signal_number);
return;
}
info.rax = static_cast<uint64_t>(gregs[REG_RAX]);
info.rbx = static_cast<uint64_t>(gregs[REG_RBX]);
info.rcx = static_cast<uint64_t>(gregs[REG_RCX]);
info.rdx = static_cast<uint64_t>(gregs[REG_RDX]);
info.rsi = static_cast<uint64_t>(gregs[REG_RSI]);
info.rdi = static_cast<uint64_t>(gregs[REG_RDI]);
info.rbp = static_cast<uint64_t>(gregs[REG_RBP]);
info.rsp = static_cast<uint64_t>(gregs[REG_RSP]);
info.r8 = static_cast<uint64_t>(gregs[REG_R8]);
info.r9 = static_cast<uint64_t>(gregs[REG_R9]);
info.r10 = static_cast<uint64_t>(gregs[REG_R10]);
info.r11 = static_cast<uint64_t>(gregs[REG_R11]);
info.r12 = static_cast<uint64_t>(gregs[REG_R12]);
info.r13 = static_cast<uint64_t>(gregs[REG_R13]);
info.r14 = static_cast<uint64_t>(gregs[REG_R14]);
info.r15 = static_cast<uint64_t>(gregs[REG_R15]);
const auto handler = g_handler.load(std::memory_order_acquire);
if (handler != nullptr && handler(info)) {
return;
}
ChainToDefault(signal_number);
}
#endif
bool InstallHandler(Handler handler) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (handler == nullptr) {
return false;
}
@@ -228,31 +243,22 @@ bool InstallHandler(Handler handler) {
g_handler.store(handler, std::memory_order_release);
if (AddVectoredExceptionHandler(1, ExceptionFilter) == nullptr) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
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");
return false;
}
g_install_state.store(2, std::memory_order_release);
return true;
#elif defined(__APPLE__)
if (handler == nullptr) {
return false;
}
uint32_t expected_state = 0;
if (!g_install_state.compare_exchange_strong(expected_state, 1, std::memory_order_acq_rel)) {
return expected_state == 2 && g_handler.load(std::memory_order_acquire) == handler;
}
g_handler.store(handler, std::memory_order_release);
struct sigaction sa {};
sa.sa_sigaction = SignalHandler;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
// The guest signal-dispatch path (KernelRaiseException) interrupts threads with
// SIGUSR1; block it while a fault is being resolved so a stop-the-world request
// cannot preempt the handler between the protection fix and the retry.
sigaddset(&sa.sa_mask, SIGUSR1);
// macOS raises SIGBUS for protection faults on some paths and SIGSEGV on others;
// SIGILL covers instructions the host cannot execute (routed to the x64 emulator).
@@ -264,13 +270,25 @@ bool InstallHandler(Handler handler) {
printf("sigaction() failed to install the host fault handler\n");
return false;
}
#else
struct sigaction action {};
action.sa_sigaction = SignalHandler;
sigemptyset(&action.sa_mask);
// Fault resolution needs the normal thread stack.
action.sa_flags = SA_SIGINFO | SA_RESTART;
for (const int signal_number: {SIGSEGV, SIGBUS, SIGILL}) {
if (::sigaction(signal_number, &action, nullptr) != 0) {
g_handler.store(nullptr, std::memory_order_release);
g_install_state.store(0, std::memory_order_release);
printf("sigaction(%d) failed\n", signal_number);
return false;
}
}
#endif
g_install_state.store(2, std::memory_order_release);
return true;
#else
(void)handler;
return false;
#endif
}
} // namespace Common::HostException
+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 };
+7 -8
View File
@@ -19,10 +19,10 @@ class LeastRecentlyUsedCache {
public:
[[nodiscard]] size_t Insert(Object object, Tick tick) {
const auto id = Build();
const auto id = Build();
auto& item = m_items[id];
item.object = std::move(object);
item.tick = tick;
item.object = std::move(object);
item.tick = tick;
Attach(item);
return id;
}
@@ -49,8 +49,7 @@ public:
template <typename Function>
void ForEachItemBelow(Tick tick, Function&& function) {
constexpr bool ReturnsBool =
std::is_same_v<std::invoke_result_t<Function, Object>, bool>;
constexpr bool ReturnsBool = std::is_same_v<std::invoke_result_t<Function, Object>, bool>;
for (auto* item = m_first; item != nullptr;) {
if (item->tick > tick) {
return;
@@ -87,10 +86,10 @@ private:
m_last = &item;
return;
}
item.prev = m_last;
item.prev = m_last;
m_last->next = &item;
item.next = nullptr;
m_last = &item;
item.next = nullptr;
m_last = &item;
}
void Detach(Item& item) {
+4
View File
@@ -23,6 +23,10 @@ struct sys_dbg_stack_info_t {
size_t commited_size;
size_t total_size;
size_t code_size;
// Full stack reservation reported by pthread.
uintptr_t reserved_addr;
size_t reserved_size;
#endif
};
+75 -4
View File
@@ -8,6 +8,8 @@
#include <cstdlib>
#include <cstring>
#include <execinfo.h>
#include <pthread.h>
#include <sys/param.h>
#include <sys/types.h>
#include <unistd.h>
@@ -15,8 +17,47 @@
#include <libgen.h> // POSIX basename() lives here on macOS, not in <cstring>
#endif
void SysStackWalk(void** /*stack*/, int* depth) {
*depth = 0;
// Avoid unwinding a guest-owned stack.
static bool OnOwnStack() {
const char* probe = reinterpret_cast<const char*>(&probe);
pthread_attr_t attr {};
#if defined(__APPLE__)
const auto* top = static_cast<const char*>(pthread_get_stackaddr_np(pthread_self()));
const auto size = pthread_get_stacksize_np(pthread_self());
(void)attr;
return top != nullptr && size != 0 && probe < top && probe >= top - size;
#else
if (pthread_getattr_np(pthread_self(), &attr) != 0) {
return false;
}
void* base = nullptr;
size_t size = 0;
const bool ok = pthread_attr_getstack(&attr, &base, &size) == 0 && base != nullptr && size != 0;
pthread_attr_destroy(&attr);
if (!ok) {
return false;
}
const auto* low = static_cast<const char*>(base);
return probe >= low && probe < low + size;
#endif
}
void SysStackWalk(void** stack, int* depth) {
if (stack == nullptr || depth == nullptr || *depth <= 0) {
if (depth != nullptr) {
*depth = 0;
}
return;
}
if (!OnOwnStack()) {
*depth = 0;
return;
}
const int n = ::backtrace(stack, *depth);
*depth = (n < 0 ? 0 : n);
}
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
@@ -33,6 +74,33 @@ void SysStackUsage(sys_dbg_stack_info_t& s) {
[[maybe_unused]] int result = 0;
memset(&s, 0, sizeof(sys_dbg_stack_info_t));
// Record the reservation before the Linux /proc walk.
{
pthread_attr_t self_attr {};
#if defined(__APPLE__)
void* stack_top = pthread_get_stackaddr_np(pthread_self());
const size_t stack_size = pthread_get_stacksize_np(pthread_self());
if (stack_top != nullptr && stack_size != 0) {
s.reserved_addr = reinterpret_cast<uintptr_t>(stack_top) - stack_size;
s.reserved_size = stack_size;
}
(void)self_attr;
#else
if (pthread_getattr_np(pthread_self(), &self_attr) == 0) {
void* stack_base = nullptr;
size_t stack_size = 0;
if (pthread_attr_getstack(&self_attr, &stack_base, &stack_size) == 0 &&
stack_base != nullptr && stack_size != 0) {
s.reserved_addr = reinterpret_cast<uintptr_t>(stack_base);
s.reserved_size = stack_size;
}
pthread_attr_destroy(&self_attr);
}
#endif
}
char str[1024];
char str2[1024];
result = sprintf(str, "/proc/%d/exe", static_cast<int>(pid));
@@ -46,8 +114,6 @@ void SysStackUsage(sys_dbg_stack_info_t& s) {
result = sprintf(str, "/proc/%d/maps", static_cast<int>(pid));
memset(&s, 0, sizeof(sys_dbg_stack_info_t));
FILE* f = fopen(str, "r");
if (f == nullptr) {
@@ -121,6 +187,11 @@ void SysStackUsage(sys_dbg_stack_info_t& s) {
}
result = fclose(f);
if (s.reserved_addr == 0) {
s.reserved_addr = s.addr;
s.reserved_size = s.total_size;
}
}
#endif
+187 -41
View File
@@ -12,7 +12,10 @@
#include <cerrno>
#include <cstdlib>
#include <dirent.h>
#include <fcntl.h>
#include <filesystem>
#include <sys/stat.h>
#include <system_error>
#include <unistd.h>
#include <utime.h>
@@ -40,10 +43,43 @@ struct sys_file_t {
};
};
// Darwin uses BSD timestamp member names.
#if defined(__APPLE__)
#define KYTY_STAT_ATIME_NS(st) ((st).st_atimespec.tv_nsec)
#define KYTY_STAT_MTIME_NS(st) ((st).st_mtimespec.tv_nsec)
#else
#define KYTY_STAT_ATIME_NS(st) ((st).st_atim.tv_nsec)
#define KYTY_STAT_MTIME_NS(st) ((st).st_mtim.tv_nsec)
#endif
static std::filesystem::path get_internal_name(const std::filesystem::path& name) {
return name.is_absolute() ? name : (std::filesystem::path(".") / name);
}
// Pass access-pattern hints to the host.
static void apply_cache_hint(FILE* f, sys_file_cache_type_t cache_type) {
if (f == nullptr) {
return;
}
#if !defined(__APPLE__)
int advice = POSIX_FADV_NORMAL;
switch (cache_type) {
case SYS_FILE_CACHE_RANDOM_ACCESS: advice = POSIX_FADV_RANDOM; break;
case SYS_FILE_CACHE_SEQUENTIAL_SCAN: advice = POSIX_FADV_SEQUENTIAL; break;
case SYS_FILE_CACHE_AUTO:
default: return;
}
::posix_fadvise(fileno(f), 0, 0, advice);
#else
if (cache_type == SYS_FILE_CACHE_SEQUENTIAL_SCAN) {
::fcntl(fileno(f), F_RDAHEAD, 1);
} else if (cache_type == SYS_FILE_CACHE_RANDOM_ACCESS) {
::fcntl(fileno(f), F_RDAHEAD, 0);
}
#endif
}
void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) {
if (f.type == SYS_FILE_FILE) {
size_t w = fread(data, 1, size, f.f);
@@ -136,8 +172,7 @@ sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
return ret;
}
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
sys_file_cache_type_t /*cache_type*/) {
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
auto* ret = new sys_file_t;
ret->type = SYS_FILE_FILE;
@@ -151,6 +186,8 @@ sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
ret->type = SYS_FILE_ERROR;
}
apply_cache_hint(f, cache_type);
ret->f = f;
return ret;
@@ -180,8 +217,7 @@ sys_file_t* SysFileCreate() {
return ret;
}
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
sys_file_cache_type_t /*cache_type*/) {
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
auto* ret = new sys_file_t;
auto real_name = get_internal_name(file_name);
@@ -195,13 +231,15 @@ sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
ret->type = SYS_FILE_FILE;
}
apply_cache_hint(f, cache_type);
ret->f = f;
return ret;
}
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
sys_file_cache_type_t /*cache_type*/) {
sys_file_cache_type_t cache_type) {
auto* ret = new sys_file_t;
auto real_name = get_internal_name(file_name);
@@ -215,6 +253,8 @@ sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
ret->type = SYS_FILE_FILE;
}
apply_cache_hint(f, cache_type);
ret->f = f;
return ret;
@@ -240,11 +280,17 @@ uint64_t SysFileSize(sys_file_t& f) {
[[maybe_unused]] int result = 0;
if (f.type == SYS_FILE_FILE) {
uint32_t pos = ftell(f.f);
result = fseek(f.f, 0, SEEK_END);
uint32_t size = ftell(f.f);
result = fseek(f.f, pos, SEEK_SET);
return size;
// Preserve sizes above 4 GiB.
const off_t pos = ftello(f.f);
if (pos < 0) {
return 0;
}
if (fseeko(f.f, 0, SEEK_END) != 0) {
return 0;
}
const off_t size = ftello(f.f);
result = fseeko(f.f, pos, SEEK_SET);
return (size < 0 ? 0 : static_cast<uint64_t>(size));
}
if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
@@ -261,8 +307,18 @@ uint64_t SysFileSize(const std::filesystem::path& file_name) {
return size;
}
bool SysFileTruncate(sys_file_t& /*f*/, uint64_t /*size*/) {
return false;
bool SysFileTruncate(sys_file_t& f, uint64_t size) {
bool ok = false;
if (f.type == SYS_FILE_FILE) {
// Flush before resizing and restore the caller's position.
const auto position = ftell(f.f);
fflush(f.f);
ok = (ftruncate(fileno(f.f), static_cast<off_t>(size)) == 0);
if (position >= 0) {
fseek(f.f, position, SEEK_SET);
}
}
return ok;
}
bool SysFileUnlink(sys_file_t& /*f*/, const std::filesystem::path& name) {
@@ -383,6 +439,7 @@ SysFileTimeStruct SysFileGetLastAccessTimeUtc(const std::filesystem::path& name)
} else {
r.is_invalid = false;
r.time = s.st_atime;
r.nanos = KYTY_STAT_ATIME_NS(s);
}
return r;
@@ -401,6 +458,7 @@ SysFileTimeStruct SysFileGetLastWriteTimeUtc(const std::filesystem::path& name)
} else {
r.is_invalid = false;
r.time = s.st_mtime;
r.nanos = KYTY_STAT_MTIME_NS(s);
}
return r;
@@ -420,13 +478,36 @@ void SysFileGetLastAccessAndWriteTimeUtc(const std::filesystem::path& name, SysF
a.is_invalid = false;
w.is_invalid = false;
a.time = s.st_atime;
a.nanos = KYTY_STAT_ATIME_NS(s);
w.time = s.st_mtime;
w.nanos = KYTY_STAT_MTIME_NS(s);
}
}
void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& /*f*/, SysFileTimeStruct& /*a*/,
SysFileTimeStruct& /*w*/) {
EXIT("not implemented\n");
void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& f, SysFileTimeStruct& a,
SysFileTimeStruct& w) {
if (f.type == SYS_FILE_FILE) {
struct stat s {};
const bool ok = (0 == fstat(fileno(f.f), &s));
a.is_invalid = w.is_invalid = !ok;
if (ok) {
a.time = s.st_atime;
a.nanos = KYTY_STAT_ATIME_NS(s);
w.time = s.st_mtime;
w.nanos = KYTY_STAT_MTIME_NS(s);
}
} else if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
// Memory-backed files use the current time.
SysTimeStruct t {};
SysGetSystemTimeUtc(t);
SysSystemToFileTimeUtc(t, a);
SysSystemToFileTimeUtc(t, w);
} else {
a.is_invalid = w.is_invalid = true;
}
}
bool SysFileSetLastAccessTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& access) {
@@ -532,45 +613,110 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
// }
}
void SysFileFindFiles(const std::filesystem::path& /*path*/,
std::vector<sys_file_find_t>& /*out*/) {
EXIT("not implemented\n");
}
// Recursively collect regular files.
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
auto real_path = get_internal_name(path);
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
DIR* dir = opendir(path.c_str());
DIR* dir = opendir(real_path.string().c_str());
if (dir == nullptr) {
return;
}
for (dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
sys_dir_entry_t r {};
r.name = entry->d_name;
if (entry->d_type == DT_REG) {
r.is_file = true;
} else if (entry->d_type == DT_DIR) {
r.is_file = false;
} else {
// DT_UNKNOWN / symlink: resolve with stat.
struct stat st {};
r.is_file = (stat((path / entry->d_name).c_str(), &st) == 0) ? S_ISREG(st.st_mode) : true;
for (const dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
const std::string file_name(entry->d_name);
if (file_name == "." || file_name == "..") {
continue;
}
auto child = real_path / file_name;
struct stat s {};
// lstat, so a symlink is never followed into a cycle during the recursive walk.
if (0 != lstat(child.string().c_str(), &s)) {
continue;
}
if (S_ISDIR(s.st_mode)) {
SysFileFindFiles(child, out);
} else if (S_ISREG(s.st_mode)) {
sys_file_find_t r {};
r.path_with_name = child;
r.size = static_cast<uint64_t>(s.st_size);
r.last_access_time.is_invalid = false;
r.last_access_time.time = s.st_atime;
r.last_access_time.nanos = KYTY_STAT_ATIME_NS(s);
r.last_write_time.is_invalid = false;
r.last_write_time.time = s.st_mtime;
r.last_write_time.nanos = KYTY_STAT_MTIME_NS(s);
out.push_back(r);
}
out.push_back(std::move(r));
}
closedir(dir);
}
bool SysFileCopyFile(const std::filesystem::path& /*src*/, const std::filesystem::path& /*dst*/) {
EXIT("not implemented\n");
return false;
// Keep "." and ".." to match FindFirstFileW.
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
auto real_path = get_internal_name(path);
DIR* dir = opendir(real_path.string().c_str());
if (dir == nullptr) {
return;
}
for (const dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
sys_dir_entry_t r {};
r.name = entry->d_name;
if (entry->d_type == DT_UNKNOWN) {
// Some filesystems do not populate d_type.
struct stat s {};
r.is_file = 0 == lstat((real_path / r.name).string().c_str(), &s) && S_ISREG(s.st_mode);
} else {
r.is_file = entry->d_type != DT_DIR;
}
out.push_back(r);
}
closedir(dir);
}
bool SysFileMoveFile(const std::filesystem::path& /*src*/, const std::filesystem::path& /*dst*/) {
EXIT("not implemented\n");
return false;
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
std::error_code error;
return std::filesystem::copy_file(get_internal_name(src), get_internal_name(dst),
std::filesystem::copy_options::overwrite_existing, error) &&
!error;
}
void SysFileRemoveReadonly(const std::filesystem::path& /*name*/) {
EXIT("not implemented\n");
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
auto real_src = get_internal_name(src);
auto real_dst = get_internal_name(dst);
// Match MoveFileW: fail when the destination exists.
std::error_code error;
if (std::filesystem::exists(real_dst, error)) {
return false;
}
return 0 == rename(real_src.string().c_str(), real_dst.string().c_str());
}
void SysFileRemoveReadonly(const std::filesystem::path& name) {
auto real_name = get_internal_name(name);
auto real_name_str = real_name.string();
struct stat s {};
if (0 != stat(real_name_str.c_str(), &s)) {
return;
}
chmod(real_name_str.c_str(), s.st_mode | S_IWUSR);
}
#endif
+134 -40
View File
@@ -8,9 +8,11 @@
#include "common/platform/sysVirtual.h"
#include "common/virtualMemory.h"
#include <atomic>
#include <map>
#include <pthread.h>
#include <sys/mman.h>
#include <unistd.h>
#if defined(__APPLE__)
#include <mach/mach.h>
@@ -81,6 +83,70 @@ static VirtualMemory::Mode get_protection_flag(int mode) {
}
}
// Keep automatic mappings inside the guest and GPU-addressable low window.
#ifdef KYTY_FIXED_NOREPLACE
static constexpr uintptr_t LOW_ARENA_LIMIT = 0x000000FC00000000ULL; // libc mspace window ceiling
static constexpr uintptr_t LOW_ARENA_FLOOR = 0x000000A000000000ULL; // 640 GiB
static constexpr uintptr_t LOW_ARENA_GRAIN = 0x0000000000010000ULL; // 64 KiB
static_assert(LOW_ARENA_LIMIT <= 0x0000010000000000ULL,
"arena must stay inside the GPU page tracker's 1<<40 window");
static_assert(LOW_ARENA_FLOOR < LOW_ARENA_LIMIT, "arena floor must sit below its ceiling");
static std::atomic<uintptr_t> g_low_arena_next {LOW_ARENA_LIMIT};
#endif
// Caller holds g_virtual_mutex.
static void record_alloc(uintptr_t addr, size_t size) {
auto next = g_allocs->upper_bound(addr);
if (next != g_allocs->begin()) {
auto it = std::prev(next);
const auto alloc_addr = it->first;
const auto alloc_end = alloc_addr + it->second;
if (alloc_addr <= addr && addr + size <= alloc_end) {
g_allocs->erase(it);
if (alloc_addr < addr) {
(*g_allocs)[alloc_addr] = addr - alloc_addr;
}
if (addr + size < alloc_end) {
(*g_allocs)[addr + size] = alloc_end - (addr + size);
}
}
}
(*g_allocs)[addr] = size;
}
#ifdef KYTY_FIXED_NOREPLACE
static uintptr_t align_up_to(uintptr_t addr, uint64_t alignment) {
return (addr + alignment - 1) & ~(alignment - 1);
}
#endif
// Freed arena addresses are not reused while GPU caches remain keyed by address.
static void* map_anonymous(uintptr_t addr, size_t size, int protect, int flags) {
if (addr != 0) {
return mmap(reinterpret_cast<void*>(addr), size, protect, flags, -1, 0); // NOLINT
}
#ifdef KYTY_FIXED_NOREPLACE
const auto step = align_up_to(size, LOW_ARENA_GRAIN);
for (int attempt = 0; attempt < 256; attempt++) {
const auto top = g_low_arena_next.fetch_sub(step, std::memory_order_relaxed);
if (top < step || top - step < LOW_ARENA_FLOOR) {
break;
}
const auto hint = (top - step) & ~(LOW_ARENA_GRAIN - 1);
void* ptr = mmap(reinterpret_cast<void*>(hint), size, protect, flags | MAP_FIXED_NOREPLACE,
-1, 0); // NOLINT
if (ptr != MAP_FAILED) {
return ptr;
}
}
#endif
return mmap(nullptr, size, protect, flags, -1, 0); // NOLINT
}
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
EXIT_IF(g_allocs == nullptr);
@@ -88,16 +154,15 @@ uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mo
int protect = get_protection_flag(mode);
void* ptr =
mmap(reinterpret_cast<void*>(addr), size, protect, MAP_PRIVATE | MAP_ANON, -1, 0); // NOLINT
void* ptr = map_anonymous(addr, size, protect, MAP_PRIVATE | MAP_ANON);
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED) {
pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size;
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
(*g_protects)[page] = protect;
}
@@ -122,16 +187,15 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
auto addr = static_cast<uintptr_t>(address);
int protect = get_protection_flag(mode);
void* ptr =
mmap(reinterpret_cast<void*>(addr), size, protect, MAP_PRIVATE | MAP_ANON, -1, 0); // NOLINT
void* ptr = map_anonymous(addr, size, protect, MAP_PRIVATE | MAP_ANON);
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
munmap(ptr, size);
ptr = mmap(reinterpret_cast<void*>(addr), size + alignment, protect,
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
ptr =
map_anonymous(addr, size + alignment, protect, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED) {
#if defined(__APPLE__)
@@ -186,9 +250,9 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
}
pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size;
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
(*g_protects)[page] = protect;
}
@@ -202,9 +266,9 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
// the first mapped region at or above `region_addr`; if it begins before the end of the
// requested range, the range overlaps an existing mapping.
static bool is_mapped(void* ptr, size_t length) {
auto query_addr = reinterpret_cast<mach_vm_address_t>(ptr);
mach_vm_address_t region_addr = query_addr;
mach_vm_size_t region_size = 0;
auto query_addr = reinterpret_cast<mach_vm_address_t>(ptr);
mach_vm_address_t region_addr = query_addr;
mach_vm_size_t region_size = 0;
vm_region_basic_info_data_64_t info {};
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
mach_port_t object_name = MACH_PORT_NULL;
@@ -272,9 +336,9 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
if (ptr != MAP_FAILED) {
pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size;
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
(*g_protects)[page] = protect;
}
@@ -303,16 +367,15 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
auto addr = static_cast<uintptr_t>(address);
void* ptr = mmap(reinterpret_cast<void*>(addr), size, PROT_NONE,
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
void* ptr = map_anonymous(addr, size, PROT_NONE, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
munmap(ptr, size);
ptr = mmap(reinterpret_cast<void*>(addr), size + alignment, PROT_NONE,
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
ptr = map_anonymous(addr, size + alignment, PROT_NONE,
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED) {
#if defined(__APPLE__)
@@ -368,12 +431,7 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
}
pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size;
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
(*g_protects)[page] = PROT_NONE;
}
record_alloc(ret_addr, size);
pthread_mutex_unlock(&g_virtual_mutex);
return ret_addr;
@@ -406,12 +464,7 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
if (ptr != MAP_FAILED) {
pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size;
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
(*g_protects)[page] = PROT_NONE;
}
record_alloc(ret_addr, size);
pthread_mutex_unlock(&g_virtual_mutex);
return true;
@@ -421,7 +474,29 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
}
bool SysVirtualDecommit(uint64_t address, uint64_t size) {
return SysVirtualProtect(address, size, VirtualMemory::Mode::NoAccess);
// Drop physical pages while preserving the reservation.
if (!SysVirtualProtect(address, size, VirtualMemory::Mode::NoAccess)) {
return false;
}
if (size != 0) {
#if defined(__APPLE__)
constexpr int RECLAIM_ADVICE = MADV_FREE;
#else
constexpr int RECLAIM_ADVICE = MADV_DONTNEED;
#endif
const auto page_size = static_cast<uintptr_t>(sysconf(_SC_PAGESIZE));
if (page_size != 0) {
// Do not discard pages outside the requested range.
const auto begin = (static_cast<uintptr_t>(address) + page_size - 1) & ~(page_size - 1);
const auto end = (static_cast<uintptr_t>(address) + size) & ~(page_size - 1);
if (end > begin) {
::madvise(reinterpret_cast<void*>(begin), end - begin, RECLAIM_ADVICE);
}
}
}
return true;
}
bool SysVirtualFree(uint64_t address) {
@@ -473,15 +548,34 @@ bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
pthread_mutex_unlock(&g_virtual_mutex);
return false;
}
auto it = std::prev(next);
const auto alloc_addr = it->first;
const auto alloc_end = alloc_addr + it->second;
if (addr < alloc_addr || end > alloc_end || munmap(reinterpret_cast<void*>(addr), size) != 0) {
// A reservation may have been split into several adjacent records.
auto first = std::prev(next);
const auto alloc_addr = first->first;
if (addr < alloc_addr || alloc_addr + first->second <= addr) {
pthread_mutex_unlock(&g_virtual_mutex);
return false;
}
g_allocs->erase(it);
auto last = first;
uintptr_t cursor = alloc_addr + first->second;
while (cursor < end) {
auto following = std::next(last);
if (following == g_allocs->end() || following->first != cursor) {
pthread_mutex_unlock(&g_virtual_mutex);
return false;
}
last = following;
cursor = following->first + following->second;
}
const auto alloc_end = cursor;
if (munmap(reinterpret_cast<void*>(addr), size) != 0) {
pthread_mutex_unlock(&g_virtual_mutex);
return false;
}
g_allocs->erase(first, std::next(last));
if (alloc_addr < addr) {
(*g_allocs)[alloc_addr] = addr - alloc_addr;
}
+11 -7
View File
@@ -27,7 +27,9 @@ struct SysFileTimeStruct {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
FILETIME time;
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
// Nanoseconds preserve sub-second file timestamps.
time_t time;
long nanos;
#endif
bool is_invalid;
};
@@ -139,7 +141,7 @@ inline void SysFileToSystemTimeUtc(const SysFileTimeStruct& f, SysTimeStruct& t)
t.Hour = i.tm_hour;
t.Minute = i.tm_min;
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
t.Milliseconds = 0;
t.Milliseconds = static_cast<uint16_t>((f.nanos / 1000000) % 1000);
}
inline void SysTimeTToSystem(time_t t, SysTimeStruct& s) {
@@ -168,10 +170,11 @@ inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t)
// Retrieves the current local date and time.
inline void SysGetSystemTime(SysTimeStruct& t) {
time_t st {};
// Preserve millisecond precision.
timespec now {};
struct tm i {};
if (time(&st) == static_cast<time_t>(-1) || localtime_r(&st, &i) == nullptr) {
if (clock_gettime(CLOCK_REALTIME, &now) != 0 || localtime_r(&now.tv_sec, &i) == nullptr) {
t.is_invalid = true;
return;
}
@@ -183,15 +186,16 @@ inline void SysGetSystemTime(SysTimeStruct& t) {
t.Hour = i.tm_hour;
t.Minute = i.tm_min;
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
t.Milliseconds = 0;
t.Milliseconds = static_cast<uint16_t>((now.tv_nsec / 1000000) % 1000);
}
// Retrieves the current system date and time in Coordinated Universal Time (UTC).
inline void SysGetSystemTimeUtc(SysTimeStruct& t) {
time_t st {};
// Preserve millisecond precision.
timespec now {};
struct tm i {};
if (time(&st) == static_cast<time_t>(-1) || gmtime_r(&st, &i) == nullptr) {
if (clock_gettime(CLOCK_REALTIME, &now) != 0 || gmtime_r(&now.tv_sec, &i) == nullptr) {
t.is_invalid = true;
return;
}
@@ -203,7 +207,7 @@ inline void SysGetSystemTimeUtc(SysTimeStruct& t) {
t.Hour = i.tm_hour;
t.Minute = i.tm_min;
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
t.Milliseconds = 0;
t.Milliseconds = static_cast<uint16_t>((now.tv_nsec / 1000000) % 1000);
}
inline void SysQueryPerformanceFrequency(uint64_t* freq) {
+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
+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_ */
+54 -7
View File
@@ -5,6 +5,7 @@
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <chrono> // IWYU pragma: keep
#include <condition_variable> // IWYU pragma: keep
#include <mutex>
@@ -14,6 +15,12 @@
#define KYTY_WIN_CS
#endif
// macOS has no clock_nanosleep.
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS && !defined(__APPLE__)
#define KYTY_POSIX_HIGH_RES_SLEEP
#include <ctime>
#endif
#include <sstream>
#include <string>
#include <thread>
@@ -21,7 +28,8 @@
#ifdef KYTY_WIN_CS
#include <windows.h> // IWYU pragma: keep
// IWYU pragma: no_include <winbase.h>
constexpr DWORD KYTY_CS_SPIN_COUNT = 4000;
constexpr DWORD KYTY_CS_SPIN_COUNT = 4000;
constexpr uint64_t KYTY_SLEEP_SPIN_LIMIT_100NS = 500; // 50 us
#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
@@ -32,7 +40,10 @@ static void SleepHighResolution100ns(uint64_t units_100ns) {
return;
}
if (units_100ns <= 10000) {
// Keep spinning only where a kernel transition is
// likely to cost more than the requested delay; ordinary millisecond sleeps use the
// per-thread high-resolution waitable timer below.
if (units_100ns <= KYTY_SLEEP_SPIN_LIMIT_100NS) {
LARGE_INTEGER frequency {};
LARGE_INTEGER start {};
if (QueryPerformanceFrequency(&frequency) != 0 && QueryPerformanceCounter(&start) != 0 &&
@@ -121,6 +132,42 @@ static SleepConditionVariableCS_func_t ResolveSleepConditionVariableCS() {
#endif
#ifdef KYTY_POSIX_HIGH_RES_SLEEP
// Spin for very short waits; use an absolute deadline for longer waits.
static void SleepHighResolutionNanos(uint64_t nanos) {
if (nanos == 0) {
return;
}
constexpr uint64_t NANOS_PER_SEC = 1000000000;
constexpr uint64_t SPIN_LIMIT_NS = 50000; // below this a context switch dominates
timespec deadline {};
if (clock_gettime(CLOCK_MONOTONIC, &deadline) != 0) {
std::this_thread::sleep_for(std::chrono::nanoseconds(nanos));
return;
}
auto target_nsec = static_cast<uint64_t>(deadline.tv_nsec) + nanos;
deadline.tv_sec += static_cast<time_t>(target_nsec / NANOS_PER_SEC);
deadline.tv_nsec = static_cast<long>(target_nsec % NANOS_PER_SEC);
if (nanos <= SPIN_LIMIT_NS) {
timespec now {};
do {
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
return;
}
} while (now.tv_sec < deadline.tv_sec ||
(now.tv_sec == deadline.tv_sec && now.tv_nsec < deadline.tv_nsec));
return;
}
while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &deadline, nullptr) == EINTR) {
}
}
#endif
namespace Common {
using thread_id_t = std::thread::id;
@@ -205,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) {
@@ -249,6 +292,8 @@ void Thread::Sleep(uint32_t millis) {
void Thread::SleepMicro(uint32_t micros) {
#ifdef KYTY_WIN_CS
SleepHighResolution100ns(static_cast<uint64_t>(micros) * 10);
#elif defined(KYTY_POSIX_HIGH_RES_SLEEP)
SleepHighResolutionNanos(static_cast<uint64_t>(micros) * 1000);
#else
std::this_thread::sleep_for(std::chrono::microseconds(micros));
#endif
@@ -257,6 +302,8 @@ void Thread::SleepMicro(uint32_t micros) {
void Thread::SleepNano(uint64_t nanos) {
#ifdef KYTY_WIN_CS
SleepHighResolution100ns((nanos + 99) / 100);
#elif defined(KYTY_POSIX_HIGH_RES_SLEEP)
SleepHighResolutionNanos(nanos);
#else
std::this_thread::sleep_for(std::chrono::nanoseconds(nanos));
#endif
+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 (*)();
+2 -4
View File
@@ -11,7 +11,7 @@ template <typename Result, typename... Args>
class UniqueFunction {
class CallableBase {
public:
virtual ~CallableBase() = default;
virtual ~CallableBase() = default;
virtual Result Invoke(Args&&... args) = 0;
};
@@ -20,9 +20,7 @@ class UniqueFunction {
public:
explicit Callable(Function function): m_function(std::move(function)) {}
Result Invoke(Args&&... args) override {
return m_function(std::forward<Args>(args)...);
}
Result Invoke(Args&&... args) override { return m_function(std::forward<Args>(args)...); }
private:
Function m_function;
-19
View File
@@ -58,25 +58,6 @@ bool FlushInstructionCache(uint64_t address, uint64_t size) {
return SysVirtualFlushInstructionCache(address, size);
}
bool PatchReplace(uint64_t vaddr, uint64_t value) {
Mode old_mode {};
Protect(vaddr, 8, Mode::ReadWrite, &old_mode);
auto* ptr = reinterpret_cast<uint64_t*>(vaddr);
bool ret = (*ptr != value);
*ptr = value;
Protect(vaddr, 8, old_mode);
if (IsExecute(old_mode)) {
FlushInstructionCache(vaddr, 8);
}
return ret;
}
} // namespace VirtualMemory
} // namespace Common
-1
View File
@@ -37,7 +37,6 @@ bool Free(uint64_t address);
bool FreeRange(uint64_t address, uint64_t size);
bool Protect(uint64_t address, uint64_t size, Mode mode, Mode* old_mode = nullptr);
bool FlushInstructionCache(uint64_t address, uint64_t size);
bool PatchReplace(uint64_t vaddr, uint64_t value);
} // namespace VirtualMemory
+25 -42
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"
@@ -43,7 +42,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,41 +104,32 @@ static void ClearDebugTextureFolder() {
}
}
static void Init(const Config::ConfigOptions& cfg) {
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);
subsystems.Initialize<Log::Lifecycle>();
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(log, {core, config});
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});
if (Common::File::IsFileExisting(param_json)) {
Loader::SystemContentLoadParamSfo(param_json);
if (const auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
flexible_memory_size != 0) {
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
}
}
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,
@@ -180,7 +170,9 @@ void Run(const RunOptions& options) {
EXIT("ELF is required\n");
}
Init(options.config);
const auto param_json = options.app0_dir / "sce_sys" / "param.json";
Common::Subsystems subsystems(true);
Init(options.config, param_json, subsystems);
ClearDebugTextureFolder();
@@ -192,15 +184,6 @@ void Run(const RunOptions& options) {
Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/app0");
Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/hostapp");
auto param_json = options.app0_dir / "sce_sys" / "param.json";
if (Common::File::IsFileExisting(param_json)) {
Loader::SystemContentLoadParamSfo(param_json);
if (auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
flexible_memory_size != 0) {
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
}
}
MountSandboxDirs();
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
+24 -1
View File
@@ -1,7 +1,8 @@
set(KYTY_GIT_VERSION "unknown")
set(KYTY_GIT_HASH "unknown")
if(GIT_EXECUTABLE)
execute_process(
COMMAND "${GIT_EXECUTABLE}" describe --tags --always
COMMAND "${GIT_EXECUTABLE}" describe --tags --always --dirty
WORKING_DIRECTORY "${GIT_WORKING_DIRECTORY}"
OUTPUT_VARIABLE KYTY_GIT_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
@@ -11,5 +12,27 @@ if(GIT_EXECUTABLE)
if(NOT GIT_RESULT EQUAL 0)
set(KYTY_GIT_VERSION "unknown")
endif()
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-parse --short=7 HEAD
WORKING_DIRECTORY "${GIT_WORKING_DIRECTORY}"
OUTPUT_VARIABLE KYTY_GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE GIT_HASH_RESULT
ERROR_QUIET
)
if(NOT GIT_HASH_RESULT EQUAL 0)
set(KYTY_GIT_HASH "unknown")
else()
execute_process(
COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD --
WORKING_DIRECTORY "${GIT_WORKING_DIRECTORY}"
RESULT_VARIABLE GIT_DIRTY_RESULT
ERROR_QUIET
)
if(NOT GIT_DIRTY_RESULT EQUAL 0)
string(APPEND KYTY_GIT_HASH "-dirty")
endif()
endif()
endif()
configure_file("${INPUT_FILE}" "${OUTPUT_FILE}")
@@ -15,6 +15,13 @@ bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t f
enum class Pm4ProcessResult { Complete, Blocked };
enum class ContextStateOperation : uint32_t {
Clear = 0,
Push = 1,
Pop = 2,
PushClear = 3,
};
class Pm4Execution {
public:
[[nodiscard]] bool MadeProgress() const noexcept { return m_made_progress; }
@@ -49,24 +56,12 @@ public:
KYTY_CLASS_NO_COPY(CommandProcessor);
void Reset();
void ApplyContextStateOperation(ContextStateOperation operation);
void BufferInit();
void BufferFlush();
void BufferFlushAndWait();
void BufferWait();
void BeginReadbackTransaction() {
if (m_readback_active) {
EXIT("nested command-processor readback transaction\n");
}
m_readback_active = true;
}
void EndReadbackTransaction() {
if (!m_readback_active) {
EXIT("command-processor readback transaction is not active\n");
}
m_readback_active = false;
}
void BufferInit();
void BufferFlush();
void BufferFlushAndWait();
void BufferWait();
HW::Context& GetCtx() { return m_ctx; }
HW::UserConfig& GetUcfg() { return m_ucfg; }
HW::Shader& GetShCtx() { return m_sh_ctx; }
@@ -83,8 +78,7 @@ public:
uint32_t first_instance = 0);
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count, uint32_t flags);
void DrawIndexAuto(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);
uint32_t render_target_slice_offset = 0);
void DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed);
void DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
const volatile uint32_t* count_addr, uint32_t stride_in_bytes,
@@ -118,6 +112,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();
@@ -152,14 +147,19 @@ private:
uint32_t interrupt_context_id);
void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
void SuspendPm4();
void SubmitNonIndexedDraw(uint32_t vertex_count, uint32_t flags,
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(); }
RenderContext& m_renderer;
RenderContext& m_renderer;
HW::Context m_ctx;
HW::Context m_saved_ctx;
bool m_context_state_pushed = false;
HW::UserConfig m_ucfg;
HW::Shader m_sh_ctx;
HW::UserSgprType m_user_data_marker = HW::UserSgprType::Unknown;
@@ -168,12 +168,12 @@ private:
uint64_t m_index_base_addr = 0;
uint64_t m_draw_indirect_args_base_addr = 0;
uint64_t m_dispatch_indirect_args_base_addr = 0;
uint32_t m_num_instances = 1;
// Persistent draw state: indirect draws update it for subsequent draws.
uint32_t m_num_instances = 1;
uint32_t m_de_count = 0;
uint32_t m_ce_count = 0;
bool m_ce_complete = false;
bool m_readback_active = false;
uint32_t m_de_count = 0;
uint32_t m_ce_count = 0;
bool m_ce_complete = false;
uint32_t m_const_ram[0x3000] = {0};
@@ -224,6 +224,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;
@@ -254,6 +255,7 @@ constexpr auto MakeCustomOpcodeDispatchTable() {
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;
return g_cp_op_custom_func;
}
@@ -121,6 +121,7 @@ uint32_t HwUcSetGeIndexOffset(CommandProcessor&, uint32_t, uint32_t, const uint3
uint32_t HwUcSetGdsOaRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t CpOpNop(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpContextState(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpSetBase(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpClearState(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpIndexBase(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
@@ -132,6 +133,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);
File diff suppressed because it is too large. Load diff
+60 -32
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,
@@ -60,23 +53,34 @@ enum class GsOutputPrimitiveType : uint32_t {
};
enum class ChannelLayout : uint32_t {
kInvalid = 0,
k8 = 1,
k16 = 2,
k8_8 = 3,
k32 = 4,
k16_16 = 5,
k11_11_10 = 6,
k10_10_10_2 = 9,
k8_8_8_8 = 10,
k32_32 = 11,
k16_16_16_16 = 12,
k32_32_32_32 = 14,
k5_5_5_1 = 17,
k4_4_4_4 = 19,
kBc1 = 35,
kBc2 = 36,
kBc3 = 37,
kInvalid = 0,
k8 = 1,
k16 = 2,
k8_8 = 3,
k32 = 4,
k16_16 = 5,
k11_11_10 = 6,
k10_11_11 = 7,
k2_10_10_10 = 8,
k10_10_10_2 = 9,
k8_8_8_8 = 10,
k32_32 = 11,
k16_16_16_16 = 12,
k32_32_32 = 13,
k32_32_32_32 = 14,
k5_6_5 = 16,
k5_5_5_1 = 17,
k1_5_5_5 = 18,
k4_4_4_4 = 19,
k10_10_10_2Float = 31,
k9_9_9_5 = 34,
kBc1 = 35,
kBc2 = 36,
kBc3 = 37,
kBc4 = 38,
kBc5 = 39,
kBc6 = 40,
kBc7 = 41,
};
enum class ChannelType : uint32_t {
@@ -104,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,
@@ -374,10 +398,14 @@ enum class BufferFormat : uint32_t {
k32_32_32_32UInt = 75,
k32_32_32_32SInt = 76,
k32_32_32_32Float = 77,
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,
+167 -77
View File
@@ -3,126 +3,216 @@
#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 supported_sampled_texture_format;
bool unsigned_integer_texture_format;
};
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::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::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_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) {
bool IsSupportedTextureFormat(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr && info->supported_sampled_texture_format;
}
bool IsUintTextureFormat(uint32_t format) {
bool IsUintTextureFormat(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr && info->unsigned_integer_texture_format;
}
bool IsFmaskTextureFormat(uint32_t format) {
return format == GpuEnumValue(BufferFormat::kFmask8_S4_F4);
bool IsFmaskTextureFormat(BufferFormat format) {
return format == BufferFormat::kFmask8_S4_F4;
}
} // namespace Libs::Graphics::Prospero
+40 -7
View File
@@ -1,16 +1,49 @@
#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 IsSupportedTextureFormat(BufferFormat format);
bool IsUintTextureFormat(BufferFormat format);
bool IsFmaskTextureFormat(BufferFormat format);
} // namespace Libs::Graphics::Prospero
+175 -297
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"
@@ -32,11 +33,11 @@
namespace Libs::Graphics {
static thread_local CommandProcessor* g_current_processor = nullptr;
static thread_local Pm4Execution* g_current_execution = nullptr;
static thread_local uint32_t g_submission_pause_depth = 0;
static thread_local bool g_gpu_mutex_owned = false;
static thread_local bool g_gpu_thread = false;
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 GuestGpu* g_gpu_state = nullptr;
class GpuMutexLock final {
public:
@@ -59,115 +60,30 @@ 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 PauseSubmissions();
void ResumeSubmissions();
void Shutdown();
[[nodiscard]] bool IsStopping();
void SendCommand(Common::UniqueFunction<void>&& command);
void SendCommandSync(Common::UniqueFunction<void>&& command);
void SendCommandSyncWithProcessor(Common::UniqueFunction<void, CommandProcessor&>&& 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 WaitLocked();
void Enqueue(Submission submission);
void WaitForIdle();
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;
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;
};
static bool GraphicsRunDebugDumpEnabled() {
return Config::GraphicsDebugDumpEnabled() &&
Config::GetPrintfDirection() != Config::OutputDirection::Silent;
}
GpuState::~GpuState() {
GuestGpu::OwnedCmdBuffer::OwnedCmdBuffer(const uint32_t* data, uint32_t count) {
EXIT_IF(data == nullptr && count != 0);
if (count != 0) {
m_words.assign(data, data + count);
}
}
GuestGpu::GuestGpu(RenderContext& renderer): m_renderer(renderer) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
GraphicsInitJmpTables();
m_gfx_cp = std::make_unique<CommandProcessor>(renderer);
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 +100,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();
@@ -198,10 +114,26 @@ void GpuState::SendCommand(Common::UniqueFunction<void>&& command) {
Common::LockGuard lock(m_queue_mutex);
EXIT_IF(!m_accepting);
m_commands.push_back(std::move(command));
m_pending_commands.fetch_add(1, std::memory_order_release);
m_work_available.Signal();
}
void GpuState::SendCommandSync(Common::UniqueFunction<void>&& command) {
void GuestGpu::ProcessCommands() {
EXIT_IF(!IsGpuThread());
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
Common::UniqueFunction<void> command;
{
Common::LockGuard lock(m_queue_mutex);
EXIT_IF(m_commands.empty());
command = std::move(m_commands.front());
m_commands.pop_front();
EXIT_IF(m_pending_commands.fetch_sub(1, std::memory_order_acq_rel) == 0);
}
command();
}
}
void GuestGpu::SendCommandSync(Common::UniqueFunction<void>&& command) {
EXIT_IF(!command);
if (IsGpuThread()) {
command();
@@ -215,19 +147,12 @@ void GpuState::SendCommandSync(Common::UniqueFunction<void>&& command) {
done.acquire();
}
void GpuState::SendCommandSyncWithProcessor(
Common::UniqueFunction<void, CommandProcessor&>&& command) {
EXIT_IF(!command);
SendCommandSync([this, operation = std::move(command)]() mutable {
EXIT_IF(g_current_processor != nullptr);
g_current_processor = m_gfx_cp.get();
operation(*m_gfx_cp);
g_current_processor = nullptr;
});
}
void GpuState::Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
void GuestGpu::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) {
if (num_draw_dw == 0) {
return;
}
EXIT_IF(cmd_draw_buffer == nullptr);
GpuMutexLock lock(m_submission_mutex);
Submission submission;
submission.type = SubmissionType::Graphics;
@@ -240,8 +165,9 @@ void GpuState::Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t*
Enqueue(std::move(submission));
}
void GpuState::SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
void GuestGpu::SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
bool trigger_agc_interrupt_on_done) {
EXIT_IF(cmd_buffer == nullptr || num_dw == 0);
GpuMutexLock lock(m_submission_mutex);
constexpr uint32_t compute_queue_base = 0x20u;
@@ -257,7 +183,7 @@ void GpuState::SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_
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;
@@ -268,27 +194,26 @@ void GpuState::SubmitFlipPreparation(uint64_t request_id) {
Enqueue(std::move(submission));
}
void GpuState::Done() {
void GuestGpu::Done() {
GpuMutexLock lock(m_submission_mutex);
if (IsGpuThread()) {
m_gfx_cp->BufferWait();
} else {
WaitLocked();
if (!IsGpuThread()) {
WaitForIdle();
}
if (RenderDocCaptureInProgress()) {
SendCommandSync([this] {
Common::LockGuard render_lock(m_renderer.GetMutex());
RenderDocEndCapture();
});
}
m_graphics_done = true;
m_done_num++;
}
int GpuState::GetFrameNum() {
int GuestGpu::GetFrameNum() const {
return m_done_num;
}
void GpuState::WaitLocked() {
WaitForIdle();
SendCommandSync([this] { m_gfx_cp->BufferWait(); });
}
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;
@@ -301,11 +226,11 @@ CommandProcessor& GpuState::GetProcessor(uint32_t queue_id) {
}
void CommandProcessor::Reset() {
BufferWait();
m_sh_ctx.Reset();
m_ucfg.Reset();
m_ctx.Reset();
m_saved_ctx.Reset();
m_context_state_pushed = false;
m_index_type_and_size = 0;
m_index_buffer_size = 0;
m_user_data_marker = HW::UserSgprType::Unknown;
@@ -315,6 +240,30 @@ void CommandProcessor::Reset() {
std::memset(m_const_ram, 0, sizeof(m_const_ram));
}
void CommandProcessor::ApplyContextStateOperation(ContextStateOperation operation) {
switch (operation) {
case ContextStateOperation::Clear: m_ctx.Reset(); break;
case ContextStateOperation::Push:
EXIT_IF(m_context_state_pushed);
m_saved_ctx = m_ctx;
m_context_state_pushed = true;
break;
case ContextStateOperation::Pop:
EXIT_IF(!m_context_state_pushed);
m_ctx = m_saved_ctx;
m_saved_ctx = {};
m_context_state_pushed = false;
break;
case ContextStateOperation::PushClear:
EXIT_IF(m_context_state_pushed);
m_saved_ctx = m_ctx;
m_context_state_pushed = true;
m_ctx.Reset();
break;
default: EXIT("unknown context state operation: %u\n", static_cast<uint32_t>(operation));
}
}
void CommandProcessor::BufferInit() {
GetScheduler().Begin(m_ctx, m_ucfg, m_sh_ctx);
}
@@ -352,8 +301,13 @@ void CommandProcessor::WaitDeDiff(uint32_t diff) {
}
}
void CommandProcessor::WaitForRewind(bool valid) {
if (!valid) {
SuspendPm4();
}
}
void CommandProcessor::IncrementDe() {
BufferWait();
m_de_count++;
}
@@ -410,8 +364,13 @@ void CommandProcessor::WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw
const uint32_t increment = (write_control >> 16u) & 0x1u;
const uint32_t write_confirm = (write_control >> 20u) & 0x1u;
if (dst_sel != 0 && dst_sel != 2 && dst_sel != 4 && dst_sel != 5) {
EXIT("unsupported writeData destination selector 0x%02" PRIx32 "\n", dst_sel);
switch (dst_sel) {
case 0:
case 2:
case 4:
case 5:
case 6: break;
default: EXIT("unsupported writeData destination selector 0x%02" PRIx32 "\n", dst_sel);
}
EXIT_NOT_IMPLEMENTED(increment != 0);
@@ -447,7 +406,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);
@@ -464,6 +422,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);
@@ -486,7 +451,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);
@@ -495,18 +460,19 @@ 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;
g_gpu_state = gpu;
for (;;) {
Submission submission;
@@ -527,6 +493,7 @@ void GpuState::ThreadRun(void* data) {
} else if (!gpu->m_commands.empty()) {
command = std::move(gpu->m_commands.front());
gpu->m_commands.pop_front();
EXIT_IF(gpu->m_pending_commands.fetch_sub(1, std::memory_order_acq_rel) == 0);
gpu->m_processing = true;
} else {
int selected_queue = -1;
@@ -558,6 +525,7 @@ void GpuState::ThreadRun(void* data) {
}
if (should_stop) {
gpu->m_gfx_cp->BufferWait();
g_gpu_state = nullptr;
g_gpu_thread = false;
return;
}
@@ -596,9 +564,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();
@@ -691,26 +663,6 @@ bool GpuState::Process(Submission& submission) {
return complete;
}
void GpuState::PauseSubmissions() {
if (g_gpu_mutex_owned) {
EXIT("GPU submissions are already paused by this thread\n");
}
g_gpu_mutex_owned = true;
m_submission_mutex.Lock();
if (!IsGpuThread()) {
WaitLocked();
}
m_renderer.GetCommandScheduler().DrainPriorityOperations();
}
void GpuState::ResumeSubmissions() {
if (!g_gpu_mutex_owned) {
EXIT("GPU submissions resumed without an active pause\n");
}
m_submission_mutex.Unlock();
g_gpu_mutex_owned = false;
}
Pm4ProcessResult CommandProcessor::Process(Pm4Execution& execution, uint32_t* buffer,
uint32_t size_dw) {
KYTY_PROFILER_BLOCK("CommandProcessor::Process");
@@ -759,6 +711,9 @@ void CommandProcessor::SuspendPm4() {
void CommandProcessor::ProcessPm4(Pm4Execution& execution, size_t stop_depth) {
while (execution.m_buffer_stack.size() > stop_depth) {
if (g_gpu_state != nullptr) {
g_gpu_state->ProcessCommands();
}
const auto buffer_index = execution.m_buffer_stack.size() - 1;
auto& cursor = execution.m_buffer_stack[buffer_index];
if (cursor.deferred_advance_dw != 0) {
@@ -962,9 +917,8 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
auto* index_addr = reinterpret_cast<const void*>(
m_index_base_addr + static_cast<uint64_t>(index_offset) * index_size);
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(),
m_index_type_and_size, index_count, index_addr,
flags, 1, m_num_instances);
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), m_index_type_and_size,
index_count, index_addr, flags, 1, m_num_instances);
}
void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed) {
@@ -1002,8 +956,9 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
args.start_vertex_location, args.start_instance_location);
}
}
DrawIndexAuto(args.vertex_count_per_instance, 0, 0, args.instance_count,
args.start_vertex_location, args.start_instance_location);
m_num_instances = args.instance_count;
SubmitNonIndexedDraw(args.vertex_count_per_instance, 0, 0, args.start_vertex_location,
args.start_instance_location);
return;
}
@@ -1043,6 +998,7 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
}
}
m_num_instances = args.instance_count;
DrawIndex(index_count, index_addr, 0, 1, args.instance_count, nullptr, 0,
static_cast<int32_t>(args.base_vertex_location), args.start_instance_location);
}
@@ -1100,8 +1056,9 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
args->start_vertex_location, args->start_instance_location);
}
}
DrawIndexAuto(args->vertex_count_per_instance, 0, 0, args->instance_count,
args->start_vertex_location, args->start_instance_location);
m_num_instances = args->instance_count;
SubmitNonIndexedDraw(args->vertex_count_per_instance, 0, 0, args->start_vertex_location,
args->start_instance_location);
continue;
}
@@ -1142,6 +1099,7 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
}
}
m_num_instances = args->instance_count;
DrawIndex(index_count, index_addr, 0, 1, args->instance_count, nullptr, 0,
static_cast<int32_t>(args->base_vertex_location), args->start_instance_location);
}
@@ -1150,9 +1108,9 @@ 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) {
uint32_t frame_num = 0;
uint32_t local_x = 1;
uint32_t local_y = 1;
uint32_t local_z = 1;
// uint32_t local_x = 1;
// uint32_t local_y = 1;
// uint32_t local_z = 1;
{
CheckBuffer();
@@ -1176,9 +1134,9 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
}
const auto& cs = m_sh_ctx.GetCs().cs_regs;
local_x = std::max(cs.num_thread_x, 1u);
local_y = std::max(cs.num_thread_y, 1u);
local_z = std::max(cs.num_thread_z, 1u);
// local_x = std::max(cs.num_thread_x, 1u);
// local_y = std::max(cs.num_thread_y, 1u);
// local_z = std::max(cs.num_thread_z, 1u);
if (cs.wave_size == 64u) {
static std::atomic_bool logged_wave64_shader {false};
if (!logged_wave64_shader.exchange(true, std::memory_order_relaxed)) {
@@ -1190,31 +1148,31 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
}
}
m_renderer.GetRenderExecutor().DispatchDirect(
m_submit_id, CurrentBuffer(), thread_group_x, thread_group_y, thread_group_z, mode);
m_renderer.GetRenderExecutor().DispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x,
thread_group_y, thread_group_z, mode);
}
constexpr uint32_t DispatchInitiatorUseThreadDimensions = 1u << 5u;
/*constexpr uint32_t DispatchInitiatorUseThreadDimensions = 1u << 5u;
auto group_count = [](uint32_t threads, uint32_t group_size) {
return (threads == 0
? 0u
: (threads + std::max(group_size, 1u) - 1u) / std::max(group_size, 1u));
return (threads == 0
? 0u
: (threads + std::max(group_size, 1u) - 1u) / std::max(group_size, 1u));
};
auto groups_x = thread_group_x;
auto groups_y = thread_group_y;
auto groups_z = thread_group_z;
if ((mode & DispatchInitiatorUseThreadDimensions) != 0) {
groups_x = group_count(thread_group_x, local_x);
groups_y = group_count(thread_group_y, local_y);
groups_z = group_count(thread_group_z, local_z);
groups_x = group_count(thread_group_x, local_x);
groups_y = group_count(thread_group_y, local_y);
groups_z = group_count(thread_group_z, local_z);
}
const uint64_t invocations =
static_cast<uint64_t>(groups_x) * groups_y * groups_z * local_x * local_y * local_z;
if (invocations != 0) {
BufferFlushAndWait();
}
BufferFlushAndWait();
}*/
}
void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) {
@@ -1233,20 +1191,25 @@ void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) {
}
void CommandProcessor::DrawIndexAuto(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) {
uint32_t render_target_slice_offset) {
SubmitNonIndexedDraw(index_count, flags, render_target_slice_offset, 0, 0);
}
void CommandProcessor::SubmitNonIndexedDraw(uint32_t vertex_count, uint32_t flags,
uint32_t render_target_slice_offset,
uint32_t first_vertex, uint32_t first_instance) {
CheckBuffer();
m_renderer.GetRenderExecutor().DrawAuto(
m_submit_id, CurrentBuffer(), index_count, flags, render_target_slice_offset,
instance_count, first_vertex, first_instance);
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), vertex_count, flags,
render_target_slice_offset, m_num_instances,
first_vertex, first_instance);
}
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
BufferFlush();
m_renderer.GetVideoOut().WaitFlipDone(static_cast<int>(video_out_handle),
static_cast<int>(display_buffer_index));
static_cast<int>(display_buffer_index));
}
template <typename T>
@@ -1317,10 +1280,13 @@ 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, value >> 16u);
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);
}
return;
}
} else if (eop_event_type == 0x04 && cache_action == 0x00 && event_index == 0x05) {
@@ -1362,9 +1328,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;
}
@@ -1486,8 +1457,7 @@ void CommandProcessor::EmitGlobalBarrier() {
barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
barrier.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite;
barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
barrier.dstAccessMask =
vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
barrier.dstAccessMask = vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
vk::DependencyInfo dependency {};
dependency.memoryBarrierCount = 1;
@@ -1524,7 +1494,6 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) {
event_index);
}
EmitGlobalBarrier();
SynchronizeGpu();
break;
// DbDataWritebackInvalidate, DbMetadataWritebackInvalidate, CbMetadataWritebackInvalidate.
case 0x0000002a:
@@ -1637,99 +1606,8 @@ void CommandProcessor::SynchronizeGpu() {
GetScheduler().FinishCurrent();
}
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::SendCommandSyncWithProcessor(Common::UniqueFunction<void, CommandProcessor&>&& command) {
m_state->SendCommandSyncWithProcessor(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();
}
void Gpu::PauseSubmissions() {
m_state->PauseSubmissions();
}
void Gpu::ResumeSubmissions() {
m_state->ResumeSubmissions();
}
Gpu::SubmissionLock::SubmissionLock(Gpu& gpu): m_gpu(gpu) {
if (g_current_processor != nullptr || g_submission_pause_depth == UINT32_MAX) {
EXIT("cannot acquire GPU submission lock in the current state\n");
}
if (g_submission_pause_depth++ == 0) {
m_gpu.PauseSubmissions();
}
}
Gpu::SubmissionLock::~SubmissionLock() {
if (g_submission_pause_depth == 0) {
EXIT("GPU submission lock released without ownership\n");
}
if (--g_submission_pause_depth == 0) {
m_gpu.ResumeSubmissions();
}
}
bool Gpu::IsCommandProcessorThread() noexcept {
return g_current_processor != nullptr;
}
CommandProcessor* Gpu::CurrentCommandProcessor() noexcept {
return g_current_processor;
}
bool Gpu::SubmissionLockHeld() noexcept {
return g_submission_pause_depth != 0;
}
bool Gpu::MutexHeld() noexcept {
return g_gpu_mutex_owned;
bool GuestGpu::IsGpuThread() noexcept {
return g_gpu_thread;
}
} // namespace Libs::Graphics
+82 -25
View File
@@ -3,27 +3,33 @@
#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 <thread>
#include <vector>
namespace Libs::Graphics {
class CommandProcessor;
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 SendCommandSyncWithProcessor(Common::UniqueFunction<void, CommandProcessor&>&& 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);
@@ -33,28 +39,79 @@ public:
void Done();
[[nodiscard]] int GetFrameNum() const;
[[nodiscard]] static bool IsCommandProcessorThread() noexcept;
[[nodiscard]] static CommandProcessor* CurrentCommandProcessor() noexcept;
[[nodiscard]] static bool SubmissionLockHeld() noexcept;
[[nodiscard]] static bool MutexHeld() noexcept;
class SubmissionLock final {
public:
explicit SubmissionLock(Gpu& gpu);
~SubmissionLock();
KYTY_CLASS_NO_COPY(SubmissionLock);
private:
Gpu& m_gpu;
};
[[nodiscard]] static bool IsGpuThread() noexcept;
private:
friend class SubmissionLock;
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;
void PauseSubmissions();
void ResumeSubmissions();
class OwnedCmdBuffer {
public:
OwnedCmdBuffer() = default;
explicit OwnedCmdBuffer(const uint32_t* data, uint32_t count);
std::unique_ptr<GpuState> m_state;
[[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;
};
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;
};
} // namespace Libs::Graphics
+116 -75
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 {
@@ -348,6 +345,16 @@ struct ModeControl {
bool persp_corr_dis = false;
};
struct PolyOffset {
int8_t neg_num_db_bits = -23;
bool db_is_float_fmt = true;
float clamp = 0.0f;
float front_scale = 0.0f;
float front_offset = 0.0f;
float back_scale = 0.0f;
float back_offset = 0.0f;
};
struct BlendControl {
uint8_t color_srcblend = 1;
uint8_t color_comb_fcn = 0;
@@ -482,6 +489,26 @@ struct VsShaderResource2 {
uint8_t shared_vgprs = 0;
};
struct HsShaderResource1 {
uint8_t vgprs = 0;
uint8_t priority = 0;
uint8_t float_mode = 0;
bool dx10_clamp = false;
bool debug_mode = false;
bool ieee_mode = false;
bool require_forward_progress = false;
bool threadgroup_configuration = false;
uint8_t ls_vgpr_component_count = 0;
bool fp16_overflow = false;
};
struct HsShaderResource2 {
bool scratch_en = false;
uint8_t user_sgpr = 0;
uint16_t lds_size = 0;
uint8_t shared_vgprs = 0;
};
struct PsShaderResource1 {
uint8_t vgprs = 0;
uint8_t sgprs = 0;
@@ -500,7 +527,7 @@ struct PsShaderResource2 {
uint8_t user_sgpr = 0;
bool wave_cnt_en = false;
uint8_t extra_lds_size = 0;
bool raster_ordered_shading = false;
uint8_t raster_ordered_shading = 0;
uint8_t shared_vgprs = 0;
};
@@ -513,22 +540,27 @@ struct PsStageRegisters {
struct CsStageRegisters {
uint64_t data_addr = 0;
uint32_t num_thread_x = 0;
uint32_t num_thread_y = 0;
uint32_t num_thread_z = 0;
uint8_t vgprs = 0;
uint8_t sgprs = 0;
uint8_t bulky = 0;
uint8_t wave_size = 64;
uint8_t scratch_en = 0;
uint8_t user_sgpr = 0;
uint8_t tgid_x_en = 0;
uint8_t tgid_y_en = 0;
uint8_t tgid_z_en = 0;
uint8_t tg_size_en = 0;
uint8_t tidig_comp_cnt = 0;
uint8_t lds_size = 0;
uint64_t data_addr = 0;
uint32_t num_thread_x = 0;
uint32_t num_thread_y = 0;
uint32_t num_thread_z = 0;
uint8_t vgprs = 0;
uint8_t sgprs = 0;
uint8_t float_mode = 0;
bool dx10_clamp = false;
bool ieee_mode = false;
bool fp16_overflow = false;
bool bulky = false;
bool threadgroup_configuration = false;
uint8_t wave_size = 64;
bool scratch_en = false;
uint8_t user_sgpr = 0;
bool tgid_x_en = false;
bool tgid_y_en = false;
bool tgid_z_en = false;
bool tg_size_en = false;
uint8_t tidig_comp_cnt = 0;
uint16_t lds_size = 0;
};
struct EsStageRegisters {
@@ -543,24 +575,24 @@ struct LsStageRegisters {
struct HsStageRegisters {
uint64_t data_addr = 0;
VsShaderResource1 rsrc1;
VsShaderResource2 rsrc2;
HsShaderResource1 rsrc1;
HsShaderResource2 rsrc2;
uint64_t chksum = 0;
};
struct GsShaderResource1 {
uint8_t vgprs = 0;
uint8_t sgprs = 0;
uint8_t priority = 0;
uint8_t float_mode = 0;
bool dx10_clamp = false;
bool debug_mode = false;
bool ieee_mode = false;
bool cu_group_enable = false;
bool require_forward_progress = false;
bool lds_configuration = false;
uint8_t gs_vgpr_component_count = 0;
bool fp16_overflow = false;
uint8_t vgprs = 0;
uint8_t sgprs = 0;
uint8_t priority = 0;
uint8_t float_mode = 0;
bool dx10_clamp = false;
bool debug_mode = false;
bool ieee_mode = false;
bool cu_group_enable = false;
bool require_forward_progress = false;
bool threadgroup_configuration = false;
uint8_t gs_vgpr_component_count = 0;
bool fp16_overflow = false;
};
struct GsShaderResource2 {
@@ -880,6 +912,8 @@ public:
void SetDepthControl(const DepthControl& control) { m_depth_control = control; }
[[nodiscard]] const ModeControl& GetModeControl() const { return m_mode_control; }
void SetModeControl(const ModeControl& control) { m_mode_control = control; }
[[nodiscard]] const PolyOffset& GetPolyOffset() const { return m_poly_offset; }
void SetPolyOffset(const PolyOffset& offset) { m_poly_offset = offset; }
[[nodiscard]] const EqaaControl& GetEqaaControl() const { return m_eqaa_control; }
void SetEqaaControl(const EqaaControl& control) { m_eqaa_control = control; }
[[nodiscard]] const StencilControl& GetStencilControl() const { return m_stencil_control; }
@@ -904,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; }
@@ -950,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;
@@ -977,6 +1014,7 @@ private:
uint8_t m_stencil_clear_value = 0;
ModeControl m_mode_control;
PolyOffset m_poly_offset;
EqaaControl m_eqaa_control;
ShaderRegisters m_sh_regs;
@@ -991,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; }
@@ -1011,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;
@@ -1034,8 +1075,8 @@ public:
void SetLsShaderResource1(const VsShaderResource1& rsrc1) { m_vs.ls_regs.rsrc1 = rsrc1; }
void SetLsShaderResource2(const VsShaderResource2& rsrc2) { m_vs.ls_regs.rsrc2 = rsrc2; }
void SetHsShaderBase(uint64_t addr) { m_vs.hs_regs.data_addr = addr; }
void SetHsShaderResource1(const VsShaderResource1& rsrc1) { m_vs.hs_regs.rsrc1 = rsrc1; }
void SetHsShaderResource2(const VsShaderResource2& rsrc2) { m_vs.hs_regs.rsrc2 = rsrc2; }
void SetHsShaderResource1(const HsShaderResource1& rsrc1) { m_vs.hs_regs.rsrc1 = rsrc1; }
void SetHsShaderResource2(const HsShaderResource2& rsrc2) { m_vs.hs_regs.rsrc2 = rsrc2; }
void SetHsShaderChksum(uint32_t value) { m_vs.hs_regs.chksum = value; }
void SetGsShaderBase(uint64_t addr) { m_vs.gs_regs.data_addr = addr; }
void SetGsShaderResource1(const GsShaderResource1& rsrc1) { m_vs.gs_regs.rsrc1 = rsrc1; }
+4 -1
View File
@@ -30,6 +30,7 @@ constexpr auto MakeRegisterNames() {
names[R_FLIP] = "R_FLIP";
names[R_RELEASE_MEM] = "R_RELEASE_MEM";
names[R_DMA_DATA] = "R_DMA_DATA";
names[R_CONTEXT_STATE] = "R_CONTEXT_STATE";
return names;
}
@@ -110,7 +111,6 @@ void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t star
auto* cmd = cmd_buffer + start_dw;
auto dw = num_dw;
while (dw != 0) {
EXIT_NOT_IMPLEMENTED(dw < 2);
EXIT_NOT_IMPLEMENTED(dw > num_dw);
auto cmd_id = *cmd++;
@@ -120,6 +120,9 @@ void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t star
uint32_t len = 0;
const auto packet_type = static_cast<PacketType>(cmd_id >> 30u);
// Type-2 packets are header-only padding; every other packet type requires a body.
EXIT_NOT_IMPLEMENTED(dw < 2 && packet_type != PacketType::Type2);
switch (packet_type) {
case PacketType::Type3: {
const bool sh_gx = (cmd_id & 0x2u) == 0;
+137 -87
View File
@@ -87,6 +87,7 @@ 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;
constexpr uint32_t R_CONTEXT_STATE = 0x1A;
constexpr uint32_t R_NUM = 0x3F + 1;
@@ -371,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;
@@ -385,6 +387,9 @@ 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;
@@ -574,24 +579,28 @@ constexpr uint32_t DB_HTILE_SURFACE_PREFETCH_HEIGHT_MASK = 0x3F;
constexpr uint32_t DB_HTILE_SURFACE_DST_OUTSIDE_ZERO_TO_ONE_SHIFT = 16;
constexpr uint32_t DB_HTILE_SURFACE_DST_OUTSIDE_ZERO_TO_ONE_MASK = 0x1;
constexpr uint32_t DB_SRESULTS_COMPARE_STATE0 = 0x2B0;
constexpr uint32_t DB_SRESULTS_COMPARE_STATE1 = 0x2B1;
constexpr uint32_t VGT_GS_MAX_VERT_OUT = 0x2CE;
constexpr uint32_t GE_NGG_SUBGRP_CNTL = 0x2D3;
constexpr uint32_t VGT_TESS_DISTRIBUTION = 0x2D4;
constexpr uint32_t VGT_SHADER_STAGES_EN = 0x2D5;
constexpr uint32_t VGT_LS_HS_CONFIG = 0x2D6;
constexpr uint32_t VGT_TF_PARAM = 0x2DB;
constexpr uint32_t DB_ALPHA_TO_MASK = 0x2DC;
constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL = 0x2DE;
constexpr uint32_t PA_SU_POLY_OFFSET_CLAMP = 0x2DF;
constexpr uint32_t PA_SU_POLY_OFFSET_FRONT_SCALE = 0x2E0;
constexpr uint32_t PA_SU_POLY_OFFSET_FRONT_OFFSET = 0x2E1;
constexpr uint32_t PA_SU_POLY_OFFSET_BACK_SCALE = 0x2E2;
constexpr uint32_t PA_SU_POLY_OFFSET_BACK_OFFSET = 0x2E3;
constexpr uint32_t VGT_GS_INSTANCE_CNT = 0x2E4;
constexpr uint32_t PA_SC_CENTROID_PRIORITY_0 = 0x2F5;
constexpr uint32_t PA_SC_CENTROID_PRIORITY_1 = 0x2F6;
constexpr uint32_t DB_SRESULTS_COMPARE_STATE0 = 0x2B0;
constexpr uint32_t DB_SRESULTS_COMPARE_STATE1 = 0x2B1;
constexpr uint32_t VGT_GS_MAX_VERT_OUT = 0x2CE;
constexpr uint32_t GE_NGG_SUBGRP_CNTL = 0x2D3;
constexpr uint32_t VGT_TESS_DISTRIBUTION = 0x2D4;
constexpr uint32_t VGT_SHADER_STAGES_EN = 0x2D5;
constexpr uint32_t VGT_LS_HS_CONFIG = 0x2D6;
constexpr uint32_t VGT_TF_PARAM = 0x2DB;
constexpr uint32_t DB_ALPHA_TO_MASK = 0x2DC;
constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL = 0x2DE;
constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL_NEG_NUM_DB_BITS_SHIFT = 0;
constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL_NEG_NUM_DB_BITS_MASK = 0xFF;
constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL_DB_IS_FLOAT_FMT_SHIFT = 8;
constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL_DB_IS_FLOAT_FMT_MASK = 0x1;
constexpr uint32_t PA_SU_POLY_OFFSET_CLAMP = 0x2DF;
constexpr uint32_t PA_SU_POLY_OFFSET_FRONT_SCALE = 0x2E0;
constexpr uint32_t PA_SU_POLY_OFFSET_FRONT_OFFSET = 0x2E1;
constexpr uint32_t PA_SU_POLY_OFFSET_BACK_SCALE = 0x2E2;
constexpr uint32_t PA_SU_POLY_OFFSET_BACK_OFFSET = 0x2E3;
constexpr uint32_t VGT_GS_INSTANCE_CNT = 0x2E4;
constexpr uint32_t PA_SC_CENTROID_PRIORITY_0 = 0x2F5;
constexpr uint32_t PA_SC_CENTROID_PRIORITY_1 = 0x2F6;
constexpr uint32_t PA_SC_AA_CONFIG = 0x2F8;
constexpr uint32_t PA_SC_AA_CONFIG_MSAA_NUM_SAMPLES_SHIFT = 0;
@@ -775,21 +784,21 @@ constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_FWD_PROGRESS_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_FP16_OVFL_SHIFT = 29;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_FP16_OVFL_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS = 0xB;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SCRATCH_EN_SHIFT = 0;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SCRATCH_EN_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_SHIFT = 1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_MASK = 0x1F;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_WAVE_CNT_EN_SHIFT = 7;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_WAVE_CNT_EN_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_EXTRA_LDS_SIZE_SHIFT = 8;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_EXTRA_LDS_SIZE_MASK = 0xFF;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_LOAD_INTRAWAVE_COLLISION_SHIFT = 26;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_LOAD_INTRAWAVE_COLLISION_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_MSB_SHIFT = 27;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_MSB_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SHARED_VGPR_CNT_SHIFT = 28;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SHARED_VGPR_CNT_MASK = 0xF;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS = 0xB;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SCRATCH_EN_SHIFT = 0;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SCRATCH_EN_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_SHIFT = 1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_MASK = 0x1F;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_WAVE_CNT_EN_SHIFT = 7;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_WAVE_CNT_EN_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_EXTRA_LDS_SIZE_SHIFT = 8;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_EXTRA_LDS_SIZE_MASK = 0xFF;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_RASTER_ORDERED_SHADING_SHIFT = 25;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_RASTER_ORDERED_SHADING_MASK = 0x3;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_MSB_SHIFT = 27;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_MSB_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SHARED_VGPR_CNT_SHIFT = 28;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SHARED_VGPR_CNT_MASK = 0xF;
constexpr uint32_t SPI_SHADER_USER_DATA_PS_0 = 0xC;
constexpr uint32_t SPI_SHADER_USER_DATA_PS_15 = 0x1B;
@@ -882,61 +891,101 @@ constexpr uint32_t SPI_SHADER_PGM_RSRC2_GS_USER_SGPR_MSB_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_GS_SHARED_VGPR_CNT_SHIFT = 28;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_GS_SHARED_VGPR_CNT_MASK = 0xF;
constexpr uint32_t SPI_SHADER_USER_DATA_GS_0 = 0x8C;
constexpr uint32_t SPI_SHADER_USER_DATA_GS_15 = 0x9B;
constexpr uint32_t SPI_SHADER_USER_DATA_GS_31 = 0xAB;
constexpr uint32_t SPI_SHADER_REQ_CTRL_ESGS = 0xB0;
constexpr uint32_t SPI_SHADER_USER_ACCUM_ESGS_0 = 0xB2;
constexpr uint32_t SPI_SHADER_USER_ACCUM_ESGS_3 = 0xB5;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_ES_GS = 0xBC;
constexpr uint32_t SPI_SHADER_PGM_LO_ES = 0xC8;
constexpr uint32_t SPI_SHADER_PGM_HI_ES = 0xC9;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_ES = 0xCA;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_ES = 0xCB;
constexpr uint32_t SPI_SHADER_USER_DATA_ES_0 = 0xCC;
constexpr uint32_t SPI_SHADER_USER_DATA_ES_15 = 0xDB;
constexpr uint32_t SPI_SHADER_USER_DATA_ES_31 = 0xEB;
constexpr uint32_t SPI_SHADER_PGM_CHKSUM_HS = 0x100;
constexpr uint32_t SPI_SHADER_PGM_RSRC4_HS = 0x101;
constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_LO_HS = 0x102;
constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_HI_HS = 0x103;
constexpr uint32_t SPI_SHADER_PGM_RSRC3_HS = 0x107;
constexpr uint32_t SPI_SHADER_PGM_LO_HS = 0x108;
constexpr uint32_t SPI_SHADER_PGM_HI_HS = 0x109;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS = 0x10A;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS = 0x10B;
constexpr uint32_t SPI_SHADER_USER_DATA_HS_0 = 0x10C;
constexpr uint32_t SPI_SHADER_USER_DATA_HS_15 = 0x11B;
constexpr uint32_t SPI_SHADER_USER_DATA_HS_31 = 0x12B;
constexpr uint32_t SPI_SHADER_REQ_CTRL_LSHS = 0x130;
constexpr uint32_t SPI_SHADER_USER_ACCUM_LSHS_0 = 0x132;
constexpr uint32_t SPI_SHADER_USER_ACCUM_LSHS_3 = 0x135;
constexpr uint32_t SPI_SHADER_PGM_LO_LS = 0x148;
constexpr uint32_t SPI_SHADER_PGM_HI_LS = 0x149;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_LS = 0x14A;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_LS = 0x14B;
constexpr uint32_t COMPUTE_START_X = 0x204;
constexpr uint32_t COMPUTE_START_Y = 0x205;
constexpr uint32_t COMPUTE_START_Z = 0x206;
constexpr uint32_t COMPUTE_NUM_THREAD_X = 0x207;
constexpr uint32_t COMPUTE_NUM_THREAD_Y = 0x208;
constexpr uint32_t COMPUTE_NUM_THREAD_Z = 0x209;
constexpr uint32_t COMPUTE_PGM_LO = 0x20C;
constexpr uint32_t COMPUTE_PGM_HI = 0x20D;
constexpr uint32_t COMPUTE_TBA_LO = 0x20E;
constexpr uint32_t COMPUTE_TBA_HI = 0x20F;
constexpr uint32_t COMPUTE_TMA_LO = 0x210;
constexpr uint32_t COMPUTE_TMA_HI = 0x211;
constexpr uint32_t SPI_SHADER_USER_DATA_GS_0 = 0x8C;
constexpr uint32_t SPI_SHADER_USER_DATA_GS_15 = 0x9B;
constexpr uint32_t SPI_SHADER_USER_DATA_GS_31 = 0xAB;
constexpr uint32_t SPI_SHADER_REQ_CTRL_ESGS = 0xB0;
constexpr uint32_t SPI_SHADER_USER_ACCUM_ESGS_0 = 0xB2;
constexpr uint32_t SPI_SHADER_USER_ACCUM_ESGS_3 = 0xB5;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_ES_GS = 0xBC;
constexpr uint32_t SPI_SHADER_PGM_LO_ES = 0xC8;
constexpr uint32_t SPI_SHADER_PGM_HI_ES = 0xC9;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_ES = 0xCA;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_ES = 0xCB;
constexpr uint32_t SPI_SHADER_USER_DATA_ES_0 = 0xCC;
constexpr uint32_t SPI_SHADER_USER_DATA_ES_15 = 0xDB;
constexpr uint32_t SPI_SHADER_USER_DATA_ES_31 = 0xEB;
constexpr uint32_t SPI_SHADER_PGM_CHKSUM_HS = 0x100;
constexpr uint32_t SPI_SHADER_PGM_RSRC4_HS = 0x101;
constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_LO_HS = 0x102;
constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_HI_HS = 0x103;
constexpr uint32_t SPI_SHADER_PGM_RSRC3_HS = 0x107;
constexpr uint32_t SPI_SHADER_PGM_LO_HS = 0x108;
constexpr uint32_t SPI_SHADER_PGM_HI_HS = 0x109;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS = 0x10A;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS = 0x10B;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_VGPRS_SHIFT = 0;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_VGPRS_MASK = 0x3F;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_PRIORITY_SHIFT = 10;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_PRIORITY_MASK = 0x3;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FLOAT_MODE_SHIFT = 12;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FLOAT_MODE_MASK = 0xFF;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_DX10_CLAMP_SHIFT = 21;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_DX10_CLAMP_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_DEBUG_MODE_SHIFT = 22;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_DEBUG_MODE_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_IEEE_MODE_SHIFT = 23;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_IEEE_MODE_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FWD_PROGRESS_SHIFT = 25;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FWD_PROGRESS_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_WGP_MODE_SHIFT = 26;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_WGP_MODE_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_LS_VGPR_COMP_CNT_SHIFT = 28;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_LS_VGPR_COMP_CNT_MASK = 0x3;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FP16_OVFL_SHIFT = 30;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FP16_OVFL_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_SCRATCH_EN_SHIFT = 0;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_SCRATCH_EN_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_USER_SGPR_SHIFT = 1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_USER_SGPR_MASK = 0x1F;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_LDS_SIZE_SHIFT = 18;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_LDS_SIZE_MASK = 0x1FF;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_USER_SGPR_MSB_SHIFT = 27;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_USER_SGPR_MSB_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_SHARED_VGPR_CNT_SHIFT = 28;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_SHARED_VGPR_CNT_MASK = 0xF;
constexpr uint32_t SPI_SHADER_USER_DATA_HS_0 = 0x10C;
constexpr uint32_t SPI_SHADER_USER_DATA_HS_15 = 0x11B;
constexpr uint32_t SPI_SHADER_USER_DATA_HS_31 = 0x12B;
constexpr uint32_t SPI_SHADER_REQ_CTRL_LSHS = 0x130;
constexpr uint32_t SPI_SHADER_USER_ACCUM_LSHS_0 = 0x132;
constexpr uint32_t SPI_SHADER_USER_ACCUM_LSHS_3 = 0x135;
constexpr uint32_t SPI_SHADER_PGM_LO_LS = 0x148;
constexpr uint32_t SPI_SHADER_PGM_HI_LS = 0x149;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_LS = 0x14A;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_LS = 0x14B;
constexpr uint32_t COMPUTE_START_X = 0x204;
constexpr uint32_t COMPUTE_START_Y = 0x205;
constexpr uint32_t COMPUTE_START_Z = 0x206;
constexpr uint32_t COMPUTE_NUM_THREAD_X = 0x207;
constexpr uint32_t COMPUTE_NUM_THREAD_Y = 0x208;
constexpr uint32_t COMPUTE_NUM_THREAD_Z = 0x209;
constexpr uint32_t COMPUTE_PGM_LO = 0x20C;
constexpr uint32_t COMPUTE_PGM_HI = 0x20D;
constexpr uint32_t COMPUTE_TBA_LO = 0x20E;
constexpr uint32_t COMPUTE_TBA_HI = 0x20F;
constexpr uint32_t COMPUTE_TMA_LO = 0x210;
constexpr uint32_t COMPUTE_TMA_HI = 0x211;
constexpr uint32_t COMPUTE_PGM_RSRC1 = 0x212;
constexpr uint32_t COMPUTE_PGM_RSRC1_VGPRS_SHIFT = 0;
constexpr uint32_t COMPUTE_PGM_RSRC1_VGPRS_MASK = 0x3F;
constexpr uint32_t COMPUTE_PGM_RSRC1_SGPRS_SHIFT = 6;
constexpr uint32_t COMPUTE_PGM_RSRC1_SGPRS_MASK = 0xF;
constexpr uint32_t COMPUTE_PGM_RSRC1_BULKY_SHIFT = 24;
constexpr uint32_t COMPUTE_PGM_RSRC1_BULKY_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_PGM_RSRC1 = 0x212;
constexpr uint32_t COMPUTE_PGM_RSRC1_VGPRS_SHIFT = 0;
constexpr uint32_t COMPUTE_PGM_RSRC1_VGPRS_MASK = 0x3F;
constexpr uint32_t COMPUTE_PGM_RSRC1_SGPRS_SHIFT = 6;
constexpr uint32_t COMPUTE_PGM_RSRC1_SGPRS_MASK = 0xF;
constexpr uint32_t COMPUTE_PGM_RSRC1_FLOAT_MODE_SHIFT = 12;
constexpr uint32_t COMPUTE_PGM_RSRC1_FLOAT_MODE_MASK = 0xFF;
constexpr uint32_t COMPUTE_PGM_RSRC1_DX10_CLAMP_SHIFT = 21;
constexpr uint32_t COMPUTE_PGM_RSRC1_DX10_CLAMP_MASK = 0x1;
constexpr uint32_t COMPUTE_PGM_RSRC1_IEEE_MODE_SHIFT = 23;
constexpr uint32_t COMPUTE_PGM_RSRC1_IEEE_MODE_MASK = 0x1;
constexpr uint32_t COMPUTE_PGM_RSRC1_BULKY_SHIFT = 24;
constexpr uint32_t COMPUTE_PGM_RSRC1_BULKY_MASK = 0x1;
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_PGM_RSRC2 = 0x213;
constexpr uint32_t COMPUTE_PGM_RSRC2_SCRATCH_EN_SHIFT = 0;
@@ -999,6 +1048,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;
File diff suppressed because it is too large. Load diff
+72 -39
View File
@@ -3,6 +3,7 @@
#include "common/abi.h"
#include "common/common.h"
#include "graphics/guest_gpu/gpu_defs.h"
namespace Libs::Graphics {
@@ -39,7 +40,7 @@ enum class TileBlockFamily : uint32_t {
};
struct TileBlockLayout {
TileBlockFamily family = TileBlockFamily::Standard256B;
TileBlockFamily family = TileBlockFamily::Count;
uint32_t bytes_per_element = 0;
uint32_t block_size = 0;
uint32_t block_width = 0;
@@ -47,60 +48,92 @@ struct TileBlockLayout {
uint32_t block_depth = 0;
};
struct TileVolumeLayout {
TileBlockFamily family = TileBlockFamily::Count;
uint32_t bytes_per_element = 0;
uint32_t texel_width = 1;
uint32_t texel_height = 1;
uint32_t first_tail_level = 0;
uint32_t block_depth = 1;
uint64_t block_slice_size = 0;
uint64_t total_size = 0;
uint64_t level_offsets[16] = {};
uint64_t level_sizes[16] = {};
uint32_t tail_x[16] = {};
uint32_t tail_y[16] = {};
uint32_t level_widths[16] = {};
uint32_t level_heights[16] = {};
struct TileTextureElementLayout {
uint32_t bytes = 0;
uint32_t texel_width = 1;
uint32_t texel_height = 1;
};
struct TileTextureBlockLayout {
TileBlockLayout block;
uint32_t texel_width = 1;
uint32_t texel_height = 1;
};
enum class TileSurfaceDimension : uint32_t {
Dim2D,
Dim3D,
};
struct TileSurfaceDescription {
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.
struct TileMipLayout {
uint64_t offset = 0;
uint64_t size = 0;
uint32_t width = 0;
uint32_t height = 0;
uint32_t padded_width = 0;
uint32_t padded_height = 0;
uint32_t tail_x = 0;
uint32_t tail_y = 0;
};
struct TileSurfaceLayout {
TileSurfaceDescription description;
TileTextureBlockLayout texture;
uint32_t first_tail_level = 0;
uint64_t block_slice_size = 0;
uint64_t total_size = 0;
TileMipLayout mips[16] = {};
};
bool TileGetBlockLayout(TileBlockFamily family, uint32_t bytes_per_element,
TileBlockLayout& layout);
TileBlockLayout& layout);
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);
bool TileGetBlockOffset(const TileBlockLayout& layout, uint32_t x, uint32_t y, uint32_t z,
uint32_t& byte_offset);
uint32_t& byte_offset);
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
uint32_t& byte_offset);
uint32_t& byte_offset);
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
uint32_t block_z, uint32_t& byte_offset);
bool TileIsStandard256BTextureSupported(uint32_t format);
bool TileIsStandard4KBTextureSupported(uint32_t format);
bool TileIsStandard64KBTextureSupported(uint32_t format);
bool TileGetTextureVolumeLayout(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
uint32_t levels, uint32_t tile, TileVolumeLayout& layout);
uint32_t block_z, uint32_t& byte_offset);
bool TileGetHtileSize(uint32_t width, uint32_t height, TileSizeAlign& htile_size);
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,
uint32_t num_fragments_log2 = 0);
bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
uint32_t bytes_per_element, TileSizeAlign& total_size,
uint32_t bytes_per_element, TileSizeAlign& total_size,
uint32_t num_fragments_log2 = 0);
bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
uint32_t bytes_per_element, uint32_t levels,
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
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 pitch,
uint32_t levels, uint32_t tile, TileSizeAlign* total_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);
void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
uint32_t pitch, uint32_t levels, uint32_t tile, bool volume_texture,
TileSizeAlign& total_size);
uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t levels, uint32_t tile);
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
+12 -12
View File
@@ -60,19 +60,19 @@ struct VulkanImage {
VulkanImage() = default;
KYTY_CLASS_NO_COPY(VulkanImage);
vk::Format format = vk::Format::eUndefined;
vk::ImageType image_type = vk::ImageType::e2D;
vk::Extent3D extent = {1, 1, 1};
uint32_t guest_pitch = 0;
uint32_t layers = 1;
uint32_t mip_levels = 1;
uint32_t samples = 1;
vk::ImageUsageFlags usage = {};
vk::ImageCreateFlags flags = {};
vk::Image image = nullptr;
VulkanImageState state;
vk::Format format = vk::Format::eUndefined;
vk::ImageType image_type = vk::ImageType::e2D;
vk::Extent3D extent = {1, 1, 1};
uint32_t guest_pitch = 0;
uint32_t layers = 1;
uint32_t mip_levels = 1;
uint32_t samples = 1;
vk::ImageUsageFlags usage = {};
vk::ImageCreateFlags flags = {};
vk::Image image = nullptr;
VulkanImageState state;
std::vector<VulkanImageState> subresource_states;
Graphics::VulkanMemory memory;
Graphics::VulkanMemory memory;
};
struct VulkanBuffer {
+1 -1
View File
@@ -30,7 +30,7 @@ bool IsAccessible(DWORD protect, HostMemoryAccess access) {
} // namespace
bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAccess access,
uint64_t& accessible_size) {
uint64_t& accessible_size) {
accessible_size = 0;
if (addr == 0 || requested_size == 0) {
return false;
+1 -1
View File
@@ -8,7 +8,7 @@ namespace Libs::Graphics {
enum class HostMemoryAccess { Read, Mapped };
bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAccess access,
uint64_t& accessible_size);
uint64_t& accessible_size);
bool HostMemoryQueryReadable(uint64_t addr, uint64_t requested_size, uint64_t& readable_size);
bool HostMemoryIsReadable(uint64_t addr);
bool HostMemoryRangeIsReadable(uint64_t addr, uint64_t size);
+11 -155
View File
@@ -4,25 +4,9 @@
namespace Libs::Graphics {
#if defined(KYTY_MEMORY_TRACKER_TESTS)
namespace {
std::atomic<MemoryTracker::UnmapContentionHook> g_unmap_contention_hook {nullptr};
}
void MemoryTracker::SetUnmapContentionHook(UnmapContentionHook hook) noexcept {
g_unmap_contention_hook.store(hook, std::memory_order_release);
}
#endif
static_assert(std::atomic<void*>::is_always_lock_free);
MemoryTracker::MemoryTracker(PageManager& page_manager, PageWatchMode gpu_watch_mode)
: m_page_manager(page_manager), m_gpu_watch_mode(gpu_watch_mode) {
switch (m_gpu_watch_mode) {
case PageWatchMode::Write:
case PageWatchMode::ReadWrite: break;
default: EXIT("unsupported memory tracker GPU page-watch mode\n");
}
MemoryTracker::MemoryTracker(PageManager& page_manager): m_page_manager(page_manager) {
m_regions = std::make_unique<std::atomic<RegionManager*>[]>(REGION_COUNT);
for (size_t i = 0; i < REGION_COUNT; i++) {
m_regions[i].store(nullptr, std::memory_order_relaxed);
@@ -31,6 +15,7 @@ MemoryTracker::MemoryTracker(PageManager& page_manager, PageWatchMode gpu_watch_
MemoryTracker::~MemoryTracker() = default;
#if KYTY_BUILD == KYTY_BUILD_DEBUG
void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation) const noexcept {
if (vaddr == 0 || size == 0 || size > UINT64_MAX - vaddr ||
@@ -68,6 +53,7 @@ void MemoryTracker::ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t va
}
}
}
#endif
void MemoryTracker::ValidateRange(uint64_t vaddr, uint64_t size) {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
@@ -93,8 +79,6 @@ RegionManager* MemoryTracker::GetOrCreateRegion(uint64_t index) {
bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
RequireMapped(vaddr, size);
return Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
return manager->IsModified<DirtySource::Cpu>(offset, bytes);
@@ -103,8 +87,6 @@ bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
RequireMapped(vaddr, size);
return Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
return manager->IsModified<DirtySource::Gpu>(offset, bytes);
@@ -113,46 +95,29 @@ bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
RequireMapped(vaddr, size);
Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
const auto changed =
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
manager->ApplyProtection(changed, false);
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
});
}
void MemoryTracker::MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
RequireMapped(vaddr, size);
Iterate<true>(vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) {
Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
const auto changed =
manager->ChangeState<DirtySource::Gpu, true>(manager->GetCpuAddr() + offset, bytes);
manager->ApplyGpuProtection(changed, true, m_gpu_watch_mode);
manager->ChangeState<DirtySource::Gpu, true>(manager->GetCpuAddr() + offset, bytes);
});
}
void MemoryTracker::UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
RequireMapped(vaddr, size);
Iterate<true>(vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) {
Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
if (!manager->IsFullyModified<DirtySource::Gpu>(offset, bytes)) {
EXIT("cannot clear partially GPU-dirty tracking range\n");
}
const auto changed =
manager->ChangeState<DirtySource::Gpu, false>(manager->GetCpuAddr() + offset, bytes);
manager->ApplyGpuProtection(changed, false, m_gpu_watch_mode);
manager->ChangeState<DirtySource::Gpu, false>(manager->GetCpuAddr() + offset, bytes);
});
}
void MemoryTracker::UntrackMemoryLocked(uint64_t vaddr, uint64_t size) {
RequireMapped(vaddr, size);
void MemoryTracker::UntrackMemoryImpl(uint64_t vaddr, uint64_t size) {
std::vector<RegionManager*> managers;
managers.reserve((vaddr % TRACKER_REGION_SIZE + size + TRACKER_REGION_SIZE - 1) /
TRACKER_REGION_SIZE);
@@ -171,123 +136,14 @@ void MemoryTracker::UntrackMemoryLocked(uint64_t vaddr, uint64_t size) {
EXIT("cannot untrack GPU-dirty memory\n");
}
Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
const auto changed =
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
manager->ApplyProtection(changed, false);
manager->Untrack(manager->GetCpuAddr() + offset, bytes);
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
});
locks.clear();
}
void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
UntrackMemoryLocked(vaddr, size);
}
void MemoryTracker::UnmapMemory(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
std::unique_lock access(m_access_mutex, std::try_to_lock);
if (!access.owns_lock()) {
#if defined(KYTY_MEMORY_TRACKER_TESTS)
if (const auto hook = g_unmap_contention_hook.load(std::memory_order_acquire);
hook != nullptr) {
hook();
}
#endif
access.lock();
}
UntrackMemoryLocked(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size);
}
bool MemoryTracker::InvalidateRegion(uint64_t vaddr, uint64_t size, PageFaultPhase phase) noexcept {
switch (phase) {
case PageFaultPhase::Release: return true;
case PageFaultPhase::Invalidate: {
const auto action = BeginCpuFault(vaddr, size);
switch (action) {
case CpuFaultAction::Untracked: return false;
case CpuFaultAction::Continue: return true;
case CpuFaultAction::Download:
EXIT("generic region invalidation cannot download GPU-dirty memory\n");
}
}
case PageFaultPhase::Complete:
return CompleteCpuFault(vaddr, size, PageFaultAccess::Write, false);
}
EXIT("unsupported region invalidation phase\n");
}
bool MemoryTracker::InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept {
switch (phase) {
case PageFaultPhase::Release: return true;
case PageFaultPhase::Invalidate: {
const bool gpu_modified = Iterate<false>(
vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
return manager->IsModified<DirtySource::Gpu>(offset, bytes);
});
if (!gpu_modified) {
return false;
}
const auto action = BeginCpuFault(vaddr, size);
if (access != PageFaultAccess::Write || action != CpuFaultAction::Download) {
EXIT("virtual GPU write fault requires write access to GPU-dirty memory\n");
}
return true;
}
case PageFaultPhase::Complete: {
if (access != PageFaultAccess::Write) {
EXIT("virtual GPU write completion requires write access\n");
}
bool completed = false;
Iterate<false>(
vaddr, size, [&completed](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
if (completed) {
EXIT("virtual GPU write fault spans multiple tracked regions\n");
}
completed =
manager->CompleteVirtualGpuWrite(manager->GetCpuAddr() + offset, bytes);
});
return completed;
}
}
EXIT("unsupported virtual GPU write invalidation phase\n");
}
CpuFaultAction MemoryTracker::BeginCpuFault(uint64_t vaddr, uint64_t size,
PageFaultAccess access) noexcept {
CheckNotInUploadCallback();
CpuFaultAction action = CpuFaultAction::Untracked;
Iterate<false>(
vaddr, size, [&action, access](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
if (action != CpuFaultAction::Untracked) {
EXIT("CPU fault spans multiple tracked regions\n");
}
action = manager->BeginCpuFault(manager->GetCpuAddr() + offset, bytes, access);
});
return action;
}
bool MemoryTracker::CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
bool downloaded) noexcept {
CheckNotInUploadCallback();
bool found = false;
Iterate<false>(
vaddr, size,
[&found, access, downloaded](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
if (found) {
EXIT("CPU fault completion spans multiple tracked regions\n");
}
found = manager->CompleteCpuFault(manager->GetCpuAddr() + offset, bytes, access,
downloaded);
});
return found;
UntrackMemoryImpl(vaddr, size);
}
} // namespace Libs::Graphics
+40 -48
View File
@@ -18,8 +18,7 @@ namespace Libs::Graphics {
class MemoryTracker final {
public:
explicit MemoryTracker(PageManager& page_manager,
PageWatchMode gpu_watch_mode = PageWatchMode::ReadWrite);
explicit MemoryTracker(PageManager& page_manager);
~MemoryTracker();
KYTY_CLASS_NO_COPY(MemoryTracker);
@@ -30,28 +29,45 @@ public:
void MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
void UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
void UntrackMemory(uint64_t vaddr, uint64_t size);
void UnmapMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] CpuFaultAction
BeginCpuFault(uint64_t vaddr, uint64_t size,
PageFaultAccess access = PageFaultAccess::Write) noexcept;
[[nodiscard]] bool CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
bool downloaded) noexcept;
[[nodiscard]] bool InvalidateRegion(uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept;
[[nodiscard]] bool InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr,
uint64_t size, PageFaultPhase phase) noexcept;
// Removes protection from a range and flushes GPU-owned data when required.
template <typename Flush>
void InvalidateRegion(uint64_t vaddr, uint64_t size, Flush&& on_flush) noexcept {
static_assert(std::is_invocable_v<Flush&>);
CheckNotInUploadCallback();
ValidateRange(vaddr, size);
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
const bool should_flush = [&] {
// Perform both the GPU modification check and CPU state change with the lock in
// case the GPU thread is racing to mark the page modified. If a flush is needed,
// on_flush performs the CPU state change.
std::scoped_lock lock(manager->lock);
if (manager->IsModified<DirtySource::Gpu>(offset, bytes)) {
return true;
}
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
return false;
}();
if (should_flush) {
on_flush();
}
});
}
#if KYTY_BUILD == KYTY_BUILD_DEBUG
void ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation) const noexcept;
void ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation);
#else
void ValidateGpuDirtyPages(const RangeSet&, uint64_t, uint64_t, const char*) const noexcept {}
void ValidateGpuDirtyOwnership(const RangeSet&, uint64_t, uint64_t, const char*) {}
#endif
template <bool clear, typename Preflight, typename Func>
void ForEachDownloadRange(uint64_t vaddr, uint64_t size, Preflight&& preflight, Func&& func) {
static_assert(std::is_nothrow_invocable_v<Preflight&, uint64_t, uint64_t>);
static_assert(std::is_nothrow_invocable_v<Func&, uint64_t, uint64_t>);
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
RequireMapped(vaddr, size);
std::vector<RegionManager*> managers;
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) {
managers.push_back(manager);
@@ -63,9 +79,6 @@ public:
}
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
const auto address = manager->GetCpuAddr() + offset;
if (manager->HasPendingFault(address, bytes)) {
EXIT("GPU download synchronization raced a pending CPU fault\n");
}
manager->template ForEachModifiedRange<DirtySource::Gpu, false>(address, bytes,
preflight);
});
@@ -77,10 +90,8 @@ public:
Iterate<false>(vaddr, size,
[&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
const auto address = manager->GetCpuAddr() + offset;
const auto changed =
manager->template ForEachModifiedRange<DirtySource::Gpu, true>(
address, bytes, [](uint64_t, uint64_t) noexcept {});
manager->ApplyGpuProtection(changed, false, m_gpu_watch_mode);
manager->template ForEachModifiedRange<DirtySource::Gpu, true>(
address, bytes, [](uint64_t, uint64_t) noexcept {});
});
}
}
@@ -91,24 +102,16 @@ public:
vaddr, size, [](uint64_t, uint64_t) noexcept {}, std::forward<Func>(func));
}
#if defined(KYTY_MEMORY_TRACKER_TESTS)
using UnmapContentionHook = void (*)() noexcept;
static void SetUnmapContentionHook(UnmapContentionHook hook) noexcept;
#endif
template <typename RangeFunc, typename UploadFunc>
void ForEachUploadRange(uint64_t vaddr, uint64_t size, bool is_written, RangeFunc&& range_func,
UploadFunc&& upload_func) {
static_assert(std::is_nothrow_invocable_v<RangeFunc&, uint64_t, uint64_t>);
static_assert(std::is_nothrow_invocable_v<UploadFunc&>);
CheckNotInUploadCallback();
std::unique_lock access(m_access_mutex);
RequireMapped(vaddr, size);
Iterate<true>(vaddr, size, [](RegionManager*, uint64_t, uint64_t) {});
const auto* previous_upload_owner = std::exchange(s_upload_owner, this);
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
manager->lock.lock();
manager->Track(manager->GetCpuAddr() + offset, bytes);
manager->ForEachModifiedRange<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset,
bytes, range_func);
if (!is_written) {
@@ -117,13 +120,12 @@ public:
});
upload_func();
if (is_written) {
Iterate<false>(
vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) {
const auto changed = manager->template ChangeState<DirtySource::Gpu, true>(
manager->GetCpuAddr() + offset, bytes);
manager->ApplyGpuProtection(changed, true, m_gpu_watch_mode);
manager->lock.unlock();
});
Iterate<false>(vaddr, size,
[](RegionManager* manager, uint64_t offset, uint64_t bytes) {
manager->template ChangeState<DirtySource::Gpu, true>(
manager->GetCpuAddr() + offset, bytes);
manager->lock.unlock();
});
}
s_upload_owner = previous_upload_owner;
}
@@ -168,24 +170,14 @@ private:
return false;
}
static void ValidateRange(uint64_t vaddr, uint64_t size);
void UntrackMemoryLocked(uint64_t vaddr, uint64_t size);
void RequireMapped(uint64_t vaddr, uint64_t size) const {
ValidateRange(vaddr, size);
if (!m_page_manager.IsMapped(vaddr, size)) {
EXIT("memory tracker range [0x%llx, 0x%llx) is not mapped\n",
static_cast<unsigned long long>(vaddr),
static_cast<unsigned long long>(vaddr + size));
}
}
static void ValidateRange(uint64_t vaddr, uint64_t size);
void UntrackMemoryImpl(uint64_t vaddr, uint64_t size);
RegionManager* GetOrCreateRegion(uint64_t index);
std::unique_ptr<std::atomic<RegionManager*>[]> m_regions;
std::vector<std::unique_ptr<RegionManager>> m_region_storage;
std::mutex m_region_mutex;
std::mutex m_access_mutex;
PageManager& m_page_manager;
PageWatchMode m_gpu_watch_mode = PageWatchMode::ReadWrite;
};
} // namespace Libs::Graphics
@@ -1,633 +0,0 @@
#include "graphics/host_gpu/objects/textureCommon.h"
#include "common/assert.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/host_gpu/renderer/tiler.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <bit>
#include <cinttypes>
#include <cstring>
namespace Libs::Graphics {
namespace {
struct RenderTargetFormatMapping {
Prospero::ChannelLayout layout;
Prospero::ChannelType type;
Prospero::ChannelOrder order;
RenderTargetFormatInfo info;
};
constexpr RenderTargetFormatMapping kRenderTargetFormats[] = {
{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::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::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}},
};
} // 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;
}
}
EXIT("unsupported render-target format combination: layout=%u type=%u order=%u\n", raw_layout,
raw_type, raw_order);
}
namespace {
static uint64_t GetLevelSrcOffset(const TileSizeOffset& level_size) {
return (level_size.src_size != 0 ? level_size.src_offset : level_size.offset);
}
static uint64_t GetLevelSrcSize(const TileSizeOffset& level_size) {
return (level_size.src_size != 0 ? level_size.src_size : level_size.size);
}
static uint32_t GetTextureLevelDepth(uint32_t depth, uint32_t level, bool volume_texture) {
return volume_texture ? std::max(depth >> level, 1u) : depth;
}
static size_t GetTextureRegionCount(uint32_t depth, uint64_t levels, bool volume_texture) {
size_t count = 0;
for (uint32_t level = 0; level < levels; level++) {
count += GetTextureLevelDepth(depth, level, volume_texture);
}
return count;
}
uint64_t TextureUploadSliceSourceOffset(const TextureUploadLayout& layout, uint32_t level,
uint32_t slice) {
if (level >= 16 || layout.level_sizes[level].size == 0) {
EXIT("invalid texture upload slice source, level=%u slice=%u\n", level, slice);
}
const auto level_offset = GetLevelSrcOffset(layout.level_sizes[level]);
const auto slice_stride =
layout.source_slice_stride != 0 ? layout.source_slice_stride : layout.slice_stride;
if (slice_stride != 0 && slice > (UINT64_MAX - level_offset) / slice_stride) {
EXIT("texture upload slice source offset overflow, level=%u slice=%u\n", level, slice);
}
return level_offset + static_cast<uint64_t>(slice) * slice_stride;
}
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) {
switch (static_cast<Prospero::CompSwizzle>(s)) {
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
case Prospero::CompSwizzle::kOne: return vk::ComponentSwizzle::eOne;
case Prospero::CompSwizzle::kRed: return vk::ComponentSwizzle::eR;
case Prospero::CompSwizzle::kGreen: return vk::ComponentSwizzle::eG;
case Prospero::CompSwizzle::kBlue: return vk::ComponentSwizzle::eB;
case Prospero::CompSwizzle::kAlpha: return vk::ComponentSwizzle::eA;
default: EXIT("unknown swizzle: %d\n", static_cast<int>(s));
}
return vk::ComponentSwizzle::eIdentity;
}
static uint32_t TextureGetDstSel(uint32_t swizzle, uint32_t channel) {
return (swizzle >> (channel * 3u)) & 0x7u;
}
} // namespace
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) {
vk::ComponentMapping components {};
components.r = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 0)));
components.g = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 1)));
components.b = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 2)));
components.a = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 3)));
return components;
}
vk::Format TextureGetFormat(uint32_t fmt) {
const auto vk_format = VulkanFormat(fmt);
if (vk_format != vk::Format::eUndefined) {
return vk_format;
}
EXIT("unknown format: fmt = %u\n", fmt);
return vk::Format::eUndefined;
}
namespace {
uint64_t CalcTextureSliceStride(const TileSizeOffset* level_sizes, uint64_t levels,
uint64_t total_size, uint32_t depth) {
uint64_t stride = 0;
for (uint32_t i = 0; i < levels; i++) {
stride =
std::max(stride, static_cast<uint64_t>(level_sizes[i].offset) + level_sizes[i].size);
}
if (depth > 1 && total_size != 0 && total_size % depth == 0) {
const auto guest_stride = total_size / depth;
if (guest_stride >= stride) {
stride = guest_stride;
}
}
return stride;
}
uint64_t CalcLinearUploadLevelSize(uint32_t fmt, uint32_t pitch, uint32_t height) {
if (const uint32_t bytes_per_element = Prospero::NumBytesPerElement(fmt);
bytes_per_element != 0) {
return static_cast<uint64_t>(pitch) * height * bytes_per_element;
}
if (const uint32_t bytes_per_block = Prospero::BlockCompressedBytesPerBlock(fmt);
bytes_per_block != 0) {
const uint32_t blocks_w = std::max((pitch + 3u) / 4u, 1u);
const uint32_t blocks_h = std::max((height + 3u) / 4u, 1u);
return static_cast<uint64_t>(blocks_w) * blocks_h * bytes_per_block;
}
return 0;
}
uint64_t SetLinearUploadLevels(TileSizeOffset* level_sizes, uint32_t fmt, uint64_t height,
uint64_t levels, uint32_t base_pitch) {
uint64_t offset = 0;
auto pitch = base_pitch;
auto h = static_cast<uint32_t>(height);
for (uint32_t i = 0; i < levels; i++) {
const auto size = CalcLinearUploadLevelSize(fmt, pitch, h);
EXIT_NOT_IMPLEMENTED(size > 0xffffffffull);
EXIT_NOT_IMPLEMENTED(offset > 0xffffffffull);
level_sizes[i].size = static_cast<uint32_t>(size);
level_sizes[i].offset = static_cast<uint32_t>(offset);
level_sizes[i].src_size = 0;
level_sizes[i].src_offset = 0;
level_sizes[i].x = 0;
level_sizes[i].y = 0;
offset += size;
if (pitch > 1) {
pitch /= 2;
}
if (h > 1) {
h /= 2;
}
}
return offset;
}
} // namespace
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
uint64_t levels, uint32_t depth, uint64_t pitch,
uint64_t tile, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner) {
TextureUploadLayout layout {};
layout.tile = static_cast<uint32_t>(tile);
layout.pitch = static_cast<uint32_t>(pitch);
layout.texel_block = Prospero::BlockCompressedBytesPerBlock(fmt) != 0 ? 4u : 1u;
layout.volume_texture = volume_texture;
if (fmt != 0) {
if (layout.tile != 0) {
const auto tile_mode = static_cast<Prospero::TileMode>(layout.tile);
switch (tile_mode) {
case Prospero::TileMode::kStandard256B:
if (TileIsStandard256BTextureSupported(fmt)) {
layout.tile_family = TileBlockFamily::Standard256B;
}
break;
case Prospero::TileMode::kStandard4KB:
if (TileIsStandard4KBTextureSupported(fmt)) {
layout.tile_family = TileBlockFamily::Standard4KB;
}
break;
case Prospero::TileMode::kStandard64KB:
if (TileIsStandard64KBTextureSupported(fmt)) {
layout.tile_family = TileBlockFamily::Standard64KB;
}
break;
case Prospero::TileMode::kPrt:
if (TileIsStandard64KBTextureSupported(fmt)) {
layout.tile_family = TileBlockFamily::Prt64KB;
}
break;
case Prospero::TileMode::kRenderTarget:
if (Prospero::RenderTargetBytesPerElement(fmt) != 0) {
layout.tile_family = TileBlockFamily::RenderTarget64KB;
}
break;
case Prospero::TileMode::kDepth:
if (allow_depth_tile && Prospero::RenderTargetBytesPerElement(fmt) != 0) {
layout.tile_family = TileBlockFamily::Depth64KB;
}
break;
default: break;
}
if (layout.tile_family == TileBlockFamily::Count) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%" PRIu64 "x%" PRIu64 " pitch=%" PRIu64
" levels=%" PRIu64 "\n",
owner, static_cast<uint32_t>(fmt), layout.tile, upload_size, width, height,
pitch, levels);
}
}
layout.pitch = TileGetTexturePitch(fmt, width, levels, layout.tile);
TileGetTextureSize(fmt, width, height, layout.pitch, levels, layout.tile, nullptr,
layout.level_sizes, layout.padded_sizes);
if (static_cast<Prospero::TileMode>(layout.tile) != Prospero::TileMode::kLinear) {
if (layout.volume_texture) {
layout.slice_stride = SetLinearUploadLevels(layout.level_sizes, fmt, height, levels,
static_cast<uint32_t>(width));
} else {
TileSizeOffset tiled_levels[16] {};
std::copy_n(layout.level_sizes, levels, tiled_levels);
layout.source_slice_stride =
CalcTextureSliceStride(tiled_levels, levels, upload_size, depth);
SetLinearUploadLevels(layout.level_sizes, fmt, height, levels, layout.pitch);
for (uint32_t i = 0; i < levels; ++i) {
if (tiled_levels[i].src_size > tiled_levels[i].size) {
layout.first_tail_level = std::min(layout.first_tail_level, i);
}
layout.level_sizes[i].src_offset = GetLevelSrcOffset(tiled_levels[i]);
layout.level_sizes[i].src_size = GetLevelSrcSize(tiled_levels[i]);
layout.level_sizes[i].x = tiled_levels[i].x;
layout.level_sizes[i].y = tiled_levels[i].y;
}
}
} else if (layout.volume_texture) {
layout.slice_stride =
CalcTextureSliceStride(layout.level_sizes, levels, upload_size, depth);
}
} else {
EXIT("%s: legacy texture upload format unsupported: fmt=0 tile=%u size=%" PRIu64
" extent=%" PRIu64 "x%" PRIu64 " pitch=%" PRIu64 " levels=%" PRIu64 "\n",
owner, layout.tile, upload_size, width, height, pitch, levels);
}
if (!layout.volume_texture) {
layout.slice_stride =
CalcTextureSliceStride(layout.level_sizes, levels, upload_size, depth);
}
return layout;
}
std::vector<vk::BufferImageCopy>
TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels, bool array_texture,
bool volume_texture) {
uint32_t mip_width = width;
uint32_t mip_height = height;
uint32_t mip_pitch = volume_texture && static_cast<Prospero::TileMode>(layout.tile) !=
Prospero::TileMode::kLinear
? width
: layout.pitch;
std::vector<vk::BufferImageCopy> regions;
regions.reserve(GetTextureRegionCount(depth, levels, volume_texture));
for (uint32_t i = 0; i < levels; i++) {
EXIT_NOT_IMPLEMENTED(layout.level_sizes[i].size == 0);
const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture);
for (uint32_t z = 0; z < mip_depth; z++) {
const auto slice_offset = z * layout.slice_stride;
vk::BufferImageCopy region {};
region.bufferOffset =
layout.level_sizes[i].offset + slice_offset;
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i,
array_texture ? z : 0, 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>(layout.tile) == Prospero::TileMode::kLinear;
if (linear) {
region.bufferRowLength = layout.padded_sizes[i].width;
region.bufferImageHeight = layout.padded_sizes[i].height;
} else {
const auto align = [](uint32_t value, uint32_t block) {
return ((value + block - 1u) / block) * block;
};
const auto pitch = align(mip_pitch, layout.texel_block);
region.bufferRowLength =
pitch > align(mip_width, layout.texel_block) ? pitch : 0;
}
regions.push_back(region);
}
if (mip_width > 1) {
mip_width /= 2;
}
if (mip_height > 1) {
mip_height /= 2;
}
if (mip_pitch > 1) {
mip_pitch /= 2;
}
}
return regions;
}
struct GpuTileElementLayout {
uint32_t bytes = 0;
uint32_t wide = 1;
uint32_t tall = 1;
};
static bool GetGpuTileElementLayout(uint32_t fmt, GpuTileElementLayout& out) {
if (const auto bytes = Prospero::NumBytesPerElement(fmt); bytes != 0) {
out = {bytes, 1, 1};
return true;
}
if (const auto bytes = Prospero::BlockCompressedBytesPerBlock(fmt); bytes != 0) {
out = {bytes, 4, 4};
return true;
}
return false;
}
static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity, uint64_t& size) {
if (offset > capacity || length > capacity - offset) {
return false;
}
size = length;
return true;
}
bool TextureBuildGpuTileInfos(uint64_t size,
const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
uint64_t levels, std::vector<GpuTileInfo>& out_infos) {
if (size == 0 || levels == 0 || levels > 16 || depth == 0 ||
regions.size() != GetTextureRegionCount(depth, levels, layout.volume_texture) ||
Prospero::IsFmaskTextureFormat(fmt)) {
return false;
}
GpuTileElementLayout element {};
if (layout.tile_family == TileBlockFamily::RenderTarget64KB ||
layout.tile_family == TileBlockFamily::Depth64KB) {
element.bytes = Prospero::RenderTargetBytesPerElement(fmt);
} else if (!GetGpuTileElementLayout(fmt, element)) {
return false;
}
if (element.bytes == 0) {
return false;
}
std::vector<GpuTileInfo> infos;
infos.reserve(regions.size());
if (layout.volume_texture) {
TileVolumeLayout volume {};
if (!TileGetTextureVolumeLayout(fmt, regions[0].imageExtent.width,
regions[0].imageExtent.height, depth,
static_cast<uint32_t>(levels), layout.tile, volume)) {
return false;
}
element = {volume.bytes_per_element, volume.texel_width, volume.texel_height};
TileBlockLayout block {};
if (!TileGetBlockLayout(volume.family, element.bytes, block)) return false;
size_t region_base = 0;
for (uint32_t level = 0; level < levels; ++level) {
const uint32_t mip_depth = GetTextureLevelDepth(depth, level, true);
const bool tail = level >= volume.first_tail_level;
const uint64_t linear_stride = layout.slice_stride;
for (uint32_t z = 0; z < mip_depth; z += block.block_depth) {
const uint32_t copy_depth = std::min(block.block_depth, mip_depth - z);
const auto& region = regions[region_base + z];
const auto pitch = region.bufferRowLength != 0
? region.bufferRowLength
: region.imageExtent.width;
const auto logical_height = region.bufferImageHeight != 0
? region.bufferImageHeight
: region.imageExtent.height;
GpuTileInfo info {};
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset;
info.tiled_offset =
static_cast<uint64_t>(z / block.block_depth) * volume.block_slice_size +
volume.level_offsets[level];
const uint64_t linear_span =
static_cast<uint64_t>(copy_depth - 1u) * linear_stride +
layout.level_sizes[level].size;
if (!SetGpuTileSize(info.linear_offset, linear_span, size, info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, volume.level_sizes[level], size,
info.tiled_size)) {
return false;
}
info.linear_slice_stride = linear_stride;
info.width = std::max(
(region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
info.height = std::max(
(logical_height + element.tall - 1u) / element.tall, 1u);
info.depth = copy_depth;
info.surface_z = block.block_depth == 1
? static_cast<uint32_t>(region.imageOffset.z)
: 0;
info.pitch =
std::max((pitch + element.wide - 1u) / element.wide, 1u);
info.tail_x = tail ? volume.tail_x[level] : 0;
info.tail_y = tail ? volume.tail_y[level] : 0;
info.tail = tail;
info.tiled_width = volume.level_widths[level];
info.tiled_height = volume.level_heights[level];
infos.push_back(info);
}
region_base += mip_depth;
}
} else {
const auto base_family = layout.tile_family;
if (base_family == TileBlockFamily::Count) {
return false;
}
size_t region_index = 0;
for (uint32_t level = 0; level < levels; level++) {
const auto& level_size = layout.level_sizes[level];
const bool tail = level >= layout.first_tail_level;
const auto family = base_family;
TileBlockLayout block {};
if (!TileGetBlockLayout(family, element.bytes, block)) {
return false;
}
const auto level_depth = GetTextureLevelDepth(depth, level, layout.volume_texture);
for (uint32_t z = 0; z < level_depth; z++) {
const auto& region = regions[region_index++];
const auto pitch = region.bufferRowLength != 0
? region.bufferRowLength
: region.imageExtent.width;
const auto logical_height = region.bufferImageHeight != 0
? region.bufferImageHeight
: region.imageExtent.height;
GpuTileInfo info {};
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset;
info.tiled_offset = TextureUploadSliceSourceOffset(layout, level, z);
if (!SetGpuTileSize(info.linear_offset, level_size.size, size, info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, GetLevelSrcSize(level_size), size,
info.tiled_size)) {
return false;
}
info.width = std::max(
(region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
info.height = std::max(
(logical_height + element.tall - 1u) / element.tall, 1u);
info.surface_z = base_family == TileBlockFamily::RenderTarget64KB ||
base_family == TileBlockFamily::Depth64KB
? region.imageSubresource.baseArrayLayer
: 0;
info.pitch =
std::max((pitch + element.wide - 1u) / element.wide, 1u);
info.tail = tail;
info.tail_x = tail ? level_size.x : 0;
info.tail_y = tail ? level_size.y : 0;
info.tiled_width =
layout.padded_sizes[level].width != 0
? std::max((layout.padded_sizes[level].width + element.wide - 1u) /
element.wide,
1u)
: info.pitch;
info.tiled_height =
layout.padded_sizes[level].height != 0
? std::max((layout.padded_sizes[level].height + element.tall - 1u) /
element.tall,
1u)
: info.height;
infos.push_back(info);
}
}
}
if (infos.empty()) {
return false;
}
out_infos = std::move(infos);
return true;
}
} // namespace Libs::Graphics
@@ -1,54 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_TEXTURECOMMON_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_TEXTURECOMMON_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <vector>
namespace Libs::Graphics {
struct GpuTileInfo;
struct RenderTargetFormatInfo {
vk::Format format = vk::Format::eUndefined;
uint32_t bytes_per_element = 0;
Prospero::ColorComponentMapping export_mapping;
};
struct TextureUploadLayout {
uint32_t tile = 0;
uint32_t pitch = 0;
uint32_t texel_block = 1;
uint64_t slice_stride = 0;
uint64_t source_slice_stride = 0;
uint32_t first_tail_level = 16;
TileBlockFamily tile_family = TileBlockFamily::Count;
bool volume_texture = false;
TileSizeOffset level_sizes[16] = {};
TilePaddedSize padded_sizes[16] = {};
};
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, uint64_t width, uint64_t height,
uint64_t levels, uint32_t depth, uint64_t pitch,
uint64_t tile, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner);
std::vector<vk::BufferImageCopy>
TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels, bool array_texture,
bool volume_texture);
bool TextureBuildGpuTileInfos(uint64_t size,
const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
uint64_t levels, std::vector<GpuTileInfo>& infos);
} // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_TEXTURECOMMON_H_ */
+185 -611
View File
@@ -1,13 +1,15 @@
#include "graphics/host_gpu/pageManager.h"
#include "common/platform/sysDbg.h"
#include "graphics/host_gpu/regionDefinitions.h"
#include "kernel/memory.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <limits>
#include <memory>
#include <mutex>
#include <vector>
@@ -20,10 +22,9 @@
#undef min
#undef max
#elif defined(__APPLE__)
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <pthread.h>
#include <sys/mman.h>
#include <unistd.h>
#else
#include <execinfo.h>
#include <unistd.h>
#endif
@@ -36,9 +37,8 @@ constexpr uint64_t ADDRESS_SIZE = TRACKER_ADDRESS_SIZE;
constexpr uint64_t REGION_COUNT = ADDRESS_SIZE / REGION_SIZE;
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
// The tracker reuses Win32 memory-protection tags as internal page-state values (on
// Windows they come from <windows.h> and are what VirtualQuery returns). Mirror the
// canonical Win32 numeric values so the shared state-machine logic is identical.
// The tracker reuses Win32 memory-protection tags as internal page-state values.
// Mirror their canonical numeric values so the shared state-machine logic is identical.
constexpr uint32_t PAGE_NOACCESS = 0x01;
constexpr uint32_t PAGE_READONLY = 0x02;
constexpr uint32_t PAGE_READWRITE = 0x04;
@@ -49,58 +49,24 @@ constexpr uint32_t NO_ACCESS_PROTECTION = PAGE_NOACCESS;
constexpr uint32_t READ_ONLY_PROTECTION = PAGE_READONLY;
constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
#if defined(__APPLE__)
// Map the tracker's Win32-style protection tags to POSIX mprotect flags.
static int PageProtToPosix(uint32_t protection) {
switch (protection) {
case PAGE_NOACCESS: return PROT_NONE;
case PAGE_READONLY: return PROT_READ;
case PAGE_READWRITE: return PROT_READ | PROT_WRITE;
default: return PROT_NONE;
}
}
// Query the current protection of the page containing vaddr via the Mach VM map and
// collapse it to the tracker's read/write tags (execute is irrelevant to write tracking).
static uint32_t MachQueryPageProt(uint64_t vaddr) {
auto region_addr = static_cast<mach_vm_address_t>(vaddr);
mach_vm_size_t region_size = 0;
vm_region_basic_info_data_64_t info {};
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
mach_port_t object_name = MACH_PORT_NULL;
kern_return_t kr =
mach_vm_region(mach_task_self(), &region_addr, &region_size, VM_REGION_BASIC_INFO_64,
reinterpret_cast<vm_region_info_t>(&info), &count, &object_name);
if (kr != KERN_SUCCESS || region_addr > vaddr) {
return PAGE_NOACCESS; // no region covering vaddr
}
if ((info.protection & VM_PROT_WRITE) != 0) {
return PAGE_READWRITE;
}
if ((info.protection & VM_PROT_READ) != 0) {
return PAGE_READONLY;
}
return PAGE_NOACCESS;
}
#endif
thread_local bool g_in_fault_resolution = false;
[[noreturn]] void FailFast(const char* reason = nullptr) noexcept {
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);
}
#else
::backtrace_symbols_fd(frames, frame_count, STDERR_FILENO);
#endif
#endif
std::fflush(stderr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
@@ -120,14 +86,13 @@ thread_local bool g_in_fault_resolution = false;
std::_Exit(322);
}
uint32_t CurrentThread() noexcept {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
return GetCurrentThreadId();
#elif defined(__APPLE__)
return static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
#else
FailFast();
#endif
Common::VirtualMemory::Mode ToMemoryMode(uint32_t protection) {
switch (protection) {
case NO_ACCESS_PROTECTION: return Common::VirtualMemory::Mode::NoAccess;
case READ_ONLY_PROTECTION: return Common::VirtualMemory::Mode::Read;
case READ_WRITE_PROTECTION: return Common::VirtualMemory::Mode::ReadWrite;
default: Fatal("unmappable protection 0x%08" PRIx32, protection);
}
}
class SpinGuard final {
@@ -163,28 +128,61 @@ uint64_t PageEnd(uint64_t vaddr, uint64_t size) {
struct PageManager::Impl {
struct PageState {
std::atomic_flag lock = ATOMIC_FLAG_INIT;
uint32_t mappings = 0;
uint32_t gpu_read_mappings = 0;
uint32_t gpu_write_mappings = 0;
uint32_t write_watchers = 0;
uint32_t access_watchers = 0;
uint32_t original_protection = 0;
uint32_t backing_writer = 0;
bool resolving = false;
bool resolving_read_write = false;
bool late_read_pending = false;
bool late_write_pending = false;
uint8_t write_watchers : 7 = 0;
uint8_t access_watchers : 1 = 0;
[[nodiscard]] uint32_t Perms() const noexcept {
if (access_watchers != 0) {
return NO_ACCESS_PROTECTION;
}
if (write_watchers != 0) {
return READ_ONLY_PROTECTION;
}
return READ_WRITE_PROTECTION;
}
template <int delta, bool is_read>
uint32_t AddDelta(uint64_t address) {
static_assert(delta >= -1 && delta <= 1);
if constexpr (is_read) {
if constexpr (delta == 1) {
if (access_watchers != 0) {
Fatal("read-watcher overflow at 0x%016" PRIx64, address);
}
return ++access_watchers;
} else if constexpr (delta == -1) {
if (access_watchers == 0) {
Fatal("read-watcher underflow at 0x%016" PRIx64, address);
}
return --access_watchers;
} else {
return access_watchers;
}
} else {
if constexpr (delta == 1) {
if (write_watchers == 0x7f) {
Fatal("write-watcher overflow at 0x%016" PRIx64, address);
}
return ++write_watchers;
} else if constexpr (delta == -1) {
if (write_watchers == 0) {
Fatal("write-watcher underflow at 0x%016" PRIx64, address);
}
return --write_watchers;
} else {
return write_watchers;
}
}
}
};
static_assert(sizeof(PageState) == 1);
struct Region {
std::atomic_flag lock = ATOMIC_FLAG_INIT;
std::array<PageState, REGION_PAGES> pages;
};
Impl(PageFaultHandler handler, void* context): fault_handler(handler), fault_context(context) {
if (fault_handler == nullptr) {
Fatal("null fault handler");
}
Impl() {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
SYSTEM_INFO info {};
GetSystemInfo(&info);
@@ -195,11 +193,13 @@ struct PageManager::Impl {
#elif defined(__APPLE__)
// Under Rosetta the host page size is 4 KB, matching TRACKER_PAGE_SIZE.
if (static_cast<uint64_t>(getpagesize()) != PAGE_SIZE) {
Fatal("unsupported host page size 0x%08" PRIx32,
static_cast<uint32_t>(getpagesize()));
Fatal("unsupported host page size 0x%08" PRIx32, static_cast<uint32_t>(getpagesize()));
}
#else
Fatal("page-fault invalidation is not implemented on this platform");
const auto host_page_size = ::sysconf(_SC_PAGESIZE);
if (host_page_size < 0 || static_cast<uint64_t>(host_page_size) != PAGE_SIZE) {
Fatal("unsupported host page size %ld", static_cast<long>(host_page_size));
}
#endif
regions = std::make_unique<std::atomic<Region*>[]>(REGION_COUNT);
for (uint64_t i = 0; i < REGION_COUNT; i++) {
@@ -209,11 +209,9 @@ struct PageManager::Impl {
~Impl() {
for (const auto& region: region_storage) {
SpinGuard lock(region->lock);
for (auto& page: region->pages) {
SpinGuard lock(page.lock);
if (page.mappings != 0 || page.gpu_read_mappings != 0 ||
page.gpu_write_mappings != 0 || page.write_watchers != 0 ||
page.access_watchers != 0 || page.backing_writer != 0 || page.resolving) {
if (page.write_watchers != 0 || page.access_watchers != 0) {
FailFast("PageManager destroyed with live page state");
}
}
@@ -241,564 +239,140 @@ struct PageManager::Impl {
return ptr;
}
PageState& GetPage(Region& region, uint64_t vaddr) const {
return region.pages[(vaddr % REGION_SIZE) / PAGE_SIZE];
}
static uint32_t WatcherProtection(const PageState& page) {
if (page.access_watchers != 0) {
return NO_ACCESS_PROTECTION;
}
if (page.write_watchers != 0) {
return READ_ONLY_PROTECTION;
}
return page.original_protection;
}
static void PublishDelayedFaults(PageState& page, uint32_t old_protection,
uint32_t new_protection) {
if (old_protection == NO_ACCESS_PROTECTION && new_protection != NO_ACCESS_PROTECTION) {
page.late_read_pending = true;
}
if ((old_protection == NO_ACCESS_PROTECTION ||
old_protection == READ_ONLY_PROTECTION) &&
new_protection == READ_WRITE_PROTECTION) {
page.late_write_pending = true;
void Protect(uint64_t vaddr, uint64_t size, uint32_t protection) noexcept {
if (!Libs::LibKernel::Memory::ProtectGuestHostMemory(vaddr, size,
ToMemoryMode(protection))) {
Fatal("address-space protection failed at 0x%016" PRIx64 ", new=0x%08" PRIx32, vaddr,
protection);
}
}
static uint32_t QueryProtection(uint64_t vaddr) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
MEMORY_BASIC_INFORMATION info {};
if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(vaddr)), &info,
sizeof(info)) == 0 ||
info.State != MEM_COMMIT || info.Protect != PAGE_READWRITE) {
Fatal("basic path requires PAGE_READWRITE at 0x%016" PRIx64 " (state=0x%08" PRIx32
", protection=0x%08" PRIx32 ")",
vaddr, static_cast<uint32_t>(info.State), static_cast<uint32_t>(info.Protect));
}
return info.Protect;
#elif defined(__APPLE__)
const uint32_t protection = MachQueryPageProt(vaddr);
if (protection != PAGE_READWRITE) {
Fatal("basic path requires PAGE_READWRITE at 0x%016" PRIx64 " (protection=0x%08" PRIx32
")",
vaddr, protection);
}
return protection;
#else
(void)vaddr;
Fatal("page query is unsupported on this platform");
#endif
}
template <bool track, bool is_read, bool masked>
void UpdateRegionWatchers(Region& region, uint64_t base_addr, size_t first, size_t last,
const RegionBits* mask = nullptr) {
SpinGuard lock(region.lock);
auto perms = region.pages[first].Perms();
uint64_t range_begin = 0;
uint64_t range_bytes = 0;
uint64_t potential_range_bytes = 0;
static bool AllowsAccess(uint64_t vaddr, PageFaultAccess access) noexcept {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
MEMORY_BASIC_INFORMATION info {};
if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(vaddr)), &info,
sizeof(info)) == 0 ||
info.State != MEM_COMMIT) {
return false;
}
switch (access) {
case PageFaultAccess::Read:
return info.Protect == PAGE_READONLY || info.Protect == PAGE_READWRITE;
case PageFaultAccess::Write: return info.Protect == PAGE_READWRITE;
default: return false;
}
#elif defined(__APPLE__)
const uint32_t protection = MachQueryPageProt(vaddr);
switch (access) {
case PageFaultAccess::Read:
return protection == PAGE_READONLY || protection == PAGE_READWRITE;
case PageFaultAccess::Write: return protection == PAGE_READWRITE;
default: return false;
}
#else
(void)vaddr;
return false;
#endif
}
static void Protect(uint64_t vaddr, uint32_t protection, uint32_t expected_old,
bool fault_path) noexcept {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
DWORD old_protection = 0;
if (VirtualProtect(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), PAGE_SIZE,
protection, &old_protection) == 0 ||
old_protection != expected_old) {
if (fault_path) {
FailFast("VirtualProtect fault transition did not match expected protection");
const auto release_pending = [&] {
if (range_bytes != 0) {
Protect(base_addr + range_begin * PAGE_SIZE, range_bytes, perms);
range_bytes = 0;
potential_range_bytes = 0;
}
Fatal("invalid protection transition at 0x%016" PRIx64 ", old=0x%08" PRIx32
", expected=0x%08" PRIx32 ", new=0x%08" PRIx32,
vaddr, static_cast<uint32_t>(old_protection), expected_old, protection);
}
#elif defined(__APPLE__)
// mprotect cannot report the previous protection, so the expected_old comparison
// is dropped; the tracker is the sole mutator of these pages and drives the
// transition from its own shadow state.
(void)expected_old;
if (mprotect(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), PAGE_SIZE,
PageProtToPosix(protection)) != 0) {
if (fault_path) {
FailFast("mprotect fault transition failed");
};
for (size_t page_index = first; page_index < last; page_index++) {
auto& page = region.pages[page_index];
const auto address = base_addr + page_index * PAGE_SIZE;
const bool update = !masked || mask->Get(page_index);
const auto old_perms = page.Perms();
const auto new_count = update ? page.AddDelta<track ? 1 : -1, is_read>(address)
: page.AddDelta<0, is_read>(address);
const auto new_perms = page.Perms();
if (new_perms != perms) [[unlikely]] {
release_pending();
perms = new_perms;
} else if (range_bytes != 0) {
potential_range_bytes += PAGE_SIZE;
}
if (!update) {
continue;
}
const bool watcher_edge = (track && new_count == 1) || (!track && new_count == 0);
if (watcher_edge && old_perms != new_perms) {
if (range_bytes == 0) {
range_begin = page_index;
potential_range_bytes = PAGE_SIZE;
}
range_bytes = potential_range_bytes;
}
Fatal("mprotect failed at 0x%016" PRIx64 ", new=0x%08" PRIx32, vaddr, protection);
}
#else
(void)vaddr;
(void)protection;
(void)fault_path;
FailFast("page protection is unsupported on this platform");
#endif
release_pending();
}
template <bool track, bool is_read>
void UpdatePageWatchers(uint64_t vaddr, uint64_t size) {
const auto begin = PageStart(vaddr);
const auto end = PageEnd(vaddr, size);
for (auto chunk_begin = begin; chunk_begin < end;) {
const auto chunk_end = std::min(end, (chunk_begin / REGION_SIZE + 1) * REGION_SIZE);
const auto region_base = chunk_begin / REGION_SIZE * REGION_SIZE;
auto* region = track ? GetOrCreateRegion(chunk_begin) : FindRegion(chunk_begin);
if (region == nullptr) {
Fatal("untracking unknown page 0x%016" PRIx64, chunk_begin);
}
const auto first = static_cast<size_t>((chunk_begin - region_base) / PAGE_SIZE);
const auto last = static_cast<size_t>((chunk_end - region_base) / PAGE_SIZE);
UpdateRegionWatchers<track, is_read, false>(*region, region_base, first, last);
chunk_begin = chunk_end;
}
}
std::unique_ptr<std::atomic<Region*>[]> regions;
std::vector<std::unique_ptr<Region>> region_storage;
std::mutex region_mutex;
PageFaultHandler fault_handler = nullptr;
void* fault_context = nullptr;
};
static_assert(std::atomic<void*>::is_always_lock_free);
PageManager::PageManager(PageFaultHandler fault_handler, void* fault_context)
: m_impl(std::make_unique<Impl>(fault_handler, fault_context)) {}
PageManager::PageManager(): m_impl(std::make_unique<Impl>()) {}
PageManager::~PageManager() = default;
uint64_t PageManager::GetPageSize() const {
if (g_in_fault_resolution) {
FailFast("nested page fault while resolving a watched page");
}
return PAGE_SIZE;
}
bool PageManager::IsTracked(uint64_t vaddr) const noexcept {
if (g_in_fault_resolution) {
FailFast("IsTracked called during fault resolution");
template <bool track>
void PageManager::UpdatePageWatchers(uint64_t vaddr, uint64_t size) {
m_impl->UpdatePageWatchers<track, false>(vaddr, size);
}
template void PageManager::UpdatePageWatchers<true>(uint64_t, uint64_t);
template void PageManager::UpdatePageWatchers<false>(uint64_t, uint64_t);
template <bool track, bool is_read>
void PageManager::UpdatePageWatchersForRegion(uint64_t base_addr, RegionBits& mask) {
if (base_addr % REGION_SIZE != 0 || base_addr >= ADDRESS_SIZE ||
REGION_SIZE > ADDRESS_SIZE - base_addr) {
Fatal("invalid tracking region base 0x%016" PRIx64, base_addr);
}
auto* region = m_impl->FindRegion(vaddr);
const auto start_range = mask.FirstRange();
const auto end_range = mask.LastRange();
if (start_range.first == REGION_PAGES) {
FailFast("empty region watcher mask");
}
const auto first = start_range.first;
const auto last = end_range.second;
if (start_range.second == end_range.second) {
m_impl->UpdatePageWatchers<track, is_read>(base_addr + first * PAGE_SIZE,
(last - first) * PAGE_SIZE);
return;
}
auto* region = track ? m_impl->GetOrCreateRegion(base_addr) : m_impl->FindRegion(base_addr);
if (region == nullptr) {
return false;
Fatal("untracking unknown region 0x%016" PRIx64, base_addr);
}
auto& page = m_impl->GetPage(*region, vaddr);
SpinGuard lock(page.lock);
return page.write_watchers != 0 || page.access_watchers != 0;
m_impl->UpdateRegionWatchers<track, is_read, true>(*region, base_addr, first, last, &mask);
}
bool PageManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
if (vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE || size > ADDRESS_SIZE - vaddr) {
return false;
}
const auto end = PageStart(vaddr + size - 1) + PAGE_SIZE;
for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) {
auto* region = m_impl->FindRegion(page_vaddr);
if (region == nullptr) {
return false;
}
auto& page = m_impl->GetPage(*region, page_vaddr);
SpinGuard lock(page.lock);
if (page.mappings == 0) {
return false;
}
}
return true;
}
template void PageManager::UpdatePageWatchersForRegion<true, true>(uint64_t, RegionBits&);
template void PageManager::UpdatePageWatchersForRegion<true, false>(uint64_t, RegionBits&);
template void PageManager::UpdatePageWatchersForRegion<false, true>(uint64_t, RegionBits&);
template void PageManager::UpdatePageWatchersForRegion<false, false>(uint64_t, RegionBits&);
bool PageManager::HasAnyMapping(uint64_t vaddr, uint64_t size) const noexcept {
if (g_in_fault_resolution || vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE ||
size > ADDRESS_SIZE - vaddr) {
return false;
}
const auto end = PageEnd(vaddr, size);
for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) {
auto* region = m_impl->FindRegion(page_vaddr);
if (region == nullptr) {
continue;
}
auto& page = m_impl->GetPage(*region, page_vaddr);
SpinGuard lock(page.lock);
if (page.mappings != 0) {
return true;
}
}
return false;
}
void PageManager::OnGpuMap(uint64_t, uint64_t) {}
bool PageManager::HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access) const noexcept {
if (access != GpuAccess::Read && access != GpuAccess::Write && access != GpuAccess::ReadWrite) {
FailFast("HasGpuAccess received an invalid GPU access mode");
}
const bool need_read = access == GpuAccess::Read || access == GpuAccess::ReadWrite;
const bool need_write = access == GpuAccess::Write || access == GpuAccess::ReadWrite;
if (vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE || size > ADDRESS_SIZE - vaddr) {
return false;
}
const auto end = PageEnd(vaddr, size);
for (auto addr = PageStart(vaddr); addr < end; addr += PAGE_SIZE) {
auto* region = m_impl->FindRegion(addr);
if (region == nullptr) {
return false;
}
auto& page = m_impl->GetPage(*region, addr);
SpinGuard lock(page.lock);
if ((need_read && page.gpu_read_mappings == 0) ||
(need_write && page.gpu_write_mappings == 0)) {
return false;
}
}
return true;
}
void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
PageWatchMode mode) {
if (mode != PageWatchMode::Write && mode != PageWatchMode::ReadWrite) {
Fatal("invalid watcher mode");
}
const auto end = PageEnd(vaddr, size);
for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) {
auto* region =
track ? m_impl->GetOrCreateRegion(page_vaddr) : m_impl->FindRegion(page_vaddr);
if (region == nullptr) {
Fatal("untracking unknown page 0x%016" PRIx64, page_vaddr);
}
auto& page = m_impl->GetPage(*region, page_vaddr);
SpinGuard lock(page.lock);
if (page.resolving && track) {
FailFast("new page watcher raced active fault resolution");
}
if (page.mappings == 0) {
Fatal("watching unmapped page 0x%016" PRIx64, page_vaddr);
}
auto& watchers =
(mode == PageWatchMode::ReadWrite ? page.access_watchers : page.write_watchers);
if (track) {
if (watchers == std::numeric_limits<uint32_t>::max()) {
Fatal("watcher overflow at 0x%016" PRIx64, page_vaddr);
}
const bool first_watcher = page.write_watchers == 0 && page.access_watchers == 0;
if (first_watcher) {
page.original_protection = Impl::QueryProtection(page_vaddr);
}
const auto old_protection = Impl::WatcherProtection(page);
watchers++;
const auto new_protection = Impl::WatcherProtection(page);
if (new_protection != old_protection) {
Impl::Protect(page_vaddr, new_protection, old_protection, false);
}
switch (new_protection) {
case NO_ACCESS_PROTECTION:
page.late_read_pending = false;
page.late_write_pending = false;
break;
case READ_ONLY_PROTECTION: page.late_write_pending = false; break;
default: break;
}
} else {
if (watchers == 0) {
Fatal("watcher underflow at 0x%016" PRIx64, page_vaddr);
}
if (page.backing_writer != 0 && page.backing_writer != CurrentThread()) {
Fatal("backing write ownership changed at 0x%016" PRIx64, page_vaddr);
}
const auto old_protection = Impl::WatcherProtection(page);
watchers--;
const auto new_protection = Impl::WatcherProtection(page);
if (page.backing_writer == 0 && new_protection != old_protection) {
Impl::Protect(page_vaddr, new_protection, old_protection, false);
}
if (page.backing_writer == 0) {
Impl::PublishDelayedFaults(page, old_protection, new_protection);
}
if (page.backing_writer == 0 && page.write_watchers == 0 && page.access_watchers == 0) {
page.original_protection = 0;
}
}
}
}
void PageManager::OnGpuMap(uint64_t vaddr, uint64_t size, GpuAccess access) {
if (g_in_fault_resolution) {
FailFast("GPU mapping changed during fault resolution");
}
if (access != GpuAccess::Read && access != GpuAccess::Write && access != GpuAccess::ReadWrite) {
FailFast("GPU map received an invalid access mode");
}
const bool gpu_read = access == GpuAccess::Read || access == GpuAccess::ReadWrite;
const bool gpu_write = access == GpuAccess::Write || access == GpuAccess::ReadWrite;
const auto end = PageEnd(vaddr, size);
for (auto addr = PageStart(vaddr); addr < end; addr += PAGE_SIZE) {
auto& page = m_impl->GetPage(*m_impl->GetOrCreateRegion(addr), addr);
SpinGuard lock(page.lock);
if (page.resolving || page.mappings == std::numeric_limits<uint32_t>::max() ||
(gpu_read && page.gpu_read_mappings == std::numeric_limits<uint32_t>::max()) ||
(gpu_write && page.gpu_write_mappings == std::numeric_limits<uint32_t>::max())) {
Fatal("invalid map state at 0x%016" PRIx64, addr);
}
page.mappings++;
page.gpu_read_mappings += gpu_read ? 1u : 0u;
page.gpu_write_mappings += gpu_write ? 1u : 0u;
}
}
void PageManager::OnGpuUnmap(uint64_t vaddr, uint64_t size, GpuAccess access) {
if (g_in_fault_resolution) {
FailFast("GPU unmapping changed during fault resolution");
}
if (access != GpuAccess::Read && access != GpuAccess::Write && access != GpuAccess::ReadWrite) {
FailFast("GPU unmap received an invalid access mode");
}
const bool gpu_read = access == GpuAccess::Read || access == GpuAccess::ReadWrite;
const bool gpu_write = access == GpuAccess::Write || access == GpuAccess::ReadWrite;
const auto end = PageEnd(vaddr, size);
for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) {
auto* region = m_impl->FindRegion(page_vaddr);
if (region == nullptr) {
Fatal("unmapping unknown page 0x%016" PRIx64, page_vaddr);
}
auto& page = m_impl->GetPage(*region, page_vaddr);
SpinGuard lock(page.lock);
if (page.resolving || page.mappings == 0 || (gpu_read && page.gpu_read_mappings == 0) ||
(gpu_write && page.gpu_write_mappings == 0) ||
(page.mappings == 1 && (page.write_watchers != 0 || page.access_watchers != 0))) {
Fatal("invalid unmap state at 0x%016" PRIx64, page_vaddr);
}
page.mappings--;
page.gpu_read_mappings -= gpu_read ? 1u : 0u;
page.gpu_write_mappings -= gpu_write ? 1u : 0u;
if (page.mappings == 0) {
if (page.gpu_read_mappings != 0 || page.gpu_write_mappings != 0) {
FailFast("GPU unmap left nonzero GPU mapping counts");
}
page.late_read_pending = false;
page.late_write_pending = false;
}
}
}
PageManager::BackingWrite::BackingWrite(PageManager& manager, uint64_t vaddr,
uint64_t size) noexcept
: m_manager(manager), m_vaddr(vaddr), m_size(size) {
m_manager.BeginBackingWrite(vaddr, size);
}
PageManager::BackingWrite::~BackingWrite() {
m_manager.EndBackingWrite(m_vaddr, m_size);
}
std::vector<std::unique_ptr<PageManager::BackingWrite>>
PageManager::ReserveBackingWrites(std::span<const RangeSet::Range> ranges) {
if (ranges.empty()) {
Fatal("cannot reserve empty backing-write ranges");
}
std::vector<std::unique_ptr<BackingWrite>> writes;
writes.reserve(ranges.size());
uint64_t begin = 0;
uint64_t end = 0;
for (const auto& range: ranges) {
if (range.address == 0 || range.size == 0 || range.size > UINT64_MAX - range.address ||
range.address + range.size > UINT64_MAX - (PAGE_SIZE - 1)) {
Fatal("invalid backing-write range");
}
const auto page_begin = PageStart(range.address);
const auto page_end = PageStart(range.address + range.size + PAGE_SIZE - 1);
if (begin != 0 && page_begin > end) {
writes.push_back(std::make_unique<BackingWrite>(*this, begin, end - begin));
begin = 0;
}
if (begin == 0) {
begin = page_begin;
end = page_end;
} else {
end = std::max(end, page_end);
}
}
writes.push_back(std::make_unique<BackingWrite>(*this, begin, end - begin));
return writes;
}
void PageManager::BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
if (g_in_fault_resolution) {
FailFast("backing write began during fault resolution");
}
const auto end = PageEnd(vaddr, size);
const auto writer = CurrentThread();
for (auto address = PageStart(vaddr); address < end; address += PAGE_SIZE) {
auto* region = m_impl->FindRegion(address);
if (region == nullptr) {
Fatal("backing write reserves an unknown page at 0x%016" PRIx64, address);
}
auto& page = m_impl->GetPage(*region, address);
SpinGuard lock(page.lock);
if (page.mappings == 0 || page.resolving || page.backing_writer != 0 ||
page.access_watchers == 0) {
Fatal("backing write races page resolution at 0x%016" PRIx64, address);
}
page.resolving = true;
page.resolving_read_write = true;
page.backing_writer = writer;
}
}
void PageManager::EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
if (g_in_fault_resolution) {
FailFast("backing write ended during fault resolution");
}
const auto end = PageEnd(vaddr, size);
const auto writer = CurrentThread();
for (auto address = PageStart(vaddr); address < end; address += PAGE_SIZE) {
auto* region = m_impl->FindRegion(address);
if (region == nullptr) {
FailFast("backing write ended for an unknown page");
}
auto& page = m_impl->GetPage(*region, address);
SpinGuard lock(page.lock);
if (!page.resolving || page.backing_writer != writer) {
FailFast("backing write ended without matching owner and resolving state");
}
const auto old_protection = NO_ACCESS_PROTECTION;
const auto new_protection = Impl::WatcherProtection(page);
if (new_protection != old_protection) {
Impl::Protect(address, new_protection, old_protection, false);
}
Impl::PublishDelayedFaults(page, old_protection, new_protection);
if (page.write_watchers == 0 && page.access_watchers == 0) {
page.original_protection = 0;
}
page.backing_writer = 0;
page.resolving = false;
page.resolving_read_write = false;
}
}
bool PageManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
if (g_in_fault_resolution) {
FailFast("nested HandleFault call");
}
auto* region = m_impl->FindRegion(fault_vaddr);
if (region == nullptr) {
return false;
}
auto& page = m_impl->GetPage(*region, fault_vaddr);
bool waited = false;
while (true) {
SpinGuard lock(page.lock);
if (access == PageFaultAccess::Read && page.late_read_pending &&
Impl::AllowsAccess(fault_vaddr, access)) {
page.late_read_pending = false;
return true;
}
if (access == PageFaultAccess::Write && page.late_write_pending &&
Impl::AllowsAccess(fault_vaddr, access)) {
page.late_write_pending = false;
return true;
}
if (page.resolving) {
if (page.backing_writer == CurrentThread()) {
FailFast("backing writer faulted on its own reserved page");
}
if ((!page.resolving_read_write && access != PageFaultAccess::Write) ||
(page.resolving_read_write && access != PageFaultAccess::Read &&
access != PageFaultAccess::Write)) {
FailFast("fault access is incompatible with the active resolver");
}
waited = true;
continue;
}
if (page.write_watchers == 0 && page.access_watchers == 0) {
if (access != PageFaultAccess::Read && access != PageFaultAccess::Write) {
return false;
}
bool& pending = (access == PageFaultAccess::Read ? page.late_read_pending
: page.late_write_pending);
const bool allowed = Impl::AllowsAccess(fault_vaddr, access);
pending = false;
if (waited && !allowed) {
FailFast("page remained inaccessible after waiting for its resolver");
}
// More than one CPU can fault before a protection transition becomes visible. The first
// delayed fault consumes the hint bit; later faults must also resume once the mapped
// page already permits the requested access. A genuinely read-only/no-access page still
// falls through to the guest exception path.
return allowed;
}
if ((access != PageFaultAccess::Read && access != PageFaultAccess::Write) ||
(access == PageFaultAccess::Read && page.access_watchers == 0)) {
FailFast("fault access is incompatible with active page watchers");
}
page.resolving = true;
page.resolving_read_write = page.access_watchers != 0;
break;
}
g_in_fault_resolution = true;
const bool handled = m_impl->fault_handler(m_impl->fault_context, access, fault_vaddr, 1,
PageFaultPhase::Invalidate);
g_in_fault_resolution = false;
{
SpinGuard lock(page.lock);
if (!handled || !page.resolving) {
FailFast("fault invalidation did not preserve the resolving state");
}
}
g_in_fault_resolution = true;
const bool completed = m_impl->fault_handler(m_impl->fault_context, access, fault_vaddr, 1,
PageFaultPhase::Complete);
g_in_fault_resolution = false;
{
SpinGuard lock(page.lock);
if (!completed || !page.resolving) {
FailFast("fault completion did not preserve the resolving state");
}
if (page.write_watchers != 0 || page.access_watchers != 0) {
const auto old_protection = Impl::WatcherProtection(page);
const bool read_only_fault = access == PageFaultAccess::Read;
if (read_only_fault && page.access_watchers == 0) {
FailFast("read fault completed without a read/write watcher");
}
page.access_watchers = 0;
if (!read_only_fault) {
page.write_watchers = 0;
}
const auto restored_protection = Impl::WatcherProtection(page);
Impl::Protect(PageStart(fault_vaddr), restored_protection, old_protection, true);
if (page.write_watchers == 0) {
page.original_protection = 0;
}
Impl::PublishDelayedFaults(page, old_protection, restored_protection);
} else if (!Impl::AllowsAccess(fault_vaddr, access)) {
FailFast("fault completion left the page inaccessible");
}
page.resolving = false;
page.resolving_read_write = false;
}
g_in_fault_resolution = true;
const bool released = m_impl->fault_handler(m_impl->fault_context, access, fault_vaddr, 1,
PageFaultPhase::Release);
g_in_fault_resolution = false;
if (!released) {
FailFast("fault release callback failed");
}
return true;
}
bool PageManager::HandleWriteRange(uint64_t vaddr, uint64_t size) noexcept {
if (g_in_fault_resolution || vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE ||
size > ADDRESS_SIZE - vaddr) {
return false;
}
const auto end = PageEnd(vaddr, size);
for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) {
if (!IsMapped(page_vaddr, 1)) {
continue;
}
const auto fault_vaddr = std::max(page_vaddr, vaddr);
if (!HandleFault(PageFaultAccess::Write, fault_vaddr)) {
return false;
}
}
return true;
}
void PageManager::OnGpuUnmap(uint64_t, uint64_t) {}
} // namespace Libs::Graphics
+8 -38
View File
@@ -2,62 +2,32 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_PAGEMANAGER_H_
#include "common/common.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/regionDefinitions.h"
#include <memory>
#include <span>
#include <vector>
namespace Libs::Graphics {
enum class PageFaultAccess { Read, Write, Execute, Unknown };
enum class PageFaultPhase { Invalidate, Complete, Release };
enum class PageWatchMode { Write, ReadWrite };
enum class GpuAccess { Read, Write, ReadWrite };
using PageFaultHandler = bool (*)(void* context, PageFaultAccess access, uint64_t vaddr,
uint64_t size, PageFaultPhase phase) noexcept;
class PageManager final {
public:
class BackingWrite final {
public:
BackingWrite(PageManager& manager, uint64_t vaddr, uint64_t size) noexcept;
~BackingWrite();
KYTY_CLASS_NO_COPY(BackingWrite);
private:
PageManager& m_manager;
uint64_t m_vaddr = 0;
uint64_t m_size = 0;
};
PageManager(PageFaultHandler fault_handler, void* fault_context);
PageManager();
// The owner must stop all PageManager callers before destruction.
~PageManager();
KYTY_CLASS_NO_COPY(PageManager);
[[nodiscard]] uint64_t GetPageSize() const;
[[nodiscard]] bool IsTracked(uint64_t vaddr) const noexcept;
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
[[nodiscard]] bool HasAnyMapping(uint64_t vaddr, uint64_t size) const noexcept;
[[nodiscard]] bool HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access) const noexcept;
void UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
PageWatchMode mode = PageWatchMode::Write);
void OnGpuMap(uint64_t vaddr, uint64_t size, GpuAccess access = GpuAccess::ReadWrite);
void OnGpuUnmap(uint64_t vaddr, uint64_t size, GpuAccess access = GpuAccess::ReadWrite);
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
[[nodiscard]] bool HandleWriteRange(uint64_t vaddr, uint64_t size) noexcept;
[[nodiscard]] std::vector<std::unique_ptr<BackingWrite>>
ReserveBackingWrites(std::span<const RangeSet::Range> ranges);
template <bool track>
void UpdatePageWatchers(uint64_t vaddr, uint64_t size);
template <bool track, bool is_read = false>
void UpdatePageWatchersForRegion(uint64_t base_addr, RegionBits& mask);
void OnGpuMap(uint64_t vaddr, uint64_t size);
void OnGpuUnmap(uint64_t vaddr, uint64_t size);
private:
void BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept;
void EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept;
struct Impl;
std::unique_ptr<Impl> m_impl;
};
+10
View File
@@ -59,6 +59,16 @@ public:
return result;
}
[[nodiscard]] bool Contains(uint64_t address, uint64_t size) const {
const auto end = End(address, size);
auto it = m_ranges.upper_bound(address);
if (it == m_ranges.begin()) {
return false;
}
--it;
return it->first <= address && it->second >= end;
}
template <typename Func>
void ForEachIntersection(uint64_t address, uint64_t size, Func&& func) const {
const auto end = End(address, size);
+3 -3
View File
@@ -1,10 +1,9 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONDEFINITIONS_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONDEFINITIONS_H_
#include "common/bitArray.h"
#include "common/common.h"
#include <bitset>
namespace Libs::Graphics {
constexpr uint64_t TRACKER_PAGE_SIZE = 4ull * 1024ull;
@@ -13,7 +12,8 @@ constexpr uint64_t TRACKER_ADDRESS_SIZE = 1ull << 40u;
constexpr size_t TRACKER_REGION_PAGES = TRACKER_REGION_SIZE / TRACKER_PAGE_SIZE;
enum class DirtySource { Cpu, Gpu };
using RegionBits = std::bitset<TRACKER_REGION_PAGES>;
using RegionBits = Common::BitArray<TRACKER_REGION_PAGES>;
static_assert(sizeof(RegionBits) == TRACKER_REGION_PAGES / 8);
} // namespace Libs::Graphics
+59 -202
View File
@@ -18,12 +18,13 @@
#undef max
#elif defined(__APPLE__)
#include <pthread.h>
#elif defined(__linux__)
#include <sys/syscall.h>
#include <unistd.h>
#endif
namespace Libs::Graphics {
enum class CpuFaultAction { Untracked, Continue, Download };
class TrackingSpinLock final {
public:
void lock() noexcept {
@@ -54,6 +55,9 @@ private:
#elif defined(__APPLE__)
// mach thread port is a nonzero per-thread id (0 is the "no owner" sentinel).
return static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
#elif defined(__linux__)
static thread_local const uint32_t tid = static_cast<uint32_t>(::syscall(SYS_gettid));
return tid;
#else
EXIT("region tracking thread identity is unsupported on this platform\n");
#endif
@@ -72,229 +76,93 @@ public:
if (m_cpu_addr % TRACKER_REGION_SIZE != 0) {
EXIT("invalid region tracking manager construction\n");
}
m_cpu_dirty.set();
m_writable.set();
m_cpu_dirty.Fill();
m_writable.Fill();
m_readable.Fill();
}
KYTY_CLASS_NO_COPY(RegionManager);
[[nodiscard]] uint64_t GetCpuAddr() const { return m_cpu_addr; }
void Track(uint64_t vaddr, uint64_t size) {
const auto [start, end] = GetPageRange(vaddr, size);
for (auto page = start; page < end; page++) {
m_tracked.set(page);
}
}
void Untrack(uint64_t vaddr, uint64_t size) {
const auto [start, end] = GetPageRange(vaddr, size);
for (auto page = start; page < end; page++) {
m_tracked.reset(page);
}
}
template <DirtySource source>
[[nodiscard]] bool IsModified(uint64_t offset, uint64_t size) const {
const auto [start, end] = GetPageRange(m_cpu_addr + offset, size);
const auto& bits = GetBits<source>();
for (auto page = start; page < end; page++) {
if (bits.test(page)) {
return true;
}
}
return false;
}
template <DirtySource source>
[[nodiscard]] bool IsFullyModified(uint64_t offset, uint64_t size) const {
const auto [start, end] = GetPageRange(m_cpu_addr + offset, size);
const auto& bits = GetBits<source>();
for (auto page = start; page < end; page++) {
if (!bits.test(page)) {
return false;
}
}
return true;
return RegionBits(bits, start, end).Any();
}
template <DirtySource source, bool enable>
RegionBits ChangeState(uint64_t vaddr, uint64_t size) {
void ChangeState(uint64_t vaddr, uint64_t size) {
const auto [start, end] = GetPageRange(vaddr, size);
if constexpr (source == DirtySource::Cpu && enable) {
for (auto page = start; page < end; page++) {
if (m_gpu_dirty.test(page) || m_fault_pending.test(page)) {
EXIT("CPU dirty state conflicts with GPU dirty or pending fault state\n");
}
if (RegionBits(m_gpu_dirty, start, end).Any()) {
EXIT("CPU dirty state conflicts with GPU dirty state\n");
}
}
if constexpr (source == DirtySource::Gpu && enable) {
for (auto page = start; page < end; page++) {
if (m_cpu_dirty.test(page) || m_fault_pending.test(page)) {
EXIT("GPU dirty state conflicts with CPU dirty or pending fault state\n");
}
if (RegionBits(m_cpu_dirty, start, end).Any()) {
EXIT("GPU dirty state conflicts with CPU dirty state\n");
}
}
auto& bits = GetBits<source>();
auto changed = bits;
for (auto page = start; page < end; page++) {
bits.set(page, enable);
auto& bits = GetBits<source>();
if constexpr (enable) {
bits.SetRange(start, end);
} else {
bits.UnsetRange(start, end);
}
changed ^= bits;
if constexpr (source == DirtySource::Cpu) {
changed = m_cpu_dirty ^ m_writable;
m_writable = m_cpu_dirty;
UpdateCpuProtection<!enable>();
} else {
UpdateGpuProtection<enable>();
}
return changed;
}
[[nodiscard]] CpuFaultAction BeginCpuFault(uint64_t vaddr, uint64_t size,
PageFaultAccess access = PageFaultAccess::Write) {
if (access != PageFaultAccess::Read && access != PageFaultAccess::Write) {
EXIT("unsupported CPU fault access while beginning ownership transfer\n");
}
const auto [start, end] = GetPageRange(vaddr, size);
const bool tracked = m_tracked.test(start);
for (auto page = start; page < end; page++) {
if (m_tracked.test(page) != tracked) {
EXIT("CPU fault spans mixed tracked and untracked pages\n");
}
if (m_fault_pending.test(page)) {
return CpuFaultAction::Untracked;
}
if (m_cpu_dirty.test(page) != m_writable.test(page) ||
(m_gpu_dirty.test(page) && (m_cpu_dirty.test(page) || m_writable.test(page)))) {
EXIT("inconsistent CPU fault page state\n");
}
}
if (!tracked) {
return CpuFaultAction::Untracked;
}
bool gpu_dirty = m_gpu_dirty.test(start);
bool writable = m_writable.test(start);
for (auto page = start + 1; page < end; page++) {
if (m_gpu_dirty.test(page) != gpu_dirty || m_writable.test(page) != writable) {
EXIT("CPU fault spans pages with incompatible dirty or writable state\n");
}
}
for (auto page = start; page < end; page++) {
if (!gpu_dirty && access == PageFaultAccess::Write) {
m_cpu_dirty.set(page);
m_writable.set(page);
}
m_fault_pending.set(page);
}
return gpu_dirty ? CpuFaultAction::Download : CpuFaultAction::Continue;
}
[[nodiscard]] bool CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
bool downloaded) {
const auto [start, end] = GetPageRange(vaddr, size);
for (auto page = start; page < end; page++) {
if (!m_fault_pending.test(page)) {
return false;
}
}
for (auto page = start; page < end; page++) {
const bool gpu_dirty = m_gpu_dirty.test(page);
if (gpu_dirty != downloaded) {
EXIT("CPU fault download result disagrees with GPU dirty state\n");
}
if (gpu_dirty) {
m_gpu_dirty.reset(page);
switch (access) {
case PageFaultAccess::Read: break;
case PageFaultAccess::Write:
m_cpu_dirty.set(page);
m_writable.set(page);
break;
default: EXIT("unsupported CPU fault access after GPU download\n");
}
}
m_fault_pending.reset(page);
}
return true;
}
[[nodiscard]] bool HasPendingFault(uint64_t vaddr, uint64_t size) const {
const auto [start, end] = GetPageRange(vaddr, size);
for (auto page = start; page < end; page++) {
if (m_fault_pending.test(page)) {
return true;
}
}
return false;
}
[[nodiscard]] bool CompleteVirtualGpuWrite(uint64_t vaddr, uint64_t size) {
const auto [start, end] = GetPageRange(vaddr, size);
for (auto page = start; page < end; page++) {
if (!m_fault_pending.test(page)) {
return false;
}
if (!m_gpu_dirty.test(page)) {
EXIT("virtual GPU write completion found a non-GPU-dirty page\n");
}
}
for (auto page = start; page < end; page++) {
m_gpu_dirty.reset(page);
m_cpu_dirty.set(page);
m_writable.set(page);
m_fault_pending.reset(page);
}
return true;
}
template <DirtySource source, bool clear, typename Func>
RegionBits ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) {
void ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) {
const auto [start, end] = GetPageRange(vaddr, size);
auto mask = GetBits<source>();
if constexpr (source == DirtySource::Cpu) {
mask &= ~m_fault_pending;
}
for (auto page = 0u; page < start; page++) {
mask.reset(page);
}
for (auto page = end; page < TRACKER_REGION_PAGES; page++) {
mask.reset(page);
}
RegionBits mask(GetBits<source>(), start, end);
if constexpr (clear) {
auto& bits = GetBits<source>();
for (auto page = start; page < end; page++) {
if (mask.test(page)) {
bits.reset(page);
}
}
GetBits<source>().UnsetRange(start, end);
}
if constexpr (source == DirtySource::Cpu && clear) {
auto changed = m_cpu_dirty ^ m_writable;
m_writable = m_cpu_dirty;
ApplyProtection(changed, true);
UpdateCpuProtection<true>();
ForEachRange(mask, std::forward<Func>(func));
return changed;
return;
}
if constexpr (source == DirtySource::Gpu && clear) {
UpdateGpuProtection<false>();
}
ForEachRange(mask, std::forward<Func>(func));
if constexpr (clear) {
return mask;
}
return {};
}
void ApplyProtection(const RegionBits& changed, bool track) {
ForEachRange(changed, [this, track](uint64_t vaddr, uint64_t size) {
m_page_manager.UpdatePageWatchers(track, vaddr, size);
});
}
void ApplyGpuProtection(const RegionBits& changed, bool track, PageWatchMode mode) {
if (mode != PageWatchMode::Write && mode != PageWatchMode::ReadWrite) {
EXIT("unsupported GPU page-watch mode\n");
}
ForEachRange(changed, [this, track, mode](uint64_t vaddr, uint64_t size) {
m_page_manager.UpdatePageWatchers(track, vaddr, size, mode);
});
}
TrackingSpinLock lock;
private:
template <bool track>
void UpdateCpuProtection() {
auto mask = m_cpu_dirty ^ m_writable;
m_writable = m_cpu_dirty;
if (mask.None()) {
return;
}
m_page_manager.UpdatePageWatchersForRegion<track>(m_cpu_addr, mask);
}
template <bool track>
void UpdateGpuProtection() {
auto readable = ~m_gpu_dirty;
auto mask = readable ^ m_readable;
m_readable = readable;
if (mask.None()) {
return;
}
if constexpr (track) {
m_page_manager.UpdatePageWatchersForRegion<true, true>(m_cpu_addr, mask);
} else {
m_page_manager.UpdatePageWatchersForRegion<false, true>(m_cpu_addr, mask);
}
}
template <DirtySource source>
RegionBits& GetBits() {
if constexpr (source == DirtySource::Cpu) {
@@ -325,18 +193,8 @@ private:
template <typename Func>
void ForEachRange(const RegionBits& bits, Func&& func) const {
size_t page = 0;
while (page < TRACKER_REGION_PAGES) {
while (page < TRACKER_REGION_PAGES && !bits.test(page)) {
page++;
}
const auto start = page;
while (page < TRACKER_REGION_PAGES && bits.test(page)) {
page++;
}
if (start != page) {
func(m_cpu_addr + start * TRACKER_PAGE_SIZE, (page - start) * TRACKER_PAGE_SIZE);
}
for (const auto [start, end]: bits) {
func(m_cpu_addr + start * TRACKER_PAGE_SIZE, (end - start) * TRACKER_PAGE_SIZE);
}
}
@@ -345,8 +203,7 @@ private:
RegionBits m_cpu_dirty;
RegionBits m_gpu_dirty;
RegionBits m_writable;
RegionBits m_fault_pending;
RegionBits m_tracked;
RegionBits m_readable;
};
} // namespace Libs::Graphics
File diff suppressed because it is too large. Load diff
+839
View File
@@ -0,0 +1,839 @@
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "kernel/memory.h"
#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstring>
#include <utility>
#include <vector>
namespace Libs::Graphics {
namespace {
constexpr uint64_t MiB = 1024 * 1024;
constexpr uint64_t GdsBufferSize = 64 * 1024;
} // namespace
uint64_t BufferCache::AlignDown(uint64_t value) noexcept {
return value & ~(CACHING_PAGE_SIZE - 1);
}
uint64_t BufferCache::AlignUp(uint64_t value) {
if (value > UINT64_MAX - (CACHING_PAGE_SIZE - 1)) {
EXIT("BufferCache: address alignment overflow\n");
}
return (value + CACHING_PAGE_SIZE - 1) & ~(CACHING_PAGE_SIZE - 1);
}
void BufferCache::Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset,
const void* source, uint64_t size) {
auto* bytes = static_cast<const uint8_t*>(source);
while (size != 0) {
const auto chunk = std::min(size, m_staging_buffer.Size());
const auto stage_offset = m_staging_buffer.Copy(bytes, chunk, 4);
destination.CopyFrom(command, m_staging_buffer, stage_offset, destination_offset, chunk,
vk::AccessFlagBits::eHostWrite);
bytes += chunk;
destination_offset += chunk;
size -= chunk;
}
}
bool BufferCache::ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept {
if (merged.address == 0 || merged.size == 0 || candidate.address == 0 || candidate.size == 0 ||
merged.size > UINT64_MAX - merged.address ||
candidate.size > UINT64_MAX - candidate.address) {
EXIT("BufferCache: invalid overlap-merge range\n");
}
const auto merged_end = merged.address + merged.size;
const auto candidate_end = candidate.address + candidate.size;
if (merged.address >= candidate_end || candidate.address >= merged_end) {
return false;
}
const auto address = std::min(merged.address, candidate.address);
const auto end = std::max(merged_end, candidate_end);
merged = {.address = address, .size = end - address};
return true;
}
struct BufferCache::CachedBuffer {
uint64_t vaddr = 0;
uint64_t size = 0;
std::shared_ptr<Buffer> buffer;
uint64_t tick_accessed_last = 0;
};
struct BufferCache::DownloadCopy {
std::shared_ptr<Buffer> owner;
uint64_t source_offset = 0;
uint64_t address = 0;
uint64_t size = 0;
};
struct BufferCache::DownloadRange {
uint64_t address = 0;
uint64_t size = 0;
uint64_t offset = 0;
};
struct BufferCache::RetiredBuffer {
uint64_t address = 0;
uint64_t size = 0;
std::shared_ptr<Buffer> owner;
};
std::pair<uint64_t, uint64_t> BufferCache::DownloadEnvelope(const DownloadCopy& copy) {
if (copy.owner == nullptr || copy.size == 0 || copy.source_offset > copy.owner->Size() ||
copy.size > copy.owner->Size() - copy.source_offset) {
EXIT("BufferCache: invalid download copy\n");
}
const auto begin = copy.source_offset & ~uint64_t {3};
if (copy.source_offset > UINT64_MAX - copy.size ||
copy.source_offset + copy.size > UINT64_MAX - 3) {
EXIT("BufferCache: download copy alignment overflow\n");
}
const auto end = (copy.source_offset + copy.size + 3) & ~uint64_t {3};
if (end > copy.owner->Size()) {
EXIT("BufferCache: aligned download copy exceeds its owner\n");
}
return {begin, end - begin};
}
std::vector<BufferCache::DownloadRange>
BufferCache::RecordDownloads(std::span<const DownloadCopy> copies) {
uint64_t reservation_size = 0;
for (const auto& copy: copies) {
const auto [source_begin, envelope_size] = DownloadEnvelope(copy);
(void)source_begin;
if (envelope_size > UINT64_MAX - (DOWNLOAD_ALIGNMENT - 1)) {
EXIT("BufferCache: download batch alignment overflow\n");
}
const auto aligned_size = AlignDownload(envelope_size);
if (aligned_size > UINT64_MAX - reservation_size) {
EXIT("BufferCache: download batch overflow\n");
}
reservation_size += aligned_size;
}
if (reservation_size == 0) {
return {};
}
auto& download = m_download_buffer;
const auto [mapped, base_offset] = download.Map(reservation_size, DOWNLOAD_ALIGNMENT);
if (mapped == nullptr) {
EXIT("BufferCache: download batch could not reserve the shared stream\n");
}
std::vector<DownloadRange> downloads;
downloads.reserve(copies.size());
uint64_t cursor = 0;
for (const auto& copy: copies) {
const auto [source_begin, envelope_size] = DownloadEnvelope(copy);
const auto prefix = copy.source_offset - source_begin;
download.CopyFrom(m_scheduler.Current(), *copy.owner, source_begin, base_offset + cursor,
envelope_size, vk::AccessFlagBits::eMemoryWrite, vk::AccessFlags {},
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlagBits::eHostRead);
downloads.push_back({copy.address, copy.size, base_offset + cursor + prefix});
cursor += AlignDownload(envelope_size);
}
download.Commit();
return downloads;
}
void BufferCache::PublishDownloads(std::span<const DownloadRange> downloads) {
for (const auto& range: downloads) {
m_download_buffer.Invalidate(range.offset, range.size);
Libs::LibKernel::Memory::WriteBacking(
range.address, m_download_buffer.Mapped().data() + range.offset, range.size);
}
}
void BufferCache::QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire) {
if (copies.empty()) {
return;
}
auto downloads = RecordDownloads(copies);
m_scheduler.DeferOperation(
[this, downloads = std::move(downloads), retire = std::move(retire)]() mutable {
PublishDownloads(downloads);
for (const auto& range: downloads) {
m_gpu_modified_ranges.Subtract(range.address, range.size);
}
// ForEachDownloadRange reports full tracker pages, and every exact GPU-owned
// interval on those pages was downloaded and removed. Clearing the original
// query therefore cannot orphan a dirty sibling on an edge page.
m_memory_tracker.UnmarkRegionAsGpuModified(retire.address, retire.size);
if (m_memory_tracker.IsRegionGpuModified(retire.address, retire.size) ||
!m_gpu_modified_ranges.Intersections(retire.address, retire.size).empty()) {
EXIT("BufferCache: asynchronous garbage collection retained GPU ownership\n");
}
m_memory_tracker.UntrackMemory(retire.address, retire.size);
});
}
BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
PageManager& page_manager, TextureCache& texture_cache)
: m_graphics(graphics), m_scheduler(scheduler),
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
m_memory_tracker(page_manager),
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
m_texture_cache(texture_cache) {
std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size()));
m_gds_buffer.Flush(0, m_gds_buffer.Size());
if (!m_graphics.CanReportMemoryUsage()) {
return;
}
constexpr int64_t GiB = 1024ll * 1024 * 1024;
constexpr int64_t target_threshold = 8 * GiB;
const auto budget =
static_cast<int64_t>(std::min<uint64_t>(m_graphics.GetTotalMemoryBudget(), INT64_MAX));
const auto threshold = std::min(budget, target_threshold);
const auto expected = std::min(budget - 6 * threshold / 10, budget - GiB);
const auto critical = std::min(budget - 2 * threshold / 10, budget - GiB / 2);
m_trigger_gc_memory = static_cast<uint64_t>(std::max<int64_t>(expected, GiB));
m_critical_gc_memory = static_cast<uint64_t>(std::max<int64_t>(critical, 2 * GiB));
}
BufferCache::~BufferCache() {
if (!m_gpu_modified_ranges.Empty()) {
EXIT("BufferCache: destroyed with pending GPU-modified ranges\n");
}
for (const auto& [vaddr, cached]: m_buffers) {
(void)vaddr;
if (m_memory_tracker.IsRegionGpuModified(cached->vaddr, cached->size)) {
EXIT("BufferCache: destroyed with GPU-modified buffer\n");
}
}
m_buffers.clear();
}
StreamBuffer& BufferCache::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");
}
BufferBinding BufferCache::UploadTransient(const void* data, uint64_t size, uint64_t alignment) {
EXIT_IF(data == nullptr || size == 0);
if (auto [mapped, offset] = m_stream_buffer.Map(size, alignment, false); mapped != nullptr) {
std::memcpy(mapped, data, static_cast<size_t>(size));
m_stream_buffer.Commit();
return {{}, m_stream_buffer.Handle(), offset};
}
auto owner =
std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::Upload, 0, AllFlags, size);
owner->Write(0, data, size);
return {owner, owner->Handle(), 0};
}
void BufferCache::InvalidateMemory(uint64_t vaddr, uint64_t size) {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: invalid memory-invalidation range\n");
}
m_memory_tracker.InvalidateRegion(vaddr, size,
[this, vaddr, size] { ReadMemory(vaddr, size, true); });
}
void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size, bool is_write) {
if (!GuestGpu::IsGpuThread() && CommandScheduler::InDeferredOperation()) {
EXIT("unsupported buffer readback from an asynchronous GPU completion, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
m_scheduler.Context().GetGpu().SendCommandSync(
[this, vaddr, size, is_write] { ReadMemoryOnGpu(vaddr, size, is_write); });
}
void BufferCache::ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write) {
// CPU invalidation reaches this point only for a GPU-owned tracker page. Resolve the exact
// Buffer owner on the GPU thread so the cache index remains single-thread-owned.
if (is_write && !IsRegionRegistered(vaddr, size)) {
return;
}
std::vector<DownloadCopy> copies;
m_memory_tracker.ForEachDownloadRange<false>(
vaddr, size,
[&](uint64_t address, uint64_t bytes) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
"memory invalidation");
},
[&](uint64_t address, uint64_t bytes) noexcept {
for (const auto range: m_gpu_modified_ranges.Intersections(address, bytes)) {
for (uint64_t copied = 0; copied < range.size;) {
const auto copy_address = range.address + copied;
auto owner = m_buffers.upper_bound(copy_address);
if (owner == m_buffers.begin()) {
EXIT("BufferCache: invalidation readback has no buffer owner\n");
}
auto& cached = *std::prev(owner)->second;
if (!cached.buffer->IsInBounds(copy_address, 1)) {
EXIT("BufferCache: invalidation readback is outside its buffer owner\n");
}
const auto copy_size =
std::min(range.size - copied, cached.vaddr + cached.size - copy_address);
copies.push_back({cached.buffer, cached.buffer->Offset(copy_address),
copy_address, copy_size});
copied += copy_size;
}
}
});
if (copies.empty()) {
if (!is_write) {
return;
}
// A preceding read fault can consume the last GPU-owned copy after this write invalidation
// has already chosen to flush. Complete the CPU ownership handoff even though this callback
// no longer has bytes to download.
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
return;
}
auto downloads = RecordDownloads(copies);
m_scheduler.FinishCurrent();
PublishDownloads(downloads);
for (const auto& range: downloads) {
m_gpu_modified_ranges.Subtract(range.address, range.size);
}
// The enumeration above covered whole dirty pages and every exact interval on them.
m_memory_tracker.UnmarkRegionAsGpuModified(vaddr, size);
if (is_write) {
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
}
}
void BufferCache::UnmapMemory(uint64_t vaddr, uint64_t size) {
if (vaddr == 0 || size == 0 || size > UINT64_MAX - vaddr) {
EXIT("BufferCache: invalid unmap range\n");
}
std::vector<DownloadCopy> copies;
std::vector<std::pair<uint64_t, uint64_t>> modified_buffers;
std::vector<std::pair<uint64_t, uint64_t>> retired_buffers;
for (const auto& [begin, cached]: m_buffers) {
if (vaddr < begin + cached->size && begin < vaddr + size) {
retired_buffers.emplace_back(begin, cached->size);
}
}
for (const auto& [begin, cached]: m_buffers) {
if (vaddr >= begin + cached->size || begin >= vaddr + size ||
!m_memory_tracker.IsRegionGpuModified(begin, cached->size)) {
continue;
}
const auto dirty = m_gpu_modified_ranges.Intersections(begin, cached->size);
if (dirty.empty()) {
EXIT("BufferCache: GPU-modified buffer has no dirty ranges\n");
}
modified_buffers.emplace_back(begin, cached->size);
}
for (const auto& [begin, bytes]: modified_buffers) {
auto owner = m_buffers.find(begin);
if (owner == m_buffers.end() || owner->second->size != bytes) {
EXIT("BufferCache: unmap owner changed during collection\n");
}
auto& cached = *owner->second;
m_memory_tracker.ForEachDownloadRange<false>(
begin, cached.size,
[&](uint64_t address, uint64_t bytes) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
"unmap");
},
[&](uint64_t address, uint64_t bytes) noexcept {
for (const auto& range: m_gpu_modified_ranges.Intersections(address, bytes)) {
copies.push_back(
{cached.buffer, range.address - begin, range.address, range.size});
}
});
}
if (!copies.empty()) {
auto downloads = RecordDownloads(copies);
m_scheduler.FinishCurrent();
PublishDownloads(downloads);
} else if (!retired_buffers.empty()) {
// Image uploads can reference a clean cached buffer without owning it. Submit the active
// command stream before removing such backing.
m_scheduler.FinishCurrent();
}
for (const auto& [begin, bytes]: modified_buffers) {
m_gpu_modified_ranges.Subtract(begin, bytes);
m_memory_tracker.UnmarkRegionAsGpuModified(begin, bytes);
}
for (const auto& [begin, bytes]: retired_buffers) {
m_memory_tracker.MarkRegionAsCpuModified(begin, bytes);
}
if (!m_gpu_modified_ranges.Intersections(vaddr, size).empty()) {
EXIT("BufferCache: unmap retained dirty byte ranges\n");
}
m_memory_tracker.UntrackMemory(vaddr, size);
for (auto it = m_buffers.begin(); it != m_buffers.end();) {
if (vaddr < it->first + it->second->size && it->first < vaddr + size) {
if (it->second->size > m_total_used_memory) {
EXIT("BufferCache: allocation accounting underflow\n");
}
m_total_used_memory -= it->second->size;
it = m_buffers.erase(it);
} else {
++it;
}
}
}
BufferCache::CachedBuffer& BufferCache::GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr,
uint64_t size) {
const auto begin = AlignDown(vaddr);
const auto end = AlignUp(vaddr + size);
const auto next = m_buffers.upper_bound(vaddr);
if (next != m_buffers.begin()) {
auto owner = std::prev(next);
if (owner->second->buffer->IsInBounds(vaddr, size)) {
owner->second->tick_accessed_last = m_gc_tick;
return *owner->second;
}
}
CacheRange merged {.address = begin, .size = end - begin};
using Iterator = decltype(m_buffers.begin());
std::vector<Iterator> overlaps;
auto first = m_buffers.lower_bound(begin);
if (first != m_buffers.begin()) {
auto previous = std::prev(first);
if (ResolveOverlap(merged, {previous->second->vaddr, previous->second->size})) {
first = previous;
}
}
for (auto candidate = first; candidate != m_buffers.end(); ++candidate) {
if (candidate->first >= merged.address + merged.size) {
break;
}
if (ResolveOverlap(merged, {candidate->second->vaddr, candidate->second->size})) {
overlaps.push_back(candidate);
}
}
auto cached = std::make_unique<CachedBuffer>();
cached->vaddr = merged.address;
cached->size = merged.size;
cached->tick_accessed_last = m_gc_tick;
cached->buffer = std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::DeviceLocal,
merged.address, AllFlags, merged.size);
for (const auto overlap: overlaps) {
const auto& old = *overlap->second;
cached->buffer->CopyFrom(command, *old.buffer, 0, old.vaddr - cached->vaddr, old.size);
command.RetainResourceUntilFence(old.buffer);
}
for (const auto overlap: overlaps) {
if (overlap->second->size > m_total_used_memory) {
EXIT("BufferCache: allocation accounting underflow\n");
}
m_total_used_memory -= overlap->second->size;
m_buffers.erase(overlap);
}
m_total_used_memory += cached->size;
return *m_buffers.emplace(cached->vaddr, std::move(cached)).first->second;
}
bool BufferCache::SynchronizeBuffer(CommandBuffer& command, Buffer& buffer, uint64_t vaddr,
uint64_t size, bool is_written, bool is_texel_buffer) {
std::vector<std::pair<uint64_t, uint64_t>> uploads;
m_memory_tracker.ForEachUploadRange(
vaddr, size, is_written,
[&](uint64_t address, uint64_t bytes) noexcept { uploads.emplace_back(address, bytes); },
[&]() noexcept {
for (const auto& [address, bytes]: uploads) {
Upload(command, buffer, buffer.Offset(address),
reinterpret_cast<const void*>(address), bytes);
}
});
if (is_texel_buffer && !is_written) {
return SynchronizeBufferFromImage(buffer, vaddr, size);
}
return false;
}
BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
bool is_written, bool is_read, bool is_formatted) {
if (command.IsInvalid() || command.IsExecute()) {
EXIT("BufferCache: buffer request requires a recording command buffer\n");
}
if (is_read && !is_written && size <= CACHING_PAGE_SIZE &&
!m_memory_tracker.IsRegionGpuModified(vaddr, size) &&
m_memory_tracker.IsRegionCpuModified(vaddr, size)) {
const auto alignment = std::max<uint64_t>(
m_graphics.physical_device_properties.limits.minUniformBufferOffsetAlignment, 1);
if (auto [mapped, offset] = m_stream_buffer.Map(size, alignment, false);
mapped != nullptr) {
if (Libs::LibKernel::Memory::TryReadBacking(vaddr, mapped, size)) {
m_stream_buffer.Commit();
return {{}, m_stream_buffer.Handle(), offset};
}
} else {
auto owner = std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::Upload, 0,
AllFlags, size);
if (Libs::LibKernel::Memory::TryReadBacking(vaddr, owner->Mapped().data(), size)) {
owner->Flush(0, size);
return {owner, owner->Handle(), 0};
}
}
}
if (is_formatted && is_written) {
(void)m_texture_cache.InvalidateMemoryFromGPU(vaddr, size, true);
}
auto& cached = GetOrCreateBuffer(command, vaddr, size);
(void)SynchronizeBuffer(command, *cached.buffer, vaddr, size, is_written,
is_formatted && is_read);
if (is_written) {
m_gpu_modified_ranges.Add(vaddr, size);
}
auto owner = cached.buffer;
uint64_t offset = cached.buffer->Offset(vaddr);
return {owner, owner->Handle(), offset};
}
std::shared_ptr<Buffer> BufferCache::ObtainNullBuffer() {
if (m_null_buffer != nullptr) {
return m_null_buffer;
}
m_null_buffer = std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::DeviceLocal, 0,
AllFlags, 16);
const std::array<uint8_t, 16> zeros {};
Upload(m_scheduler.Current(), *m_null_buffer, 0, zeros.data(), zeros.size());
return m_null_buffer;
}
ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t size) {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: invalid image source\n");
}
auto find_owner = [&]() -> CachedBuffer* {
auto owner = m_buffers.upper_bound(vaddr);
if (owner == m_buffers.begin()) {
return nullptr;
}
--owner;
return owner->second->buffer->IsInBounds(vaddr, size) ? owner->second.get() : nullptr;
};
{
const bool cpu_modified = m_memory_tracker.IsRegionCpuModified(vaddr, size);
const bool gpu_modified = m_memory_tracker.IsRegionGpuModified(vaddr, size);
const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size);
const bool has_dirty_buffer_source = !dirty.empty();
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size,
"image source");
auto* owner = find_owner();
if (has_dirty_buffer_source && owner == nullptr) {
if (!IsRegionRegistered(vaddr, size)) {
EXIT("BufferCache: GPU-dirty image source has no native buffer\n");
}
auto& cached = GetOrCreateBuffer(m_scheduler.Current(), vaddr, size);
owner = &cached;
const auto resolved = m_buffers.find(cached.vaddr);
if (resolved == m_buffers.end() || resolved->second.get() != &cached ||
!cached.buffer->IsInBounds(vaddr, size)) {
EXIT("BufferCache: merged image source does not contain the requested range\n");
}
}
if (owner != nullptr && !cpu_modified && (!gpu_modified || has_dirty_buffer_source)) {
owner->tick_accessed_last = m_gc_tick;
return {owner->buffer.get(), owner->buffer->Offset(vaddr)};
}
if (has_dirty_buffer_source && owner == nullptr) {
EXIT("BufferCache: GPU-dirty image source could not resolve its native owner\n");
}
}
auto [staging, stage_offset] = m_staging_buffer.Map(size, 16);
if (staging == nullptr || (!Libs::LibKernel::Memory::TryReadBacking(vaddr, staging, size) &&
!Libs::LibKernel::Memory::TryReadPrtBacking(vaddr, staging, size))) {
EXIT("BufferCache: failed to read mapped guest image backing\n");
}
m_staging_buffer.Commit();
const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size);
const bool has_dirty_buffer_source = !dirty.empty();
auto* owner = find_owner();
if (has_dirty_buffer_source && owner == nullptr) {
EXIT("BufferCache: GPU-dirty image source lost its native owner\n");
}
if (owner == nullptr ||
(m_memory_tracker.IsRegionGpuModified(vaddr, size) && !has_dirty_buffer_source)) {
return {&m_staging_buffer, stage_offset};
}
auto& cached = *owner;
cached.tick_accessed_last = m_gc_tick;
std::vector<std::pair<uint64_t, uint64_t>> uploads;
m_memory_tracker.ForEachUploadRange(
vaddr, size, false,
[&](uint64_t address, uint64_t upload_size) noexcept {
uploads.emplace_back(address, upload_size);
},
[&]() noexcept {
for (const auto& [address, upload_size]: uploads) {
cached.buffer->CopyFrom(
m_scheduler.Current(), m_staging_buffer, stage_offset + address - vaddr,
cached.buffer->Offset(address), upload_size, vk::AccessFlagBits::eHostWrite);
}
});
return {cached.buffer.get(), cached.buffer->Offset(vaddr)};
}
void BufferCache::WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data) {
if (vaddr == 0 || data.empty() || data.size() > UINT64_MAX - vaddr) {
EXIT("BufferCache: invalid host DMA write\n");
}
Libs::LibKernel::Memory::WriteBacking(vaddr, data.data(), data.size());
const auto end = vaddr + data.size();
for (auto& [address, cached]: m_buffers) {
const auto cached_end = address + cached->size;
const auto begin = std::max(vaddr, address);
const auto range_end = std::min(end, cached_end);
if (begin >= range_end) {
continue;
}
Upload(m_scheduler.Current(), *cached->buffer, cached->buffer->Offset(begin),
data.data() + begin - vaddr, range_end - begin);
cached->tick_accessed_last = m_gc_tick;
}
}
void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds) {
if ((vaddr & 3u) != 0 || size == 0 || (size & 3u) != 0 || size > UINT64_MAX - vaddr) {
EXIT("BufferCache: fill range must be dword aligned\n");
}
if (is_gds) {
if (vaddr > m_gds_buffer.Size() || size > m_gds_buffer.Size() - vaddr) {
EXIT("BufferCache: GDS fill range is out of bounds\n");
}
m_gds_buffer.Fill(vaddr, size, value);
return;
}
if (vaddr == 0) {
EXIT("BufferCache: invalid fill memory address\n");
}
(void)m_texture_cache.ClearMeta(vaddr);
{
const auto region = m_texture_cache.QueryRegion(vaddr, size);
if (!HasGpuDirtyBytes(vaddr, size) && !region.gpu_image_bytes) {
if (region.image_bytes) {
m_texture_cache.InvalidateMemory(vaddr, size);
}
std::array<uint32_t, 4096> values;
values.fill(value);
const std::span<const uint8_t> bytes {reinterpret_cast<const uint8_t*>(values.data()),
sizeof(values)};
for (uint64_t offset = 0; offset < size;) {
const auto chunk = std::min<uint64_t>(size - offset, bytes.size());
WriteHostMemory(vaddr + offset, bytes.first(chunk));
offset += chunk;
}
return;
}
}
auto& command = m_scheduler.Current();
auto dst = ObtainBuffer(command, vaddr, size, true, false, true);
EXIT_IF(dst.buffer == nullptr || dst.owner == nullptr);
command.RetainResourceUntilFence(dst.owner);
auto owner = std::static_pointer_cast<Buffer>(dst.owner);
owner->Fill(dst.offset, size, value);
}
void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds,
bool src_gds) {
const bool dst_memory = !dst_gds;
const bool src_memory = !src_gds;
if ((dst_memory && dst_vaddr == 0) || (src_memory && src_vaddr == 0) || size == 0 ||
((dst_gds || src_gds) && ((dst_vaddr | src_vaddr | size) & 3u) != 0) ||
size > UINT64_MAX - dst_vaddr || size > UINT64_MAX - src_vaddr || (dst_gds && src_gds) ||
(dst_gds && (dst_vaddr > m_gds_buffer.Size() || size > m_gds_buffer.Size() - dst_vaddr)) ||
(src_gds && (src_vaddr > m_gds_buffer.Size() || size > m_gds_buffer.Size() - src_vaddr))) {
EXIT("BufferCache: invalid copy range, src=0x%016" PRIx64 " dst=0x%016" PRIx64
" size=0x%016" PRIx64 " src_gds=%d dst_gds=%d\n",
src_vaddr, dst_vaddr, size, static_cast<int>(src_gds), static_cast<int>(dst_gds));
}
if (src_memory || dst_memory) {
const auto src_region =
src_memory ? m_texture_cache.QueryRegion(src_vaddr, size) : TextureCache::RegionInfo {};
const auto dst_region =
dst_memory ? m_texture_cache.QueryRegion(dst_vaddr, size) : TextureCache::RegionInfo {};
if (src_memory && dst_memory && !HasGpuDirtyBytes(src_vaddr, size) &&
!HasGpuDirtyBytes(dst_vaddr, size) && !src_region.gpu_image_bytes &&
!dst_region.gpu_image_bytes) {
if (dst_region.image_bytes) {
m_texture_cache.InvalidateMemory(dst_vaddr, size);
}
std::array<uint8_t, 64 * 1024> bytes;
for (uint64_t offset = 0; offset < size;) {
const auto chunk = std::min<uint64_t>(size - offset, bytes.size());
if (!Libs::LibKernel::Memory::TryReadBacking(src_vaddr + offset, bytes.data(),
chunk)) {
EXIT("BufferCache: host DMA source has no direct backing\n");
}
WriteHostMemory(dst_vaddr + offset, std::span {bytes}.first(chunk));
offset += chunk;
}
return;
}
}
auto& command = m_scheduler.Current();
auto src = src_memory ? ObtainBuffer(command, src_vaddr, size, false, true, true)
: BufferBinding {.buffer = m_gds_buffer.Handle(), .offset = src_vaddr};
auto dst = dst_memory ? ObtainBuffer(command, dst_vaddr, size, true, false, true)
: BufferBinding {.buffer = m_gds_buffer.Handle(), .offset = dst_vaddr};
EXIT_IF(src.buffer == nullptr || dst.buffer == nullptr || (dst_memory && dst.owner == nullptr));
if (src.owner != nullptr) {
command.RetainResourceUntilFence(src.owner);
}
if (dst.owner != nullptr) {
command.RetainResourceUntilFence(dst.owner);
}
if (src.buffer == dst.buffer && src.offset < dst.offset + size &&
dst.offset < src.offset + size) {
EXIT("BufferCache: resolved Vulkan copy ranges overlap\n");
}
auto& source = src.owner != nullptr ? *std::static_pointer_cast<Buffer>(src.owner)
: src_gds ? m_gds_buffer
: m_stream_buffer;
auto& destination =
dst.owner != nullptr ? *std::static_pointer_cast<Buffer>(dst.owner) : m_gds_buffer;
if (source.Handle() != src.buffer || destination.Handle() != dst.buffer) {
EXIT("BufferCache: resolved copy owner does not match its Vulkan handle\n");
}
destination.CopyFrom(command, source, src.offset, dst.offset, size);
}
bool BufferCache::IsRegionRegistered(uint64_t vaddr, uint64_t size) {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: invalid registered-region query\n");
}
// Cached buffers are ordered and non-overlapping. The last buffer beginning before the query
// end is therefore the only possible intersection.
const auto candidate = m_buffers.lower_bound(vaddr + size);
if (candidate == m_buffers.begin()) {
return false;
}
const auto& [address, cached] = *std::prev(candidate);
return address + cached->size > vaddr;
}
bool BufferCache::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
return m_memory_tracker.IsRegionGpuModified(vaddr, size);
}
bool BufferCache::HasGpuDirtyBytes(uint64_t vaddr, uint64_t size) {
return !m_gpu_modified_ranges.Intersections(vaddr, size).empty();
}
bool BufferCache::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
return m_memory_tracker.IsRegionCpuModified(vaddr, size);
}
void BufferCache::RunGarbageCollector() {
const auto tick = m_gc_tick++;
if (m_graphics.CanReportMemoryUsage()) {
m_total_used_memory = m_graphics.GetDeviceMemoryUsage();
}
if (m_total_used_memory < m_trigger_gc_memory) {
return;
}
const bool aggressive = m_total_used_memory >= m_critical_gc_memory;
const uint64_t age = std::min<uint64_t>(aggressive ? 80 : 160, tick);
const size_t limit = aggressive ? 64 : 32;
std::vector<RetiredBuffer> retires;
std::vector<std::pair<RetiredBuffer, std::vector<DownloadCopy>>> dirty_retires;
{
std::vector<uint64_t> candidates;
for (const auto& [address, owner]: m_buffers) {
const auto& cached = *owner;
if (tick - std::min(tick, cached.tick_accessed_last) < age) {
continue;
}
candidates.push_back(address);
}
std::ranges::sort(candidates, [&](uint64_t left, uint64_t right) {
return m_buffers.at(left)->tick_accessed_last < m_buffers.at(right)->tick_accessed_last;
});
if (candidates.size() > limit) {
candidates.resize(limit);
}
for (const auto address: candidates) {
auto& cached = *m_buffers.at(address);
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, cached.vaddr,
cached.size, "garbage collection");
retires.push_back({address, cached.size, cached.buffer});
// GC runs immediately before submission. Preserve every source referenced by commands
// already recorded in the active batch.
m_scheduler.Current().RetainResourceUntilFence(cached.buffer);
}
for (const auto& retire: retires) {
if (!m_memory_tracker.IsRegionGpuModified(retire.address, retire.size)) {
continue;
}
auto& copies = dirty_retires.emplace_back(retire, std::vector<DownloadCopy> {}).second;
m_memory_tracker.ForEachDownloadRange<false>(
retire.address, retire.size,
[&](uint64_t address, uint64_t size) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, size,
"garbage collection");
},
[&](uint64_t address, uint64_t size) noexcept {
for (const auto range: m_gpu_modified_ranges.Intersections(address, size)) {
copies.push_back({retire.owner, range.address - retire.address,
range.address, range.size});
}
});
}
}
for (auto& [retire, copies]: dirty_retires) {
QueueGarbageDownload(copies, std::move(retire));
}
for (const auto& retire: retires) {
auto found = m_buffers.find(retire.address);
if (found == m_buffers.end() || found->second->size != retire.size ||
found->second->buffer != retire.owner) {
EXIT("BufferCache: garbage-collection owner changed during download\n");
}
if (!m_memory_tracker.IsRegionGpuModified(retire.address, retire.size)) {
m_memory_tracker.UntrackMemory(retire.address, retire.size);
}
if (retire.size > m_total_used_memory) {
EXIT("BufferCache: allocation accounting underflow\n");
}
m_total_used_memory -= retire.size;
m_buffers.erase(found);
}
}
} // namespace Libs::Graphics
@@ -3,14 +3,12 @@
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/streamBuffer.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include <map>
#include <memory>
#include <mutex>
#include <span>
#include <utility>
#include <vector>
@@ -21,7 +19,6 @@ struct GraphicContext;
class CommandBuffer;
class CommandScheduler;
class TextureCache;
class ResourceMutex;
struct BufferBinding {
std::shared_ptr<void> owner;
@@ -30,9 +27,8 @@ struct BufferBinding {
};
struct ImageBufferSource {
Buffer* buffer = nullptr;
uint64_t offset = 0;
bool gpu_owned = false;
Buffer* buffer = nullptr;
uint64_t offset = 0;
};
class BufferCache {
@@ -43,13 +39,13 @@ public:
}
BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
TextureCache& texture_cache, ResourceMutex& resource_mutex);
TextureCache& texture_cache);
~BufferCache();
KYTY_CLASS_NO_COPY(BufferCache);
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept;
void UnmapMemory(uint64_t vaddr, uint64_t size);
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);
@@ -60,23 +56,15 @@ public:
uint64_t alignment);
[[nodiscard]] std::shared_ptr<Buffer> ObtainNullBuffer();
[[nodiscard]] ImageBufferSource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
[[nodiscard]] std::pair<std::shared_ptr<Buffer>, uint64_t>
ObtainBufferForImageWrite(uint64_t vaddr, uint64_t size);
void DiscardGpuDirtyBytes(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);
[[nodiscard]] bool HasPageOverlap(uint64_t vaddr, uint64_t size);
// 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 InvalidateImageAliases(uint64_t vaddr, uint64_t size);
void BeginBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick);
void CompleteBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick);
[[nodiscard]] bool SynchronizeBacking(uint64_t vaddr, uint64_t size);
void PublishImageBuffer(uint64_t vaddr, uint64_t size);
void ValidateGpuAccess(uint64_t vaddr, uint64_t size, bool is_read, bool is_written) const;
void RunGarbageCollector();
void RunGarbageCollector();
private:
friend struct BufferCacheTestAccess;
@@ -89,51 +77,39 @@ private:
struct DownloadCopy;
struct DownloadRange;
struct RetiredBuffer;
struct FaultReadback;
struct PendingBackingPublication;
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
[[nodiscard]] static uint64_t AlignDown(uint64_t value) noexcept;
[[nodiscard]] static uint64_t AlignUp(uint64_t value);
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 bool PageOverlaps(uint64_t left, uint64_t left_size, uint64_t right,
uint64_t right_size) noexcept;
[[nodiscard]] static std::pair<uint64_t, uint64_t>
DownloadEnvelope(const DownloadCopy& copy);
[[nodiscard]] static bool ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept;
[[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);
[[nodiscard]] std::vector<DownloadRange>
RecordDownloads(std::span<const DownloadCopy> copies);
[[nodiscard]] bool SynchronizeBuffer(CommandBuffer& command, Buffer& buffer, uint64_t vaddr,
uint64_t size, bool is_written, bool is_texel_buffer);
[[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 RefreshInvalidatedRanges(CommandBuffer& command, CachedBuffer& cached, uint64_t vaddr,
uint64_t size, bool upload);
void DiscardGpuDirtyBytesLocked(uint64_t vaddr, uint64_t size, const char* operation);
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;
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;
std::unique_ptr<FaultReadback> m_fault_readback;
RangeSet m_gpu_modified_ranges;
RangeSet m_image_invalidated_ranges;
std::mutex m_publication_mutex;
std::vector<PendingBackingPublication> m_pending_backing_publications;
MemoryTracker m_memory_tracker;
StreamBuffer m_staging_buffer;
StreamBuffer m_stream_buffer;
StreamBuffer m_download_buffer;
StreamBuffer m_device_buffer;
PageManager& m_page_manager;
TextureCache& m_texture_cache;
ResourceMutex& m_resource_mutex;
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;
@@ -0,0 +1,85 @@
#include "graphics/host_gpu/renderer/cache/gpuResourceManager.h"
#include "common/assert.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
namespace Libs::Graphics {
GpuResourceManager::GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler)
: m_scheduler(scheduler), m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache),
m_texture_cache(graphics, scheduler, m_page_manager, m_buffer_cache) {}
GpuResourceManager::~GpuResourceManager() = default;
bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
constexpr uint64_t fault_size = 8;
if (!IsMapped(fault_vaddr, fault_size)) {
return false;
}
if (access == PageFaultAccess::Write) {
m_buffer_cache.InvalidateMemory(fault_vaddr, fault_size);
m_texture_cache.InvalidateMemory(fault_vaddr, fault_size);
} else {
m_buffer_cache.ReadMemory(fault_vaddr, fault_size);
}
return true;
}
bool GpuResourceManager::InvalidateMemory(uint64_t vaddr, uint64_t size) {
if (!IsMapped(vaddr, size)) {
return false;
}
m_buffer_cache.InvalidateMemory(vaddr, size);
m_texture_cache.InvalidateMemory(vaddr, size);
return true;
}
bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) {
return false;
}
std::shared_lock lock(m_mapped_ranges_mutex);
return m_mapped_ranges.Contains(vaddr, size);
}
void GpuResourceManager::MapMemory(uint64_t vaddr, uint64_t size) {
{
std::lock_guard lock(m_mapped_ranges_mutex);
m_mapped_ranges.Add(vaddr, size);
}
m_page_manager.OnGpuMap(vaddr, size);
}
void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported memory unmap from an asynchronous GPU completion, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
const auto unmap = [this, vaddr, size] {
if (m_scheduler.Active()) {
const auto tick = m_scheduler.CurrentTick();
m_scheduler.FinishCurrent();
m_scheduler.WaitPriorityOperations(tick);
}
m_buffer_cache.UnmapMemory(vaddr, size);
m_texture_cache.UnmapMemory(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size);
std::lock_guard lock(m_mapped_ranges_mutex);
m_mapped_ranges.Subtract(vaddr, size);
};
if (m_gpu == nullptr) {
unmap();
return;
}
m_gpu->SendCommandSync(unmap);
}
void GpuResourceManager::RunGarbageCollector() {
m_texture_cache.ProcessDownloadImages();
m_texture_cache.RunGarbageCollector();
m_buffer_cache.RunGarbageCollector();
}
} // namespace Libs::Graphics
@@ -4,16 +4,16 @@
#include "common/abi.h"
#include "common/common.h"
#include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/renderer/bufferCache.h"
#include "graphics/host_gpu/renderer/resourceMutex.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include <cstdint>
#include <shared_mutex>
namespace Libs::Graphics {
class CommandScheduler;
class Gpu;
class GuestGpu;
class GpuResourceManager {
public:
@@ -23,26 +23,23 @@ 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;
void PrepareHostWrite(uint64_t vaddr, uint64_t size);
[[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, GpuAccess access);
void UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess access);
void MapMemory(uint64_t vaddr, uint64_t size);
void UnmapMemory(uint64_t vaddr, uint64_t size);
void RunGarbageCollector();
private:
static bool FaultThunk(void* context, PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept;
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept;
PageManager m_page_manager;
ResourceMutex m_resource_mutex;
BufferCache m_buffer_cache;
TextureCache m_texture_cache;
Gpu* m_gpu = nullptr;
PageManager m_page_manager;
CommandScheduler& m_scheduler;
BufferCache m_buffer_cache;
TextureCache m_texture_cache;
mutable std::shared_mutex m_mapped_ranges_mutex;
RangeSet m_mapped_ranges;
GuestGpu* m_gpu = nullptr;
};
} // namespace Libs::Graphics
@@ -0,0 +1,272 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
#include "common/assert.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
namespace Libs::Graphics {
// Sparse two-level lookup for texture-cache page ownership.
// Entries are selected by guest page number; queries never allocate L1 buckets.
template <typename EntryT, size_t PageBits = 20, size_t AddressSpaceBits = 40,
size_t FirstLevelBits = 10>
class MultiLevelPageTable final {
public:
using Entry = EntryT;
static_assert(AddressSpaceBits < 64);
static_assert(PageBits < AddressSpaceBits);
static_assert(FirstLevelBits > 0 && FirstLevelBits <= AddressSpaceBits - PageBits);
static constexpr size_t kPageBits = PageBits;
static constexpr size_t kAddressSpaceBits = AddressSpaceBits;
static constexpr size_t kFirstLevelBits = FirstLevelBits;
static constexpr size_t kSecondLevelBits = AddressSpaceBits - FirstLevelBits - PageBits;
static constexpr size_t kFirstLevelEntries = size_t {1} << FirstLevelBits;
static constexpr size_t kBucketEntries = size_t {1} << kSecondLevelBits;
static constexpr size_t kPageCount = size_t {1} << (AddressSpaceBits - PageBits);
static constexpr uint64_t kAddressSpaceSize = uint64_t {1} << AddressSpaceBits;
struct PageRange final {
size_t first = 0;
size_t last_exclusive = 0;
};
MultiLevelPageTable(): m_first_level(kFirstLevelEntries) {}
[[nodiscard]] Entry* Find(size_t page) {
if (!IsValidPage(page)) {
return nullptr;
}
auto& bucket = m_first_level[FirstLevelIndex(page)];
return bucket == nullptr ? nullptr : &(*bucket)[SecondLevelIndex(page)];
}
[[nodiscard]] const Entry* Find(size_t page) const {
if (!IsValidPage(page)) {
return nullptr;
}
const auto& bucket = m_first_level[FirstLevelIndex(page)];
return bucket == nullptr ? nullptr : &(*bucket)[SecondLevelIndex(page)];
}
[[nodiscard]] Entry& GetOrCreate(size_t page) {
if (!IsValidPage(page)) {
EXIT("MultiLevelPageTable page is outside the guest address space");
}
auto& bucket = m_first_level[FirstLevelIndex(page)];
if (bucket == nullptr) {
bucket = std::make_unique<Bucket>();
++m_allocated_buckets;
}
return (*bucket)[SecondLevelIndex(page)];
}
[[nodiscard]] Entry& operator[](size_t page) { return GetOrCreate(page); }
[[nodiscard]] static constexpr bool IsValidPage(size_t page) { return page < kPageCount; }
// Returns the half-open page interval touched by a non-empty byte range.
// An end exactly at 2^AddressSpaceBits is valid; wrapping or crossing it is not.
[[nodiscard]] static constexpr bool TryGetPageRange(uint64_t address, uint64_t size,
PageRange& range) {
if (size == 0 || address >= kAddressSpaceSize || size > kAddressSpaceSize - address) {
return false;
}
const uint64_t end = address + size;
range.first = static_cast<size_t>(address >> PageBits);
range.last_exclusive = static_cast<size_t>(((end - 1) >> PageBits) + 1);
return true;
}
[[nodiscard]] size_t AllocatedBucketCount() const { return m_allocated_buckets; }
private:
using Bucket = std::array<Entry, kBucketEntries>;
[[nodiscard]] static constexpr size_t FirstLevelIndex(size_t page) {
return page >> kSecondLevelBits;
}
[[nodiscard]] static constexpr size_t SecondLevelIndex(size_t page) {
return page & (kBucketEntries - 1);
}
std::vector<std::unique_ptr<Bucket>> m_first_level;
size_t m_allocated_buckets = 0;
};
// Inline owner storage for page-table entries. Texture pages normally have only a
// handful of owners, so avoid a heap allocation on the first 16 registrations.
//
// Owners are packed into live uint64_t objects instead of being constructed in
// raw storage. This keeps sparse bucket construction cheap without relying on
// subtle implicit-lifetime or pointer-provenance rules.
template <typename OwnerT, size_t InlineCapacity = 16>
class InlinePageOwnerList final {
public:
using value_type = OwnerT;
using size_type = size_t;
class const_iterator final {
public:
[[nodiscard]] OwnerT operator*() const noexcept { return m_owners->At(m_index); }
const_iterator& operator++() noexcept {
++m_index;
return *this;
}
bool operator==(const const_iterator&) const = default;
private:
friend class InlinePageOwnerList;
const_iterator(const InlinePageOwnerList* owners, size_type index) noexcept
: m_owners(owners), m_index(index) {}
const InlinePageOwnerList* m_owners = nullptr;
size_type m_index = 0;
};
static_assert(InlineCapacity > 0);
static_assert(std::is_default_constructible_v<OwnerT>);
static_assert(std::is_trivially_copyable_v<OwnerT>);
static_assert(std::has_unique_object_representations_v<OwnerT>);
static_assert(sizeof(OwnerT) <= sizeof(uint64_t));
InlinePageOwnerList() noexcept {}
InlinePageOwnerList(const InlinePageOwnerList&) = delete;
InlinePageOwnerList& operator=(const InlinePageOwnerList&) = delete;
InlinePageOwnerList(InlinePageOwnerList&& other) noexcept
: m_overflow(std::move(other.m_overflow)),
m_inline_size(std::exchange(other.m_inline_size, 0)) {
if (m_overflow == nullptr) {
std::copy_n(other.m_inline.begin(), m_inline_size, m_inline.begin());
}
}
InlinePageOwnerList& operator=(InlinePageOwnerList&& other) noexcept {
if (this != &other) {
m_overflow = std::move(other.m_overflow);
m_inline_size = std::exchange(other.m_inline_size, 0);
if (m_overflow == nullptr) {
std::copy_n(other.m_inline.begin(), m_inline_size, m_inline.begin());
}
}
return *this;
}
[[nodiscard]] const_iterator begin() const noexcept { return {this, 0}; }
[[nodiscard]] const_iterator end() const noexcept { return {this, size()}; }
[[nodiscard]] bool empty() const noexcept { return size() == 0; }
[[nodiscard]] size_type size() const noexcept {
return m_overflow == nullptr ? m_inline_size : m_overflow->size();
}
[[nodiscard]] OwnerT front() const noexcept { return At(0); }
[[nodiscard]] OwnerT operator[](size_type index) const noexcept { return At(index); }
void push_back(const OwnerT& owner) {
const uint64_t packed = Pack(owner);
if (m_overflow != nullptr) {
m_overflow->push_back(packed);
return;
}
if (m_inline_size < InlineCapacity) {
m_inline[m_inline_size] = packed;
++m_inline_size;
return;
}
auto overflow = std::make_unique<std::vector<uint64_t>>();
overflow->reserve(InlineCapacity * 2);
overflow->assign(m_inline.begin(), m_inline.end());
overflow->push_back(packed);
m_overflow = std::move(overflow);
}
[[nodiscard]] bool Contains(const OwnerT& owner) const noexcept {
const uint64_t packed = Pack(owner);
if (m_overflow != nullptr) {
return std::find(m_overflow->begin(), m_overflow->end(), packed) != m_overflow->end();
}
return std::find(m_inline.begin(), m_inline.begin() + static_cast<ptrdiff_t>(m_inline_size),
packed) != m_inline.begin() + static_cast<ptrdiff_t>(m_inline_size);
}
[[nodiscard]] bool Erase(const OwnerT& owner) noexcept {
const uint64_t packed = Pack(owner);
if (m_overflow != nullptr) {
const auto found = std::find(m_overflow->begin(), m_overflow->end(), packed);
if (found == m_overflow->end()) {
return false;
}
m_overflow->erase(found);
if (m_overflow->size() == InlineCapacity) {
std::copy(m_overflow->begin(), m_overflow->end(), m_inline.begin());
m_inline_size = InlineCapacity;
m_overflow.reset();
}
return true;
}
const auto end = m_inline.begin() + static_cast<ptrdiff_t>(m_inline_size);
const auto found = std::find(m_inline.begin(), end, packed);
if (found == end) {
return false;
}
std::move(found + 1, end, found);
--m_inline_size;
return true;
}
template <typename Func>
void ForEach(Func&& func) const {
if (m_overflow != nullptr) {
for (const uint64_t owner: *m_overflow) {
func(Unpack(owner));
}
return;
}
for (size_type index = 0; index < m_inline_size; ++index) {
func(Unpack(m_inline[index]));
}
}
private:
[[nodiscard]] OwnerT At(size_type index) const noexcept {
return Unpack(m_overflow == nullptr ? m_inline[index] : (*m_overflow)[index]);
}
[[nodiscard]] static uint64_t Pack(const OwnerT& owner) noexcept {
uint64_t packed = 0;
std::memcpy(&packed, &owner, sizeof(owner));
return packed;
}
[[nodiscard]] static OwnerT Unpack(uint64_t packed) noexcept {
OwnerT owner {};
std::memcpy(&owner, &packed, sizeof(owner));
return owner;
}
std::array<uint64_t, InlineCapacity> m_inline;
std::unique_ptr<std::vector<uint64_t>> m_overflow;
size_type m_inline_size = 0;
};
// Removes only the requested owner. Other owners in the same page entry remain intact.
template <typename Container, typename Value>
[[nodiscard]] bool EraseExact(Container& owners, const Value& owner) {
const auto it = std::find(owners.begin(), owners.end(), owner);
if (it == owners.end()) {
return false;
}
owners.erase(it);
return true;
}
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
@@ -1,4 +1,4 @@
#include "graphics/host_gpu/renderer/samplerCache.h"
#include "graphics/host_gpu/renderer/cache/samplerCache.h"
#include "common/assert.h"
#include "common/logging/log.h"
@@ -56,7 +56,10 @@ vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
case Prospero::SamplerAnisoRatio::kFour: aniso_ratio = 4.0f; break;
case Prospero::SamplerAnisoRatio::kEight: aniso_ratio = 8.0f; break;
case Prospero::SamplerAnisoRatio::kSixteen: aniso_ratio = 16.0f; break;
default: EXIT("unknown ratio: %d\n", static_cast<int>(r.MaxAnisoRatio()));
default:
EXIT("unknown ratio: %d dwords=%08x,%08x,%08x,%08x\n",
static_cast<int>(r.MaxAnisoRatio()), r.fields[0], r.fields[1], r.fields[2],
r.fields[3]);
}
}
@@ -1,4 +1,4 @@
#include "graphics/host_gpu/renderer/streamBuffer.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include "common/assert.h"
#include "common/profiler.h"
@@ -135,8 +135,8 @@ void Buffer::Write(uint64_t offset, const void* source, uint64_t size) {
void Buffer::Flush(uint64_t offset, uint64_t size) {
EXIT_IF(m_mapped.empty() || offset > m_size || size > m_size - offset);
if (!m_is_coherent && size != 0) {
const auto result = vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.allocation,
offset, size);
const auto result =
vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
}
@@ -144,8 +144,8 @@ void Buffer::Flush(uint64_t offset, uint64_t size) {
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) {
EXIT("Buffer: invalid DMA barrier, handle=%p offset=0x%016" PRIx64
" size=0x%016" PRIx64 " capacity=0x%016" PRIx64 "\n",
EXIT("Buffer: invalid DMA barrier, handle=%p offset=0x%016" PRIx64 " size=0x%016" PRIx64
" capacity=0x%016" PRIx64 "\n",
static_cast<const void*>(Handle()), offset, size, m_size);
}
vk::BufferMemoryBarrier barrier {};
@@ -175,10 +175,9 @@ void Buffer::CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t sou
command.EndRendering();
const vk::BufferMemoryBarrier before[] = {
source.Barrier(source_offset, size, source_before, vk::AccessFlagBits::eTransferRead),
Barrier(destination_offset, size, destination_before,
vk::AccessFlagBits::eTransferWrite),
Barrier(destination_offset, size, destination_before, vk::AccessFlagBits::eTransferWrite),
};
const auto host_access = vk::AccessFlagBits::eHostRead | vk::AccessFlagBits::eHostWrite;
const auto host_access = vk::AccessFlagBits::eHostRead | vk::AccessFlagBits::eHostWrite;
auto before_stage = vk::PipelineStageFlags {vk::PipelineStageFlagBits::eAllCommands};
if (static_cast<bool>((source_before | destination_before) & host_access)) {
before_stage |= vk::PipelineStageFlagBits::eHost;
@@ -214,9 +213,8 @@ void Buffer::Fill(uint64_t offset, uint64_t size, uint32_t value) {
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion,
0, nullptr, 1, &before, 0, nullptr);
native.fillBuffer(Handle(), offset, size, value);
const auto after =
Barrier(offset, size, vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
const auto after = Barrier(offset, size, vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eAllCommands,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 1, &after, 0, nullptr);
@@ -250,8 +248,8 @@ std::pair<uint8_t*, uint64_t> StreamBuffer::Map(uint64_t size, uint64_t alignmen
if (Mapped().empty()) {
return {nullptr, 0};
}
uint64_t mapped_size = size;
const auto atom = Graphics().physical_device_properties.limits.nonCoherentAtomSize;
uint64_t mapped_size = size;
const auto atom = Graphics().physical_device_properties.limits.nonCoherentAtomSize;
if (!NormalizeReservation(IsCoherent(), atom, mapped_size, alignment)) {
return {nullptr, 0};
}
@@ -54,16 +54,15 @@ public:
[[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 CopyFrom(
CommandBuffer& command, const Buffer& source, uint64_t source_offset,
uint64_t destination_offset, uint64_t size,
vk::AccessFlags source_before = vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags destination_before =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags source_after =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags destination_after =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
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,
vk::AccessFlags destination_before = vk::AccessFlagBits::eMemoryRead |
vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags source_after = vk::AccessFlagBits::eMemoryRead |
vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags destination_after = vk::AccessFlagBits::eMemoryRead |
vk::AccessFlagBits::eMemoryWrite);
void Fill(uint64_t offset, uint64_t size, uint32_t value);
protected:
@@ -107,13 +106,13 @@ private:
uint64_t upper_bound = 0;
};
void ReserveWatches(std::vector<Watch>& watches, size_t grow_size);
void ReserveWatches(std::vector<Watch>& watches, size_t grow_size);
[[nodiscard]] static bool NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
uint64_t& alignment);
[[nodiscard]] bool WaitPendingOperations(const std::vector<Watch>& watches,
std::optional<size_t> invalidation_mark,
uint64_t requested_upper_bound, bool allow_wait,
size_t& wait_cursor, uint64_t& wait_bound);
[[nodiscard]] bool WaitPendingOperations(const std::vector<Watch>& watches,
std::optional<size_t> invalidation_mark,
uint64_t requested_upper_bound, bool allow_wait,
size_t& wait_cursor, uint64_t& wait_bound);
uint64_t m_offset = 0;
uint64_t m_mapped_size = 0;
@@ -4,17 +4,16 @@
#include "common/abi.h"
#include "common/common.h"
#include "common/lruCache.h"
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/renderer/blitHelper.h"
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/renderer/multiLevelPageTable.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 <compare>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <span>
#include <utility>
#include <vector>
@@ -25,7 +24,6 @@ class Buffer;
class BufferCache;
class CommandBuffer;
class CommandScheduler;
class ResourceMutex;
class RenderExecutor;
class StreamBuffer;
class TileManager;
@@ -48,11 +46,12 @@ public:
};
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
BufferCache& buffer_cache, ResourceMutex& resource_mutex);
BufferCache& buffer_cache);
~TextureCache();
KYTY_CLASS_NO_COPY(TextureCache);
[[nodiscard]] ImageId FindImage(ImageDesc& desc, bool exact_format = false);
void UpdateImage(ImageId id);
[[nodiscard]] ImageId FindImageFromRange(uint64_t address, uint64_t size,
bool ensure_valid = true);
[[nodiscard]] vk::ImageView FindTexture(ImageId id, const ImageDesc& desc);
@@ -64,8 +63,7 @@ public:
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size,
uint32_t packed_clear);
void PrepareHostWrite(uint64_t address, uint64_t size);
[[nodiscard]] bool SynchronizeImageToBuffer(uint64_t address, uint64_t size);
void InvalidateMemory(uint64_t address, uint64_t size);
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t address, uint64_t size,
bool formatted_buffer_write = false);
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
@@ -75,11 +73,9 @@ public:
[[nodiscard]] bool ClearMeta(uint64_t address);
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t address, uint64_t size,
PageFaultPhase phase) noexcept;
void UnmapMemory(uint64_t address, uint64_t size);
void ProcessDownloadImages();
void RunGarbageCollector();
void UnmapMemory(uint64_t address, uint64_t size);
void ProcessDownloadImages();
void RunGarbageCollector();
private:
enum class TransferDirection { Upload, Download };
@@ -101,7 +97,8 @@ private:
int32_t layer = -1;
};
using ImageOwnerIndex = MultiRangePageOwnerIndex<ImageId>;
using ImageIds = InlinePageOwnerList<ImageId, 16>;
using ImagePageTable = MultiLevelPageTable<ImageIds, 20, 40, 10>;
[[nodiscard]] Image& ResolveImage(ImageId id);
[[nodiscard]] const Image& ResolveImage(ImageId id) const;
@@ -109,20 +106,28 @@ private:
[[nodiscard]] ImageId InsertImage(const ImageInfo& info);
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
void RegisterImage(ImageId id);
void UnregisterImage(ImageId id, bool release_tracking);
void DeleteImage(ImageId id, bool release_tracking = true);
void DeleteImages(std::span<const ImageId> ids, std::optional<ImageId> native_source = {});
void RetainImage(CommandBuffer& command, ImageId id);
void TouchImage(Image& image);
void TrackImageDownload(ImageId id);
void TrackImageDownloadLocked(ImageId id, Image& image);
void UnregisterImage(ImageId id);
void DeleteImage(ImageId id);
void FreeImage(ImageId id);
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 MarkAsMaybeDirty(ImageId id, Image& image);
void TrackImageDownload(ImageId id);
void TrackImageDownloadLocked(ImageId id, Image& image);
[[nodiscard]] static bool SameBacking(const ImageInfo& cached, const ImageInfo& requested,
bool exact_format);
[[nodiscard]] static BindingType UploadBinding(const Image& image);
[[nodiscard]] bool SafeToDownload(const Image& image);
[[nodiscard]] std::vector<ImageId> FindImagesInRegion(uint64_t address, uint64_t size,
bool page_overlap) const;
// Caller holds m_lock; it also serializes the per-image query epoch.
[[nodiscard]] ImageIds FindImagesInRegion(uint64_t address, uint64_t size,
bool page_overlap) const;
[[nodiscard]] OverlapResult ResolveOverlap(const ImageInfo& requested, BindingType binding,
ImageId cached, ImageId merged);
[[nodiscard]] ImageId ResolveDepthOverlap(const ImageInfo& requested, BindingType binding,
@@ -135,7 +140,7 @@ private:
[[nodiscard]] DownloadPlan BuildDownload(const Image& image) const;
void UploadImage(Image& image, const ImageDesc& desc, Buffer& source, uint64_t source_offset);
void DownloadImageData(Image& image, Buffer& destination, uint64_t destination_offset,
DownloadPlan plan);
uint64_t destination_size, DownloadPlan plan);
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
void CommitGpuWrite(Image& image);
void PrepareImageCopy(Image& image);
@@ -148,10 +153,8 @@ private:
void ValidateImageDesc(const ImageDesc& desc) const;
void InvalidateCpuAliases(uint64_t address, uint64_t size);
void RestoreGpuTracking(const Image& image);
void ReleaseGpuTracking(ImageId id);
void ClearGpuModified(ImageId id);
[[nodiscard]] bool SynchronizeImageToBuffer(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);
@@ -160,14 +163,13 @@ private:
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
TrackingSpinLock m_lock;
MemoryTracker m_memory_tracker;
PageManager& m_page_manager;
BlitHelper m_blit_helper;
std::unique_ptr<TileManager> m_tiler;
BufferCache& m_buffer_cache;
ResourceMutex& m_resource_mutex;
std::vector<Slot> m_slots;
std::vector<uint32_t> m_free_slots;
ImageOwnerIndex m_image_owner_index;
ImagePageTable m_image_page_table;
std::map<vk::Format, ImageId> m_null_images;
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
std::set<ImageId> m_download_images;
@@ -175,11 +177,13 @@ private:
uint64_t m_total_used_memory = 0;
uint64_t m_trigger_gc_memory = 0;
uint64_t m_pressure_gc_memory = 1536ull * 1024 * 1024;
uint64_t m_critical_gc_memory = 3ull * 1024 * 1024 * 1024;
uint64_t m_gc_tick = 0;
bool m_readback_linear_images = false;
uint64_t m_critical_gc_memory = 3ull * 1024 * 1024 * 1024;
uint64_t m_gc_tick = 0;
mutable uint32_t m_image_query_epoch = 0;
bool m_readback_linear_images = false;
friend struct TextureCacheTestAccess;
friend class BufferCache;
friend class RenderExecutor;
};
@@ -7,9 +7,9 @@
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/descriptorCache.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"
@@ -23,10 +23,10 @@ static std::atomic<uint32_t> g_render_color_log_count = 0;
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderColorInfo& r,
uint32_t render_target_slice_offset,
uint32_t render_target_slot, bool ignore_target_mask,
bool exact_format) {
RenderColorInfo& r,
uint32_t render_target_slice_offset,
uint32_t render_target_slot, bool ignore_target_mask,
bool exact_format) {
KYTY_PROFILER_FUNCTION();
const auto& hw = buffer.GetRegisters();
@@ -79,10 +79,8 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
const auto view = ResolveTargetViewInfo(
rt.view.base_array_slice_index, rt.view.last_array_slice_index, render_target_slice_offset);
switch (view.type) {
case TargetViewType::Image2D: break;
case TargetViewType::Image2DArray:
EXIT("layered render-target views are unsupported: base=%u count=%u\n", view.base_layer,
view.layer_count);
case TargetViewType::Image2D:
case TargetViewType::Image2DArray: break;
case TargetViewType::Unsupported:
EXIT("invalid render-target view: base=%u last=%u draw_offset=%u\n",
rt.view.base_array_slice_index, rt.view.last_array_slice_index,
@@ -103,8 +101,9 @@ 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));
}
}
@@ -121,16 +120,31 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
uint32_t pitch = 0;
uint64_t size = 0;
bool tile = false;
const bool standard64 =
rt.attrib3.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB);
const bool volume = rt.attrib3.dimension == 2;
if (rt.attrib3.dimension != 1 && !volume) {
EXIT("unsupported render-target dimension: %u\n", rt.attrib3.dimension);
}
if (!volume && rt.attrib3.depth != 0) {
EXIT("2D render target has nonzero depth: %u\n", rt.attrib3.depth);
}
if (volume && samples != 1) {
EXIT("multisampled 3D render targets are unsupported\n");
}
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");
@@ -138,6 +152,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;
@@ -147,30 +164,42 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
if (bytes_per_element == 0) {
EXIT("render-target format has no valid element size\n");
}
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
const auto transfer_format = ImageOps::RenderTargetTransferFormat(bytes_per_element);
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) {
pitch = standard64
? TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float),
width, levels, rt.attrib3.tile_mode)
: TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
if (volume) {
pitch = TileGetTexturePitch(transfer_format, 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);
}
if (pitch == 0) {
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
}
@@ -178,16 +207,32 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
pitch = width;
}
TileSizeOffset mip_sizes[16] {};
TilePaddedSize mip_padded[16] {};
if (tile) {
TileSizeOffset mip_sizes[16] {};
TilePaddedSize mip_padded[16] {};
TileSurfaceLayout volume_layout {};
uint64_t backing_size = 0;
if (volume) {
const TileSurfaceDescription description {transfer_format,
rt.attrib3.tile_mode,
TileSurfaceDimension::Dim3D,
width,
height,
depth,
levels,
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, 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, pitch, 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,
@@ -205,12 +250,6 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
mip_padded[0] = {pitch, height};
}
if (rt.slice.slice_div64_minus1 != 0 &&
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u != size) {
EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64
"\n",
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u, size);
}
} else {
size = static_cast<uint64_t>(pitch) * height * bytes_per_element * samples;
if (size > UINT32_MAX) {
@@ -219,40 +258,68 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
mip_padded[0] = {pitch, height};
}
if (size == 0 || size > UINT64_MAX / view.image_layers) {
if (rt.slice.slice_div64_minus1 != 0 &&
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u != size) {
EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64
"\n",
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u, size);
}
if (size == 0 || (!volume && size > UINT64_MAX / view.image_layers)) {
EXIT("render-target memory footprint is invalid\n");
}
const auto backing_size = size * view.image_layers;
if (!volume) {
backing_size = size * view.image_layers;
}
if (backing_size == 0) {
EXIT("render-target backing is empty\n");
}
if (backing_size > TRACKER_ADDRESS_SIZE - rt.base.addr) {
EXIT("render-target backing range is invalid\n");
}
const vk::Extent2D view_extent = {std::max(width >> rt.view.current_mip_level, 1u),
std::max(height >> rt.view.current_mip_level, 1u)};
const uint32_t view_depth = std::max(depth >> rt.view.current_mip_level, 1u);
if (volume &&
(view.base_layer >= view_depth || view.layer_count > view_depth - view.base_layer)) {
EXIT("3D render-target view exceeds mip depth: base=%u count=%u depth=%u mip=%u\n",
view.base_layer, view.layer_count, view_depth, rt.view.current_mip_level);
}
auto decision_log_id = g_render_color_log_count.fetch_add(1);
if (decision_log_id < 128) {
LOGF("RenderColorTarget: slot=%" PRIu32 " addr=0x%010" PRIx64 " size=0x%016" PRIx64
" extent=%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u"
" 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, 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");
rt_slot, rt.base.addr, backing_size, width, height, depth, rt.view.current_mip_level,
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 {};
desc.type = TextureCache::BindingType::RenderTarget;
desc.info.data = {rt.base.addr, backing_size};
desc.info.pixel_format = target_format.format;
desc.info.guest_format = ImageOps::RenderTargetTransferFormat(bytes_per_element);
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, 1};
desc.info.resources = {levels, view.image_layers};
desc.info.pitch = pitch;
desc.info.guest_format = transfer_format;
desc.info.type = volume ? Prospero::ImageType::kColor3D : Prospero::ImageType::kColor2D;
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;
for (uint32_t level = 0; level < levels; level++) {
if (volume) {
const auto& mip = volume_layout.mips[level];
desc.info.mip_layout[level] = {
mip.offset,
mip.size,
mip.padded_width,
mip.padded_height,
};
continue;
}
const auto level_offset =
mip_sizes[level].src_size != 0 ? mip_sizes[level].src_offset : mip_sizes[level].offset;
const auto level_size =
@@ -275,20 +342,20 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
desc.view_info.base_layer = view.base_layer;
desc.view_info.layer_count = view.layer_count;
desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
auto& texture_cache = m_context.GetTextureCache();
r.desc = std::move(desc);
r.image_id = texture_cache.FindImage(r.desc, exact_format);
r.type = RenderColorType::RenderTexture;
r.base_addr = rt.base.addr;
r.image_view = nullptr;
r.format = r.desc.view_info.format;
r.extent = view_extent;
r.base_mip_level = rt.view.current_mip_level;
r.buffer_size = backing_size;
r.samples = samples;
r.export_mapping = target_format.export_mapping;
r.color_clear_enable = false;
r.color_clear_value = {};
auto& texture_cache = m_context.GetTextureCache();
r.desc = std::move(desc);
r.image_id = texture_cache.FindImage(r.desc, exact_format);
r.type = RenderColorType::RenderTexture;
r.base_addr = rt.base.addr;
r.image_view = nullptr;
r.format = r.desc.view_info.format;
r.extent = view_extent;
r.base_mip_level = rt.view.current_mip_level;
r.buffer_size = backing_size;
r.samples = samples;
r.export_mapping = target_format.export_mapping;
r.color_clear_enable = false;
r.color_clear_value = {};
BindRenderTarget(r.image_id);
}
@@ -2,8 +2,8 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COLORRENDERTARGET_H_
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <cstdint>
@@ -44,13 +44,13 @@ CommandSlot* CommandScheduler::CommandPool::CreateSlot() {
allocate.commandPool = m_pool;
allocate.level = vk::CommandBufferLevel::ePrimary;
allocate.commandBufferCount = 1;
vk::CommandBuffer buffer = nullptr;
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;
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");
@@ -70,9 +70,9 @@ CommandSlot* CommandScheduler::CommandPool::Allocate(GraphicContext& graphics) {
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;
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;
}
@@ -99,7 +99,10 @@ 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_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {
m_buffers.reserve(CommandBufferGrowStep);
m_buffer_ticks.reserve(CommandBufferGrowStep);
}
CommandScheduler::~CommandScheduler() {
Shutdown();
@@ -154,10 +157,7 @@ void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config
m_shaders = &shaders;
if (!Active()) {
for (auto& buffer: m_buffers) {
buffer = std::make_unique<RenderCommandBuffer>(*this);
}
m_current = 0;
m_current = static_cast<int>(GrowCommandBuffers());
}
BindCurrent();
@@ -235,7 +235,13 @@ void CommandScheduler::Wait(uint64_t tick) {
}
void CommandScheduler::PopPendingOperations() {
m_master.Refresh();
PopPendingOperations(true);
}
void CommandScheduler::PopPendingOperations(bool refresh_gpu_tick) {
if (refresh_gpu_tick) {
m_master.Refresh();
}
for (;;) {
Common::UniqueFunction<void> callback;
{
@@ -331,8 +337,7 @@ void CommandScheduler::WaitPriorityOperations(uint64_t tick) {
EXIT_IF(g_deferred_callback_scheduler == this);
std::unique_lock lock(m_operation_mutex);
m_operation_available.wait(lock, [this, tick] {
const bool active_before_or_at =
m_priority_active && m_priority_active_tick <= tick;
const bool active_before_or_at = m_priority_active && m_priority_active_tick <= tick;
const bool queued_before_or_at =
!m_priority_operations.empty() && m_priority_operations.front().tick <= tick;
return !active_before_or_at && !queued_before_or_at;
@@ -363,7 +368,7 @@ uint64_t CommandScheduler::NextSubmitSequence() noexcept {
}
void CommandScheduler::CheckActive() const {
EXIT_IF(!Active() || m_current >= BufferCount);
EXIT_IF(!Active() || static_cast<size_t>(m_current) >= m_buffers.size());
}
RenderCommandBuffer& CommandScheduler::Current() const {
@@ -385,15 +390,52 @@ CommandBuffer& CommandScheduler::SubmitCurrent(SubmitInfo& submit) {
const auto signal_tick = m_master.NextTick();
submit.AddSignal(m_master.Handle(), signal_tick);
submitted.Execute(submit);
m_recording = false;
m_buffer_ticks[static_cast<size_t>(m_current)] = signal_tick;
m_recording = false;
return submitted;
}
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);
}
}
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);
}
return first;
}
void CommandScheduler::BeginNext() {
EXIT_IF(m_recording);
m_current = (m_current + 1) % BufferCount;
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();
PopPendingOperations(!refreshed);
BindCurrent();
Current().Begin();
m_recording = true;
@@ -16,6 +16,7 @@
#include <queue>
#include <thread>
#include <vector>
namespace Libs::Graphics {
@@ -31,8 +32,6 @@ struct CommandSlot {
class CommandScheduler {
public:
static constexpr int BufferCount = 8;
CommandScheduler(RenderContext& context, GraphicContext& graphics);
~CommandScheduler();
KYTY_CLASS_NO_COPY(CommandScheduler);
@@ -47,24 +46,26 @@ public:
void FinishCurrent();
// Deferred callbacks can observe an externally owned drain, but cannot initiate shutdown:
// the priority runner cannot join itself.
void Shutdown();
void Wait(uint64_t tick);
void PopPendingOperations();
void DrainPriorityOperations();
void WaitPriorityOperations(uint64_t tick);
void DeferOperation(Common::UniqueFunction<void>&& operation);
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
void Shutdown();
void Wait(uint64_t tick);
void PopPendingOperations();
void DrainPriorityOperations();
void WaitPriorityOperations(uint64_t tick);
void DeferOperation(Common::UniqueFunction<void>&& operation);
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]] 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; }
private:
static constexpr size_t CommandBufferGrowStep = 4;
class CommandPool {
public:
CommandPool() = default;
@@ -91,33 +92,37 @@ private:
uint64_t tick = 0;
};
void BindCurrent() const;
CommandBuffer& SubmitCurrent(SubmitInfo& submit);
void BeginNext();
void PriorityOperationsThread(std::stop_token stop);
void RunOperation(Common::UniqueFunction<void>&& operation);
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;
MasterSemaphore m_master;
RenderContext& m_context;
GraphicContext& m_graphics;
CommandPool m_command_pool;
std::array<std::unique_ptr<RenderCommandBuffer>, BufferCount> m_buffers;
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;
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;
};
+14 -14
View File
@@ -8,8 +8,8 @@
#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/descriptorCache.h"
#include "graphics/host_gpu/renderer/imageView.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"
@@ -270,30 +270,30 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
colors[i].sType = vk::StructureType::eRenderingAttachmentInfo;
colors[i].imageView = attachment.image_view;
colors[i].imageLayout = attachment.image_layout;
colors[i].loadOp = attachment.is_clear ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad;
colors[i].storeOp = vk::AttachmentStoreOp::eStore;
colors[i].clearValue.color.uint32 = attachment.clear_value;
colors[i].loadOp =
attachment.is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
colors[i].storeOp = vk::AttachmentStoreOp::eStore;
colors[i].clearValue.color.uint32 = attachment.clear_value;
}
const auto& depth_stencil = state.depth_stencil_attachment;
const auto& depth_stencil = state.depth_stencil_attachment;
vk::RenderingAttachmentInfo depth {};
depth.sType = vk::StructureType::eRenderingAttachmentInfo;
depth.imageView = depth_stencil.image_view;
depth.imageLayout = depth_stencil.image_layout;
depth.loadOp = depth_stencil.depth_clear ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad;
depth.storeOp = vk::AttachmentStoreOp::eStore;
depth.loadOp =
depth_stencil.depth_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
depth.storeOp = vk::AttachmentStoreOp::eStore;
depth.clearValue.depthStencil.depth = std::bit_cast<float>(depth_stencil.clear_value[0]);
vk::RenderingAttachmentInfo stencil {};
stencil.sType = vk::StructureType::eRenderingAttachmentInfo;
stencil.imageView = depth_stencil.image_view;
stencil.imageLayout = depth_stencil.image_layout;
stencil.loadOp = depth_stencil.stencil_clear ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad;
stencil.storeOp = vk::AttachmentStoreOp::eStore;
stencil.clearValue.depthStencil.stencil = depth_stencil.clear_value[1];
stencil.loadOp =
depth_stencil.stencil_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
stencil.storeOp = vk::AttachmentStoreOp::eStore;
stencil.clearValue.depthStencil.stencil = depth_stencil.clear_value[1];
vk::RenderingInfo rendering {};
rendering.sType = vk::StructureType::eRenderingInfo;
+77 -98
View File
@@ -10,6 +10,7 @@
#include "graphics/host_gpu/renderer/render.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cmath>
#include <fmt/format.h>
@@ -70,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) {
@@ -140,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",
@@ -153,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(
@@ -167,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",
@@ -211,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) {
@@ -359,30 +363,13 @@ static void RtCheck(const HW::RenderTarget& rt) {
logged = true;
}
}
if (rt.attrib3.depth != 0x00000000) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: ignoring PS5 color target depth_minus1=0x%08" PRIx32
"\n",
rt.attrib3.depth);
logged = true;
}
}
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);
}
if (rt.attrib3.dimension != 0x00000001) {
static bool logged = false;
if (!logged) {
LOGF("RenderTarget: temporary: using 2D fallback for PS5 color "
"dimension=0x%08" PRIx32 "\n",
rt.attrib3.dimension);
logged = true;
}
}
if (!rt.attrib3.cmask_pipe_aligned) {
static bool logged = false;
if (!logged) {
@@ -473,13 +460,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,
@@ -497,18 +485,27 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
static void ZCheck(const HW::DepthRenderTarget& z) {
static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
const HW::RenderControl& rc) {
const bool depth_active =
dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable || rc.depth_clear_enable;
const bool stencil_active = dc.stencil_enable || rc.stencil_clear_enable;
if (!depth_active && !stencil_active) {
return;
}
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) {
@@ -522,16 +519,17 @@ static void ZCheck(const HW::DepthRenderTarget& z) {
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);
@@ -548,14 +546,6 @@ static void ZCheck(const HW::DepthRenderTarget& z) {
EXIT_NOT_IMPLEMENTED(z.htile_surface.prefetch_height != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.htile_surface.dst_outside_zero_to_one != 0x00000000);
if (z.depth_view.slice_start != 0x00000000 || z.depth_view.slice_max != 0x00000000) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("DepthTarget: temporary: ignoring PS5 array slice view start=0x%08" PRIx32
", max=0x%08" PRIx32 "\n",
z.depth_view.slice_start, z.depth_view.slice_max);
}
}
if (z.depth_view.current_mip_level != 0x00000000) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
@@ -574,7 +564,8 @@ static void ZCheck(const HW::DepthRenderTarget& z) {
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) {
if (z.stencil_info.format != Prospero::StencilFormat::kInvalid &&
z.stencil_write_base_addr == 0) {
LOGF("\t warning: stencil format is set without a stencil base address, continuing "
"without stencil attachment\n");
}
@@ -626,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);
}
@@ -651,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);
@@ -704,14 +683,6 @@ static void McCheck(const HW::ModeControl& c) {
}
EXIT_NOT_IMPLEMENTED(c.polymode_front_ptype != 0 && c.polymode_front_ptype != 2);
EXIT_NOT_IMPLEMENTED(c.polymode_back_ptype != 0 && c.polymode_back_ptype != 2);
if (c.poly_offset_front_enable || c.poly_offset_back_enable) {
static bool logged = false;
if (!logged) {
LOGF("\t temporary: PA_SU_SC_MODE_CNTL.POLY_OFFSET_*_ENABLE is not implemented; "
"continuing without depth bias\n");
logged = true;
}
}
if (c.vtx_window_offset_enable) {
static bool logged = false;
if (!logged) {
@@ -1085,18 +1056,24 @@ static ScissorRect ScissorRectClamp(ScissorRect r, uint32_t width, uint32_t heig
return r;
}
static constexpr std::array<uint16_t, 16> MakeScissorIntersectionRules() {
std::array<uint16_t, 16> rules {};
for (uint32_t candidate = 0; candidate < rules.size(); candidate++) {
for (uint32_t combination = 0; combination < 16; combination++) {
if ((combination & candidate) == candidate) {
rules[candidate] |= static_cast<uint16_t>(1u << combination);
}
}
}
return rules;
}
static bool ScissorClipRuleToIntersectionMask(uint16_t rule, uint8_t* mask) {
EXIT_IF(mask == nullptr);
for (uint32_t candidate = 0; candidate < 16; candidate++) {
uint16_t expected = 0;
for (uint32_t combination = 0; combination < 16; combination++) {
if ((combination & candidate) == candidate) {
expected |= static_cast<uint16_t>(1u << combination);
}
}
if (expected == rule) {
static constexpr auto rules = MakeScissorIntersectionRules();
for (uint32_t candidate = 0; candidate < rules.size(); candidate++) {
if (rules[candidate] == rule) {
*mask = static_cast<uint8_t>(candidate);
return true;
}
@@ -1214,7 +1191,7 @@ void hw_check(const RenderCommandBuffer& buffer) {
log_phase("vp");
VpCheck(vp, smc);
log_phase("z");
ZCheck(z);
ZCheck(z, d, rc);
log_phase("clip");
ClipCheck(c);
log_phase("rc");
@@ -1237,8 +1214,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 &&
@@ -1277,9 +1255,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());
+1 -1
View File
@@ -34,7 +34,7 @@ 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);
bool RenderIsColorTileModeLinear(Prospero::TileMode tile_mode);
void hw_print(const RenderCommandBuffer& buffer);
void hw_check(const RenderCommandBuffer& buffer);
void LogDrawPhase(const char* draw_name, const char* phase);
@@ -10,10 +10,10 @@
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/imageView.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"
@@ -38,7 +38,10 @@ namespace Libs::Graphics {
EXIT("unsupported render state; details were printed above\n");
}
static vk::StencilOp ConvertStencilOp(uint8_t value) {
static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t op_value) {
if (write_mask == 0) {
return vk::StencilOp::eKeep;
}
switch (static_cast<Prospero::StencilOp>(value)) {
case Prospero::StencilOp::kKeep: return vk::StencilOp::eKeep;
case Prospero::StencilOp::kZero: return vk::StencilOp::eZero;
@@ -49,14 +52,23 @@ static vk::StencilOp ConvertStencilOp(uint8_t value) {
case Prospero::StencilOp::kInvert: return vk::StencilOp::eInvert;
case Prospero::StencilOp::kAddWrap: return vk::StencilOp::eIncrementAndWrap;
case Prospero::StencilOp::kSubWrap: return vk::StencilOp::eDecrementAndWrap;
default: DepthFatal("unsupported stencil operation");
case Prospero::StencilOp::kXor:
if ((write_mask & op_value) == 0) {
return vk::StencilOp::eKeep;
}
if ((write_mask & ~op_value) != 0) {
DepthFatal("unsupported stencil XOR operands: write mask=0x%02" PRIx8
", operation value=0x%02" PRIx8,
write_mask, op_value);
}
return vk::StencilOp::eInvert;
default: DepthFatal("unsupported stencil operation: 0x%02" PRIx8, value);
}
}
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,
@@ -90,15 +102,16 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
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 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 = dc.stencil_enable || rc.stencil_clear_enable;
const bool stencil_active = has_stencil && (dc.stencil_enable || rc.stencil_clear_enable);
if (!depth_active && !stencil_active) {
return;
}
@@ -107,9 +120,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 &&
@@ -139,8 +151,6 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
}
return;
}
const bool has_stencil =
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
const bool has_htile = z.z_info.htile_acceleration;
const auto samples = render_sample_count(z.z_info.num_samples);
if (samples == 0) {
@@ -150,33 +160,30 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
has_stencil, has_htile, z.stencil_info.htile_stencil_disabled);
const auto view = ResolveTargetViewInfo(z.depth_view.slice_start, z.depth_view.slice_max);
switch (view.type) {
case TargetViewType::Image2D: break;
case TargetViewType::Image2DArray:
DepthFatal("layered depth views are unsupported: base=%u count=%u", view.base_layer,
view.layer_count);
case TargetViewType::Image2D:
case TargetViewType::Image2DArray: break;
case TargetViewType::Unsupported:
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
z.depth_view.slice_max);
}
if ((stencil_active && !has_stencil) || 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 ||
(z.z_read_base_addr & 0xffffu) != 0 ||
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.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 ||
if (z.stencil_info.format != 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) {
DepthFatal("unsupported stencil attachment state");
@@ -194,8 +201,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;
@@ -220,7 +225,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 ||
@@ -281,10 +286,12 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
r.depth_min_bounds = hw.GetDepthBoundsMin();
r.depth_max_bounds = hw.GetDepthBoundsMax();
r.stencil_clear_enable = rc.stencil_clear_enable;
r.stencil_clear_enable = has_stencil && rc.stencil_clear_enable;
r.stencil_clear_value = hw.GetStencilClearValue();
r.stencil_test_enable = dc.stencil_enable;
if (dc.stencil_enable) {
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;
if (dc.stencilfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
(dc.backface_enable &&
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways)) ||
@@ -296,19 +303,18 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
DepthFatal("unsupported stencil compare or replacement state");
}
r.stencil_static_front = {
ConvertStencilOp(sc.stencil_fail), ConvertStencilOp(sc.stencil_zpass),
ConvertStencilOp(sc.stencil_zfail), static_cast<vk::CompareOp>(dc.stencilfunc)};
r.stencil_dynamic_front = {sm.stencil_mask,
rc.stencil_clear_enable ? 0u : sm.stencil_writemask,
sm.stencil_testval};
ConvertStencilOp(sc.stencil_fail, front_write_mask, sm.stencil_opval),
ConvertStencilOp(sc.stencil_zpass, front_write_mask, sm.stencil_opval),
ConvertStencilOp(sc.stencil_zfail, front_write_mask, sm.stencil_opval),
static_cast<vk::CompareOp>(dc.stencilfunc)};
r.stencil_dynamic_front = {sm.stencil_mask, front_write_mask, sm.stencil_testval};
if (dc.backface_enable) {
r.stencil_static_back = {ConvertStencilOp(sc.stencil_fail_bf),
ConvertStencilOp(sc.stencil_zpass_bf),
ConvertStencilOp(sc.stencil_zfail_bf),
static_cast<vk::CompareOp>(dc.stencilfunc_bf)};
r.stencil_dynamic_back = {sm.stencil_mask_bf,
rc.stencil_clear_enable ? 0u : sm.stencil_writemask_bf,
sm.stencil_testval_bf};
r.stencil_static_back = {
ConvertStencilOp(sc.stencil_fail_bf, back_write_mask, sm.stencil_opval_bf),
ConvertStencilOp(sc.stencil_zpass_bf, back_write_mask, sm.stencil_opval_bf),
ConvertStencilOp(sc.stencil_zfail_bf, back_write_mask, sm.stencil_opval_bf),
static_cast<vk::CompareOp>(dc.stencilfunc_bf)};
r.stencil_dynamic_back = {sm.stencil_mask_bf, back_write_mask, sm.stencil_testval_bf};
} else {
r.stencil_static_back = r.stencil_static_front;
r.stencil_dynamic_back = r.stencil_dynamic_front;
@@ -333,7 +339,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;
@@ -2,9 +2,9 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_H_
#include "common/assert.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <cstdint>
@@ -1,141 +0,0 @@
#include "graphics/host_gpu/renderer/gpuResourceManager.h"
#include "common/assert.h"
#include "graphics/guest_gpu/command_processor/commandProcessor.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
namespace Libs::Graphics {
GpuResourceManager::GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler)
: m_page_manager(FaultThunk, this),
m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache, m_resource_mutex),
m_texture_cache(graphics, scheduler, m_page_manager, m_buffer_cache, m_resource_mutex) {}
GpuResourceManager::~GpuResourceManager() = default;
bool GpuResourceManager::FaultThunk(void* context, PageFaultAccess access, uint64_t vaddr,
uint64_t size, PageFaultPhase phase) noexcept {
return static_cast<GpuResourceManager*>(context)->InvalidateMemory(access, vaddr, size, phase);
}
bool GpuResourceManager::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept {
// Let the authoritative image materialize first. A clean overlapping buffer marks a write
// fault CPU-dirty when it begins ownership transfer; doing that before image preflight would
// make the image appear to race a real CPU write. Completion and release retain buffer-first
// ordering so its pending fault is gone before TextureCache publishes the downloaded backing.
if (phase == PageFaultPhase::Invalidate) {
const bool image_handled = m_texture_cache.InvalidateMemory(access, vaddr, size, phase);
const bool buffer_handled = m_buffer_cache.InvalidateMemory(access, vaddr, size, phase);
return buffer_handled || image_handled;
}
const bool buffer_handled = m_buffer_cache.InvalidateMemory(access, vaddr, size, phase);
const bool image_handled = m_texture_cache.InvalidateMemory(access, vaddr, size, phase);
return buffer_handled || image_handled;
}
bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
if (!m_page_manager.IsMapped(fault_vaddr, 1)) {
return false;
}
if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported guest-memory fault from an asynchronous GPU completion, "
"addr=0x%016" PRIx64 " access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
bool handled = false;
const auto resolve = [this, access, fault_vaddr, &handled](CommandProcessor& cp) {
cp.BeginReadbackTransaction();
(void)m_buffer_cache.SynchronizeBacking(fault_vaddr, 1);
{
ResourceMutex::FaultScope fault(m_resource_mutex);
handled = m_page_manager.HandleFault(access, fault_vaddr);
}
cp.EndReadbackTransaction();
};
if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) {
resolve(*cp);
return handled;
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("unsupported page fault from a pre-owned resource transaction, addr=0x%016" PRIx64
" access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
EXIT_IF(m_gpu == nullptr);
m_gpu->SendCommandSyncWithProcessor(resolve);
return handled;
}
void GpuResourceManager::PrepareHostWrite(uint64_t vaddr, uint64_t size) {
if (!m_page_manager.HasAnyMapping(vaddr, size)) {
return;
}
if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported host write from an asynchronous GPU completion, addr=0x%016" PRIx64
" size=0x%016" PRIx64 "\n",
vaddr, size);
}
const auto handle_range = [this, vaddr, size] {
if (!m_page_manager.HandleWriteRange(vaddr, size)) {
EXIT("failed to prepare host write, addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
};
const auto resolve = [this, &handle_range](CommandProcessor& cp) {
cp.BeginReadbackTransaction();
{
ResourceMutex::FaultScope fault(m_resource_mutex);
handle_range();
}
cp.EndReadbackTransaction();
};
if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) {
resolve(*cp);
return;
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("unsupported host write from a pre-owned resource transaction, addr=0x%016" PRIx64
" size=0x%016" PRIx64 "\n",
vaddr, size);
}
EXIT_IF(m_gpu == nullptr);
m_gpu->SendCommandSyncWithProcessor(resolve);
}
bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
return m_page_manager.IsMapped(vaddr, size);
}
void GpuResourceManager::MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access) {
m_page_manager.OnGpuMap(vaddr, size, access);
}
void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess access) {
if (!IsMapped(vaddr, size)) {
EXIT("cannot unmap an unmapped GPU resource range\n");
}
const auto unmap = [this, vaddr, size, access] {
m_texture_cache.UnmapMemory(vaddr, size);
m_buffer_cache.UnmapMemory(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size, access);
};
if (m_gpu == nullptr) {
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("cannot synchronously unmap from a resource transaction\n");
}
unmap();
return;
}
Gpu::SubmissionLock submissions(*m_gpu);
m_gpu->SendCommandSync(unmap);
}
void GpuResourceManager::RunGarbageCollector() {
m_texture_cache.ProcessDownloadImages();
m_texture_cache.RunGarbageCollector();
m_buffer_cache.RunGarbageCollector();
}
} // namespace Libs::Graphics
@@ -1,11 +1,11 @@
#include "graphics/host_gpu/renderer/blitHelper.h"
#include "graphics/host_gpu/renderer/image/blitHelper.h"
#include "common/assert.h"
#include "gpu_blit_shaders/gpu_blit_color_to_ms_depth_spv.h"
#include "gpu_blit_shaders/gpu_blit_fs_triangle_spv.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include <algorithm>
@@ -182,8 +182,8 @@ void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
auto command = command_buffer.Handle();
source.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {},
command);
destination.Transit(ColorToMsDepthLayout,
vk::AccessFlagBits2::eDepthStencilAttachmentWrite, {}, command);
destination.Transit(ColorToMsDepthLayout, vk::AccessFlagBits2::eDepthStencilAttachmentWrite, {},
command);
vk::RenderingAttachmentInfo depth_attachment {};
depth_attachment.sType = vk::StructureType::eRenderingAttachmentInfo;
@@ -1,11 +1,11 @@
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include "common/assert.h"
#include "common/profiler.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/renderer/streamBuffer.h"
#include "kernel/memory.h"
#include <algorithm>
@@ -99,12 +99,16 @@ vk::ImageAspectFlags Image::FullAspectMask(vk::Format format) noexcept {
}
}
Image::Barriers Image::GetBarriers(vk::ImageLayout destination_layout,
vk::AccessFlags2 destination_access,
vk::PipelineStageFlags2 destination_stage,
Image::Barriers Image::GetBarriers(vk::ImageLayout destination_layout,
vk::AccessFlags2 destination_access,
vk::PipelineStageFlags2 destination_stage,
std::optional<ImageSubresourceRange> range) {
auto& state = backing.state;
auto& subresource_states = backing.subresource_states;
if (range && info.IsVolume()) {
range->base_layer = 0;
range->layer_count = 1;
}
const bool partial =
range && (range->base_level != 0 || range->level_count != info.resources.levels ||
@@ -130,25 +134,25 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout destination_layout,
constexpr auto write_access = vk::AccessFlagBits2::eTransferWrite |
vk::AccessFlagBits2::eShaderWrite |
vk::AccessFlagBits2::eMemoryWrite;
const bool repeated_write =
const bool repeated_write =
static_cast<bool>(subresource_state.access_mask & write_access);
if (subresource_state.layout != destination_layout ||
subresource_state.access_mask != destination_access || repeated_write) {
vk::ImageMemoryBarrier2 barrier {};
barrier.srcStageMask = subresource_state.pl_stage;
barrier.srcAccessMask = subresource_state.access_mask;
barrier.dstStageMask = destination_stage;
barrier.dstAccessMask = destination_access;
barrier.oldLayout = subresource_state.layout;
barrier.newLayout = destination_layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = backing.image;
barrier.subresourceRange.aspectMask = FullAspectMask(backing.format);
barrier.subresourceRange.baseMipLevel = level;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = layer;
barrier.subresourceRange.layerCount = 1;
barrier.srcStageMask = subresource_state.pl_stage;
barrier.srcAccessMask = subresource_state.access_mask;
barrier.dstStageMask = destination_stage;
barrier.dstAccessMask = destination_access;
barrier.oldLayout = subresource_state.layout;
barrier.newLayout = destination_layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = backing.image;
barrier.subresourceRange.aspectMask = FullAspectMask(backing.format);
barrier.subresourceRange.baseMipLevel = level;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = layer;
barrier.subresourceRange.layerCount = 1;
barriers.push_back(barrier);
subresource_state = {destination_stage, destination_access, destination_layout};
}
@@ -159,10 +163,10 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout destination_layout,
subresource_states.clear();
}
} else {
constexpr auto write_access = vk::AccessFlagBits2::eTransferWrite |
vk::AccessFlagBits2::eShaderWrite |
vk::AccessFlagBits2::eMemoryWrite;
const bool repeated_write = static_cast<bool>(state.access_mask & write_access);
constexpr auto write_access = vk::AccessFlagBits2::eTransferWrite |
vk::AccessFlagBits2::eShaderWrite |
vk::AccessFlagBits2::eMemoryWrite;
const bool repeated_write = static_cast<bool>(state.access_mask & write_access);
if (state.layout == destination_layout && state.access_mask == destination_access &&
!repeated_write) {
return {};
@@ -191,8 +195,7 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout destination_layout,
}
void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access,
std::optional<ImageSubresourceRange> range,
vk::CommandBuffer command_buffer) {
std::optional<ImageSubresourceRange> range, vk::CommandBuffer command_buffer) {
const auto transfer_access =
vk::AccessFlagBits2::eTransferRead | vk::AccessFlagBits2::eTransferWrite;
vk::PipelineStageFlags2 destination_stage {};
@@ -201,8 +204,8 @@ void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destina
}
if (!destination_access ||
static_cast<bool>(destination_access & ~vk::AccessFlags2 {transfer_access})) {
destination_stage |= vk::PipelineStageFlagBits2::eAllGraphics |
vk::PipelineStageFlagBits2::eComputeShader;
destination_stage |=
vk::PipelineStageFlagBits2::eAllGraphics | vk::PipelineStageFlagBits2::eComputeShader;
}
const auto barriers =
GetBarriers(destination_layout, destination_access, destination_stage, range);
@@ -218,10 +221,9 @@ void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destina
command_buffer.pipelineBarrier2(dependency);
}
void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
uint64_t offset, uint64_t size) {
EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr ||
size == 0);
void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, uint64_t offset,
uint64_t size) {
EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr || size == 0);
m_scheduler->EndRendering();
vk::BufferMemoryBarrier2 buffer_barrier {};
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
@@ -234,16 +236,15 @@ void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffe
buffer_barrier.offset = offset;
buffer_barrier.size = size;
const auto image_barriers =
GetBarriers(vk::ImageLayout::eTransferDstOptimal,
vk::AccessFlagBits2::eTransferWrite,
GetBarriers(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
vk::PipelineStageFlagBits2::eCopy, {});
vk::DependencyInfo dependency {};
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &buffer_barrier;
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
dependency.pImageMemoryBarriers = image_barriers.data();
auto command = m_scheduler->Current().Handle();
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
dependency.pImageMemoryBarriers = image_barriers.data();
auto command = m_scheduler->Current().Handle();
command.pipelineBarrier2(dependency);
command.copyBufferToImage(buffer, backing.image, vk::ImageLayout::eTransferDstOptimal,
static_cast<uint32_t>(copies.size()), copies.data());
@@ -256,8 +257,7 @@ void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffe
dependency.pImageMemoryBarriers = nullptr;
command.pipelineBarrier2(dependency);
Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {},
command);
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, command);
}
void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
@@ -265,7 +265,7 @@ void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buf
EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr || size == 0);
m_scheduler->EndRendering();
vk::BufferMemoryBarrier2 buffer_barrier {};
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
buffer_barrier.srcAccessMask =
vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
buffer_barrier.dstStageMask = vk::PipelineStageFlagBits2::eCopy;
@@ -276,16 +276,15 @@ void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buf
buffer_barrier.offset = offset;
buffer_barrier.size = size;
const auto image_barriers =
GetBarriers(vk::ImageLayout::eTransferSrcOptimal,
vk::AccessFlagBits2::eTransferRead,
GetBarriers(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
vk::PipelineStageFlagBits2::eCopy, {});
vk::DependencyInfo dependency {};
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &buffer_barrier;
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
dependency.pImageMemoryBarriers = image_barriers.data();
auto command = m_scheduler->Current().Handle();
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
dependency.pImageMemoryBarriers = image_barriers.data();
auto command = m_scheduler->Current().Handle();
command.pipelineBarrier2(dependency);
command.copyImageToBuffer(backing.image, vk::ImageLayout::eTransferSrcOptimal, buffer,
static_cast<uint32_t>(copies.size()), copies.data());
@@ -299,11 +298,11 @@ void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buf
command.pipelineBarrier2(dependency);
}
std::pair<uint32_t, uint32_t>
Image::SanitizeCopyLayers(const Image& source, const Image& destination, uint32_t depth) {
const auto source_type = source.backing.image_type;
const auto destination_type = destination.backing.image_type;
uint32_t source_layers = source.backing.layers;
std::pair<uint32_t, uint32_t> Image::SanitizeCopyLayers(const Image& source,
const Image& destination, uint32_t depth) {
const auto source_type = source.backing.image_type;
const auto destination_type = destination.backing.image_type;
uint32_t source_layers = source.backing.layers;
uint32_t destination_layers = destination.backing.layers;
if (source_type == vk::ImageType::e3D) {
source_layers = 1;
@@ -312,13 +311,10 @@ Image::SanitizeCopyLayers(const Image& source, const Image& destination, uint32_
destination_layers = 1;
}
if (source_type == destination_type) {
source_layers = destination_layers =
std::min(source_layers, destination_layers);
} else if (source_type == vk::ImageType::e2D &&
destination_type == vk::ImageType::e3D) {
source_layers = destination_layers = std::min(source_layers, destination_layers);
} else if (source_type == vk::ImageType::e2D && destination_type == vk::ImageType::e3D) {
source_layers = depth;
} else if (source_type == vk::ImageType::e3D &&
destination_type == vk::ImageType::e2D) {
} else if (source_type == vk::ImageType::e3D && destination_type == vk::ImageType::e2D) {
destination_layers = depth;
}
return {source_layers, destination_layers};
@@ -327,12 +323,11 @@ Image::SanitizeCopyLayers(const Image& source, const Image& destination, uint32_
void Image::CopyImage(Image& source) {
EXIT_IF(m_scheduler == nullptr || source.backing.samples != backing.samples);
m_scheduler->EndRendering();
const uint32_t levels =
std::min(source.backing.mip_levels, backing.mip_levels);
const uint32_t levels = std::min(source.backing.mip_levels, backing.mip_levels);
const uint32_t base_depth = backing.image_type == vk::ImageType::e3D
? backing.extent.depth
: source.backing.extent.depth;
const auto source_aspect =
const auto source_aspect =
FullAspectMask(source.backing.format) & ~vk::ImageAspectFlagBits::eStencil;
const auto destination_aspect =
FullAspectMask(backing.format) & ~vk::ImageAspectFlagBits::eStencil;
@@ -342,8 +337,7 @@ void Image::CopyImage(Image& source) {
const auto width = std::max(source.backing.extent.width >> level, 1u);
const auto height = std::max(source.backing.extent.height >> level, 1u);
const auto depth = std::max(base_depth >> level, 1u);
const auto [source_layers, destination_layers] =
SanitizeCopyLayers(source, *this, depth);
const auto [source_layers, destination_layers] = SanitizeCopyLayers(source, *this, depth);
vk::ImageCopy copy {};
copy.srcSubresource = {source_aspect, level, 0, 1};
copy.dstSubresource = {destination_aspect, level, 0, 1};
@@ -351,8 +345,7 @@ void Image::CopyImage(Image& source) {
if (source.backing.image_type == vk::ImageType::e3D) {
copy.extent = {width, height, depth};
} else {
copy.srcSubresource.layerCount =
std::min(source_layers, destination_layers);
copy.srcSubresource.layerCount = std::min(source_layers, destination_layers);
copy.dstSubresource.layerCount = copy.srcSubresource.layerCount;
copy.extent = {width, height, 1};
}
@@ -369,34 +362,30 @@ void Image::CopyImage(Image& source) {
return;
}
auto command = m_scheduler->Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal,
vk::AccessFlagBits2::eTransferRead, {}, command);
Transit(vk::ImageLayout::eTransferDstOptimal,
vk::AccessFlagBits2::eTransferWrite, {}, command);
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
backing.image, vk::ImageLayout::eTransferDstOptimal,
static_cast<uint32_t>(copies.size()), copies.data());
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
command);
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, backing.image,
vk::ImageLayout::eTransferDstOptimal, static_cast<uint32_t>(copies.size()),
copies.data());
Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {},
command);
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, command);
}
void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
const ImageSubresourceRange& destination_range) {
EXIT_IF(m_scheduler == nullptr || backing.samples != 1 ||
source.backing.image_type != vk::ImageType::e2D ||
backing.image_type != vk::ImageType::e2D ||
source_range.level_count != 1 || destination_range.level_count != 1 ||
backing.image_type != vk::ImageType::e2D || source_range.level_count != 1 ||
destination_range.level_count != 1 ||
source_range.base_level >= source.backing.mip_levels ||
destination_range.base_level >= backing.mip_levels ||
source_range.base_layer >= source.backing.layers ||
destination_range.base_layer >= backing.layers);
const auto layers = std::min(
{source_range.layer_count, destination_range.layer_count,
source.backing.layers - source_range.base_layer,
backing.layers - destination_range.base_layer});
const auto source_width =
std::max(source.backing.extent.width >> source_range.base_level, 1u);
const auto layers = std::min({source_range.layer_count, destination_range.layer_count,
source.backing.layers - source_range.base_layer,
backing.layers - destination_range.base_layer});
const auto source_width = std::max(source.backing.extent.width >> source_range.base_level, 1u);
const auto source_height =
std::max(source.backing.extent.height >> source_range.base_level, 1u);
const auto destination_width =
@@ -404,43 +393,40 @@ void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
const auto destination_height =
std::max(backing.extent.height >> destination_range.base_level, 1u);
const bool copy = source.backing.samples == 1;
EXIT_IF(layers == 0 || info.extent.width > source_width ||
info.extent.height > source_height || info.extent.width > destination_width ||
info.extent.height > destination_height ||
EXIT_IF(layers == 0 || info.extent.width > source_width || info.extent.height > source_height ||
info.extent.width > destination_width || info.extent.height > destination_height ||
(copy ? !ImageViewOps::FormatsCompatible(source.backing.format, backing.format)
: source.backing.format != backing.format));
auto resolved_source_range = source_range;
auto resolved_destination_range = destination_range;
auto resolved_source_range = source_range;
auto resolved_destination_range = destination_range;
resolved_source_range.layer_count = layers;
resolved_destination_range.layer_count = layers;
const vk::Extent3D resolve_extent {info.extent.width, info.extent.height, 1};
m_scheduler->EndRendering();
auto command = m_scheduler->Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal,
vk::AccessFlagBits2::eTransferRead, resolved_source_range, command);
Transit(vk::ImageLayout::eTransferDstOptimal,
vk::AccessFlagBits2::eTransferWrite, resolved_destination_range, command);
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
resolved_source_range, command);
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
resolved_destination_range, command);
if (copy) {
vk::ImageCopy region {};
region.srcSubresource = {vk::ImageAspectFlagBits::eColor,
resolved_source_range.base_level,
region.srcSubresource = {vk::ImageAspectFlagBits::eColor, resolved_source_range.base_level,
resolved_source_range.base_layer, layers};
region.dstSubresource = {vk::ImageAspectFlagBits::eColor,
resolved_destination_range.base_level,
resolved_destination_range.base_layer, layers};
region.extent = resolve_extent;
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
backing.image, vk::ImageLayout::eTransferDstOptimal, region);
region.extent = resolve_extent;
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, backing.image,
vk::ImageLayout::eTransferDstOptimal, region);
} else {
vk::ImageResolve region {};
region.srcSubresource = {vk::ImageAspectFlagBits::eColor,
resolved_source_range.base_level,
region.srcSubresource = {vk::ImageAspectFlagBits::eColor, resolved_source_range.base_level,
resolved_source_range.base_layer, layers};
region.dstSubresource = {vk::ImageAspectFlagBits::eColor,
resolved_destination_range.base_level,
resolved_destination_range.base_layer, layers};
region.extent = resolve_extent;
region.extent = resolve_extent;
command.resolveImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
backing.image, vk::ImageLayout::eTransferDstOptimal, region);
}
@@ -454,22 +440,21 @@ uint32_t Image::CopyRows(uint64_t row_size, uint32_t rows, uint64_t capacity) no
}
void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
EXIT_IF(m_scheduler == nullptr || buffer.Handle() == nullptr ||
source.backing.samples != 1 || backing.samples != 1);
EXIT_IF(m_scheduler == nullptr || buffer.Handle() == nullptr || source.backing.samples != 1 ||
backing.samples != 1);
m_scheduler->EndRendering();
const uint32_t levels =
std::min(source.backing.mip_levels, backing.mip_levels);
const auto source_aspect =
const uint32_t levels = std::min(source.backing.mip_levels, backing.mip_levels);
const auto source_aspect =
FullAspectMask(source.backing.format) & ~vk::ImageAspectFlagBits::eStencil;
const auto destination_aspect =
FullAspectMask(backing.format) & ~vk::ImageAspectFlagBits::eStencil;
const auto source_bytes = DepthAspectTransferBytes(source.backing.format) != 0
? DepthAspectTransferBytes(source.backing.format)
: source.info.bytes_per_block;
const auto destination_bytes = DepthAspectTransferBytes(backing.format) != 0
? DepthAspectTransferBytes(backing.format)
: info.bytes_per_block;
const uint32_t source_block = source.info.IsBlock() ? 4u : 1u;
const auto source_bytes = DepthAspectTransferBytes(source.backing.format) != 0
? DepthAspectTransferBytes(source.backing.format)
: source.info.bytes_per_block;
const auto destination_bytes = DepthAspectTransferBytes(backing.format) != 0
? DepthAspectTransferBytes(backing.format)
: info.bytes_per_block;
const uint32_t source_block = source.info.IsBlock() ? 4u : 1u;
const uint32_t destination_block = info.IsBlock() ? 4u : 1u;
EXIT_IF(levels == 0 || source_bytes == 0 || source_bytes != destination_bytes ||
source_block != destination_block);
@@ -484,74 +469,66 @@ void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
barrier.buffer = buffer.Handle();
barrier.offset = 0;
vk::DependencyInfo dependency {};
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &barrier;
auto command = m_scheduler->Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal,
vk::AccessFlagBits2::eTransferRead, {}, command);
Transit(vk::ImageLayout::eTransferDstOptimal,
vk::AccessFlagBits2::eTransferWrite, {}, command);
auto command = m_scheduler->Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
command);
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
for (uint32_t level = 0; level < levels; level++) {
const auto width = std::max(source.backing.extent.width >> level, 1u);
const auto height = std::max(source.backing.extent.height >> level, 1u);
const auto source_depth = source.backing.image_type == vk::ImageType::e3D
? std::max(source.backing.extent.depth >> level, 1u)
: source.backing.layers;
const auto width = std::max(source.backing.extent.width >> level, 1u);
const auto height = std::max(source.backing.extent.height >> level, 1u);
const auto source_depth = source.backing.image_type == vk::ImageType::e3D
? std::max(source.backing.extent.depth >> level, 1u)
: source.backing.layers;
const auto destination_depth = backing.image_type == vk::ImageType::e3D
? std::max(backing.extent.depth >> level, 1u)
: backing.layers;
const auto slices = std::min(source_depth, destination_depth);
const auto block_rows = (height + source_block - 1) / source_block;
const auto slices = std::min(source_depth, destination_depth);
const auto block_rows = (height + source_block - 1) / source_block;
const auto row_size =
static_cast<uint64_t>((width + source_block - 1) / source_block) * source_bytes;
const auto rows_per_copy = CopyRows(row_size, block_rows, buffer.Size());
EXIT_IF(slices == 0 || rows_per_copy == 0);
for (uint32_t slice = 0; slice < slices; slice++) {
for (uint32_t block_row = 0; block_row < block_rows;
block_row += rows_per_copy) {
const auto copy_rows = std::min(rows_per_copy, block_rows - block_row);
const auto y = block_row * source_block;
const auto copy_height =
std::min(copy_rows * source_block, height - y);
const auto copy_size = row_size * copy_rows;
for (uint32_t block_row = 0; block_row < block_rows; block_row += rows_per_copy) {
const auto copy_rows = std::min(rows_per_copy, block_rows - block_row);
const auto y = block_row * source_block;
const auto copy_height = std::min(copy_rows * source_block, height - y);
const auto copy_size = row_size * copy_rows;
vk::BufferImageCopy source_copy {};
source_copy.imageSubresource = {
source_aspect, level,
source.backing.image_type == vk::ImageType::e3D ? 0u : slice, 1};
source_copy.imageOffset = {
0, static_cast<int32_t>(y),
source.backing.image_type == vk::ImageType::e3D
? static_cast<int32_t>(slice)
: 0};
source_copy.imageExtent = {width, copy_height, 1};
auto destination_copy = source_copy;
source_copy.imageOffset = {0, static_cast<int32_t>(y),
source.backing.image_type == vk::ImageType::e3D
? static_cast<int32_t>(slice)
: 0};
source_copy.imageExtent = {width, copy_height, 1};
auto destination_copy = source_copy;
destination_copy.imageSubresource = {
destination_aspect, level,
backing.image_type == vk::ImageType::e3D ? 0u : slice, 1};
destination_copy.imageOffset.z =
backing.image_type == vk::ImageType::e3D
? static_cast<int32_t>(slice)
: 0;
backing.image_type == vk::ImageType::e3D ? static_cast<int32_t>(slice) : 0;
barrier.size = copy_size;
barrier.srcAccessMask = vk::AccessFlagBits2::eTransferRead;
barrier.dstAccessMask = vk::AccessFlagBits2::eTransferWrite;
command.pipelineBarrier2(dependency);
command.copyImageToBuffer(source.backing.image,
vk::ImageLayout::eTransferSrcOptimal,
buffer.Handle(), source_copy);
vk::ImageLayout::eTransferSrcOptimal, buffer.Handle(),
source_copy);
barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite;
barrier.dstAccessMask = vk::AccessFlagBits2::eTransferRead;
command.pipelineBarrier2(dependency);
command.copyBufferToImage(buffer.Handle(), backing.image,
vk::ImageLayout::eTransferDstOptimal,
destination_copy);
vk::ImageLayout::eTransferDstOptimal, destination_copy);
}
}
}
Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {},
command);
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, command);
}
void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
@@ -561,11 +538,9 @@ void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
const auto width = std::max(backing.extent.width >> mip, 1u);
const auto height = std::max(backing.extent.height >> mip, 1u);
const auto depth = std::max(backing.extent.depth >> mip, 1u);
EXIT_IF(width != source.backing.extent.width ||
height != source.backing.extent.height);
const auto [source_layers, destination_layers] =
SanitizeCopyLayers(source, *this, depth);
const auto aspects = FullAspectMask(source.backing.format);
EXIT_IF(width != source.backing.extent.width || height != source.backing.extent.height);
const auto [source_layers, destination_layers] = SanitizeCopyLayers(source, *this, depth);
const auto aspects = FullAspectMask(source.backing.format);
EXIT_IF(aspects != FullAspectMask(backing.format));
std::array<vk::ImageCopy, 2> copies {};
uint32_t copy_count = 0;
@@ -580,16 +555,13 @@ void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
copy.extent = {width, height, depth};
}
auto command = m_scheduler->Current().Handle();
Transit(vk::ImageLayout::eTransferDstOptimal,
vk::AccessFlagBits2::eTransferWrite, {}, command);
source.Transit(vk::ImageLayout::eTransferSrcOptimal,
vk::AccessFlagBits2::eTransferRead, {}, command);
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
backing.image, vk::ImageLayout::eTransferDstOptimal, copy_count,
copies.data());
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
command);
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, backing.image,
vk::ImageLayout::eTransferDstOptimal, copy_count, copies.data());
Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {},
command);
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}, command);
}
namespace ImageOps {
@@ -601,8 +573,7 @@ void Validate(const ImageInfo& info) {
if (info.pixel_format == vk::Format::eUndefined) {
const bool metadata_empty =
info.metadata.range.address == 0 && info.metadata.range.size == 0 &&
info.metadata.kind == ImageMetadataKind::None &&
info.metadata.control == 0 &&
info.metadata.kind == ImageMetadataKind::None && info.metadata.control == 0 &&
info.metadata.compression == VideoOutCompression::Uncompressed &&
!info.metadata.stencil_compressed;
if (info.data.Empty() || info.HasStencil() || !metadata_empty || info.extent.width == 0 ||
@@ -615,9 +586,9 @@ void Validate(const ImageInfo& info) {
}
if (info.extent.width == 0 || info.extent.height == 0 || info.extent.depth == 0 ||
info.resources.levels == 0 ||
info.resources.levels > info.mip_layout.size() || info.resources.layers == 0 ||
info.samples == 0 || vulkan_sample_count(info.samples) == vk::SampleCountFlagBits {} ||
info.resources.levels == 0 || info.resources.levels > info.mip_layout.size() ||
info.resources.layers == 0 || info.samples == 0 ||
vulkan_sample_count(info.samples) == vk::SampleCountFlagBits {} ||
info.bytes_per_block == 0 || (info.data.address != 0 && info.pitch == 0)) {
EXIT("invalid image geometry or format\n");
}
@@ -675,24 +646,25 @@ 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);
}
}
} // namespace ImageOps
Image::Image(GraphicContext& graphics, CommandScheduler& scheduler,
const ImageInfo& image_info)
Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageInfo& image_info)
: info(image_info), m_graphics(&graphics), m_scheduler(&scheduler) {
KYTY_PROFILER_FUNCTION();
ImageOps::Validate(info);
m_cpu_dirty =
!info.data.Empty() && info.metadata.compression == VideoOutCompression::Uncompressed;
if (info.pixel_format == vk::Format::eUndefined) {
return;
}
@@ -742,9 +714,9 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler,
}
uint64_t Image::HashGuestEdges() const {
constexpr uint64_t page_mask = TRACKER_PAGE_SIZE - 1;
constexpr uint64_t page_mask = TRACKER_PAGE_SIZE - 1;
std::array<uint8_t, TRACKER_PAGE_SIZE * 2> bytes {};
const auto range = info.data;
const auto range = info.data;
const uint64_t head_end = std::min(range.End(), (range.address + page_mask) & ~page_mask);
const uint64_t tail_begin = std::max(range.address, range.End() & ~page_mask);
const uint64_t head_size = head_end - range.address;
@@ -3,7 +3,7 @@
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/imageInfo.h"
#include "graphics/host_gpu/renderer/image/imageInfo.h"
#include <compare>
#include <limits>
@@ -55,6 +55,7 @@ struct ImageBinding {
bool is_target = false;
bool needs_rebind = false;
bool force_general = false;
bool shader_write = false;
};
class Image final {
@@ -66,16 +67,16 @@ public:
[[nodiscard]] vk::ImageView FindView(const ImageViewInfo& view_info);
void AssociateDepth(ImageId image_id) { depth_id = image_id; }
using Barriers = std::vector<vk::ImageMemoryBarrier2>;
[[nodiscard]] Barriers
GetBarriers(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access,
vk::PipelineStageFlags2 destination_stage,
std::optional<ImageSubresourceRange> range);
[[nodiscard]] Barriers GetBarriers(vk::ImageLayout destination_layout,
vk::AccessFlags2 destination_access,
vk::PipelineStageFlags2 destination_stage,
std::optional<ImageSubresourceRange> range);
void Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access,
std::optional<ImageSubresourceRange> range, vk::CommandBuffer command_buffer);
void Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
uint64_t offset, uint64_t size);
void Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
uint64_t offset, uint64_t size);
void Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, uint64_t offset,
uint64_t size);
void Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, uint64_t offset,
uint64_t size);
void CopyImage(Image& source);
void Resolve(Image& source, const ImageSubresourceRange& source_range,
const ImageSubresourceRange& destination_range);
@@ -84,8 +85,8 @@ public:
void InvalidateCpuWrite(uint64_t vaddr, uint64_t size) {
if (ImageRangeOverlaps(info.data.address, info.data.size, vaddr, size)) {
m_cpu_dirty = true;
m_maybe_cpu_dirty = false;
m_cpu_dirty = true;
m_maybe_cpu_dirty = false;
m_maybe_hash_valid = false;
} else if (ImagePageRangesOverlap(info.data.address, info.data.size, vaddr, size)) {
m_maybe_cpu_dirty = true;
@@ -95,6 +96,11 @@ public:
[[nodiscard]] bool IsCpuDirty() const { return m_cpu_dirty || m_maybe_cpu_dirty; }
[[nodiscard]] bool IsDefinitelyCpuDirty() const { return m_cpu_dirty; }
[[nodiscard]] bool IsMaybeCpuDirty() const { return m_maybe_cpu_dirty; }
void MarkMaybeCpuDirty() {
if (!m_cpu_dirty) {
m_maybe_cpu_dirty = true;
}
}
[[nodiscard]] bool NeedsMaybeCpuHash() const {
return m_maybe_cpu_dirty && !m_maybe_hash_valid;
}
@@ -102,14 +108,14 @@ public:
if (!NeedsMaybeCpuHash()) {
EXIT("image cannot initialize maybe-dirty hash\n");
}
m_maybe_cpu_hash = hash;
m_maybe_cpu_hash = hash;
m_maybe_hash_valid = true;
}
[[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) {
if (!m_maybe_cpu_dirty || !m_maybe_hash_valid || m_cpu_dirty) {
EXIT("image cannot resolve maybe-dirty hash\n");
}
m_maybe_cpu_dirty = false;
m_maybe_cpu_dirty = false;
m_maybe_hash_valid = false;
m_cpu_dirty |= hash != m_maybe_cpu_hash;
return m_cpu_dirty;
@@ -125,14 +131,15 @@ public:
}
[[nodiscard]] bool IsGpuModified() const noexcept { return m_gpu_modified; }
void MarkGpuModified() noexcept { m_gpu_modified = true; }
void ClearGpuModified() noexcept { m_gpu_modified = false; }
void MarkGpuModified() noexcept { m_gpu_modified = true; }
void ClearGpuModified() noexcept { m_gpu_modified = false; }
[[nodiscard]] bool IsBufferModified() const noexcept { return m_buffer_modified; }
void MarkBufferModified() noexcept { m_buffer_modified = true; }
void ClearBufferModified() noexcept { m_buffer_modified = false; }
void MarkBufferModified() noexcept { m_buffer_modified = true; }
void ClearBufferModified() noexcept { m_buffer_modified = false; }
[[nodiscard]] bool Overlaps(uint64_t address, uint64_t size, bool pages = false) const noexcept {
[[nodiscard]] bool Overlaps(uint64_t address, uint64_t size,
bool pages = false) const noexcept {
return pages ? ImagePageRangesOverlap(info.data.address, info.data.size, address, size)
: ImageRangeOverlaps(info.data.address, info.data.size, address, size);
}
@@ -142,44 +149,48 @@ public:
[[nodiscard]] bool SafeToDownload() const noexcept {
return IsGpuModified() && !IsBufferModified() && !IsCpuDirty();
}
[[nodiscard]] bool IsTracked() const noexcept { return track_addr != 0 && track_addr_end != 0; }
[[nodiscard]] uint64_t AccountedSize() const noexcept {
return backing.image == nullptr ? 0 : (info.data.size + 1023) & ~uint64_t {1023};
}
[[nodiscard]] uint64_t HashGuestEdges() const;
ImageInfo info;
VulkanImage backing;
ImageViewCache views;
ImageUsage usage;
ImageBinding binding;
bool registered = false;
ImageId depth_id {};
uint64_t tick_accessed_last = 0;
size_t lru_id = 0;
ImageInfo info;
VulkanImage backing;
ImageViewCache views;
ImageUsage usage;
ImageBinding binding;
bool registered = false;
mutable uint32_t query_epoch = 0;
uint64_t track_addr = 0;
uint64_t track_addr_end = 0;
ImageId depth_id {};
uint64_t tick_accessed_last = 0;
size_t lru_id = 0;
private:
friend struct ImageTestAccess;
[[nodiscard]] static vk::ImageAspectFlags FullAspectMask(vk::Format format) noexcept;
[[nodiscard]] static uint32_t CopyRows(uint64_t row_size, uint32_t rows,
uint64_t capacity) noexcept;
[[nodiscard]] static uint32_t CopyRows(uint64_t row_size, uint32_t rows,
uint64_t capacity) noexcept;
[[nodiscard]] static std::pair<uint32_t, uint32_t>
SanitizeCopyLayers(const Image& source, const Image& destination, uint32_t depth);
GraphicContext* m_graphics = nullptr;
CommandScheduler* m_scheduler = nullptr;
uint64_t m_maybe_cpu_hash = 0;
bool m_cpu_dirty = false;
bool m_maybe_cpu_dirty = false;
bool m_maybe_hash_valid = false;
bool m_gpu_modified = false;
bool m_buffer_modified = false;
GraphicContext* m_graphics = nullptr;
CommandScheduler* m_scheduler = nullptr;
uint64_t m_maybe_cpu_hash = 0;
bool m_cpu_dirty = false;
bool m_maybe_cpu_dirty = false;
bool m_maybe_hash_valid = false;
bool m_gpu_modified = false;
bool m_buffer_modified = false;
};
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
@@ -19,10 +19,9 @@ struct GuestRange {
uint64_t address = 0;
uint64_t size = 0;
[[nodiscard]] constexpr bool Empty() const noexcept { return address == 0 || size == 0; }
[[nodiscard]] constexpr bool Valid() const noexcept {
return !Empty() && address < TRACKER_ADDRESS_SIZE &&
size <= TRACKER_ADDRESS_SIZE - address;
[[nodiscard]] constexpr bool Empty() const noexcept { return address == 0 || size == 0; }
[[nodiscard]] constexpr bool Valid() const noexcept {
return !Empty() && address < TRACKER_ADDRESS_SIZE && size <= TRACKER_ADDRESS_SIZE - address;
}
[[nodiscard]] constexpr uint64_t End() const noexcept { return address + size; }
auto operator<=>(const GuestRange&) const = default;
@@ -47,10 +46,10 @@ struct ImageSubresources {
};
struct ImageSubresourceRange {
uint32_t base_level = 0;
uint32_t level_count = 1;
uint32_t base_layer = 0;
uint32_t layer_count = 1;
uint32_t base_level = 0;
uint32_t level_count = 1;
uint32_t base_layer = 0;
uint32_t layer_count = 1;
auto operator<=>(const ImageSubresourceRange&) const = default;
};
@@ -67,15 +66,15 @@ struct ImageInfo {
GuestRange stencil;
ImageMetadataInfo metadata;
uint32_t htile_clear_mask = UINT32_MAX;
vk::Format pixel_format = vk::Format::eUndefined;
uint32_t guest_format = 0;
Prospero::ImageType type = Prospero::ImageType::kColor2D;
vk::Extent3D extent = {1, 1, 1};
vk::Format pixel_format = vk::Format::eUndefined;
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 {};
@@ -87,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;
}
@@ -215,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;
};
@@ -224,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;
}
}
@@ -246,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;
}
}
@@ -270,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;
@@ -319,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 &&
@@ -352,8 +327,7 @@ inline bool ImageInfo::IsDepth() const noexcept {
}
const auto transfer_bytes = DepthAspectTransferBytes(info.pixel_format);
return transfer_bytes == info.bytes_per_block ||
(info.bytes_per_block == sizeof(uint16_t) &&
transfer_bytes == sizeof(uint32_t));
(info.bytes_per_block == sizeof(uint16_t) && transfer_bytes == sizeof(uint32_t));
}
[[nodiscard]] inline VideoOutCompression
@@ -385,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 {
@@ -398,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,
@@ -441,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 =
@@ -462,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);
}
@@ -470,18 +438,13 @@ IsSupportedDisplayRenderTargetTileMode(uint32_t tile_mode) noexcept {
vk::ClearColorValue& clear) {
vk::ClearColorValue next {};
const auto unorm8 = [](uint32_t value) { return static_cast<float>(value & 0xffu) / 255.0f; };
const auto srgb8 = [](uint32_t value) {
const auto srgb8 = [](uint32_t value) {
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
return encoded <= 0.04045f ? encoded / 12.92f
: std::pow((encoded + 0.055f) / 1.055f, 2.4f);
return encoded <= 0.04045f ? encoded / 12.92f : std::pow((encoded + 0.055f) / 1.055f, 2.4f);
};
switch (format) {
case vk::Format::eR32Uint:
next.uint32[0] = packed;
break;
case vk::Format::eR32Sint:
next.int32[0] = static_cast<int32_t>(packed);
break;
case vk::Format::eR32Uint: next.uint32[0] = packed; break;
case vk::Format::eR32Sint: next.int32[0] = static_cast<int32_t>(packed); break;
case vk::Format::eR8G8B8A8Srgb:
next.float32[0] = srgb8(packed);
next.float32[1] = srgb8(packed >> 8u);
@@ -1,8 +1,8 @@
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include <mutex>
@@ -36,17 +36,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:
@@ -70,15 +59,14 @@ namespace {
}
case vk::ImageType::e3D:
switch (info.type) {
case vk::ImageViewType::e3D:
return info.base_layer == 0 && info.layer_count == 1;
case vk::ImageViewType::e3D: return info.base_layer == 0 && info.layer_count == 1;
case vk::ImageViewType::e2D:
return static_cast<bool>(
image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) &&
return static_cast<bool>(image.flags &
vk::ImageCreateFlagBits::e2DArrayCompatible) &&
info.level_count == 1 && info.layer_count == 1;
case vk::ImageViewType::e2DArray:
return static_cast<bool>(
image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) &&
return static_cast<bool>(image.flags &
vk::ImageCreateFlagBits::e2DArrayCompatible) &&
info.level_count == 1;
default: return false;
}
@@ -325,43 +313,40 @@ bool FormatsCompatible(vk::Format base, vk::Format view) noexcept {
} // namespace ImageViewOps
vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
const auto& image = backing;
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 bool is_storage = static_cast<bool>(normalized.usage & vk::ImageUsageFlagBits::eStorage);
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;
}
normalized.usage =
is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {};
normalized.usage = is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {};
const bool format_compatible = normalized.format != vk::Format::eUndefined &&
IsCompatibleViewFormat(image.format, normalized.format);
const bool slice_view = image.image_type == vk::ImageType::e3D &&
(normalized.type == vk::ImageViewType::e2D ||
normalized.type == vk::ImageViewType::e2DArray);
const bool slice_view =
image.image_type == vk::ImageType::e3D && (normalized.type == vk::ImageViewType::e2D ||
normalized.type == vk::ImageViewType::e2DArray);
const bool levels_valid = normalized.level_count != 0 &&
normalized.base_level < image.mip_levels &&
normalized.level_count <= image.mip_levels - normalized.base_level;
const auto view_layers = slice_view && levels_valid
? std::max(image.extent.depth >> normalized.base_level, 1u)
: image.layers;
const bool ranges_valid = levels_valid &&
normalized.layer_count != 0 && normalized.base_layer < view_layers &&
const auto view_layers = slice_view && levels_valid
? std::max(image.extent.depth >> normalized.base_level, 1u)
: image.layers;
const bool ranges_valid = levels_valid && normalized.layer_count != 0 &&
normalized.base_layer < view_layers &&
normalized.layer_count <= view_layers - normalized.base_layer;
const bool mapping_valid =
IsComponentSwizzle(normalized.mapping.r) && IsComponentSwizzle(normalized.mapping.g) &&
IsComponentSwizzle(normalized.mapping.b) && IsComponentSwizzle(normalized.mapping.a);
if (image.image == nullptr || !format_compatible || !ranges_valid || !mapping_valid ||
!IsValidViewType(image, normalized) ||
!IsValidAspect(image, normalized.aspect)) {
!IsValidViewType(image, normalized) || !IsValidAspect(image, normalized.aspect)) {
EXIT("invalid image view: image_format=%d view_format=%d type=%d aspect=0x%x "
"mip=%u+%u layer=%u+%u usage=0x%x image_levels=%u image_layers=%u\n",
static_cast<int>(image.format), static_cast<int>(normalized.format),
Loaded 100 of 368 files, more files were not shown because too many files have changed in this diff. Show more