Compare commits

...
Author SHA1 Message Date
ADBHXNLOL dff2b19c56 json2: export Value::clear (#584)
Export the existing clear implementation while preserving tree ownership and root handler metadata.
2026-09-12 15:15:26 +04:00
Vandvmexe a58db4f023 docs: fix pre-commit setup instructions (#556)
Co-authored-by: vmexe <vmexe@users.noreply.github.com>
2026-09-12 14:58:34 +04:00
Frans 7bcd43dbee shader: support S_MUL_HI_I32 (SOP2 0x36) (#576) 2026-09-12 14:56:14 +04:00
nmzik 7c5473400e config: simplify logging and profiler options. Change to Mailbox 2026-09-12 14:41:34 +04:00
nmzik 23c8a43e37 Simplify and batch buffer cache downloads 2026-09-12 14:17:00 +04:00
nmzik 2d37634ee9 shader: construct SPIR-V module variables in one place 2026-09-12 13:42:15 +04:00
nmzik e026bfc9a2 shader: let structurization own rollback on failure 2026-09-12 13:42:15 +04:00
nmzik 5a31de7955 shader: keep derived frontend metadata with its owner 2026-09-12 13:42:15 +04:00
nmzik 20e57994cf shader: reuse native emitters and simplify memory dispatch 2026-09-12 13:42:15 +04:00
nmzik 59ef88c959 shader: generate SPIR-V dispatch and marshal handler arguments 2026-09-12 13:42:15 +04:00
nmzik 1d1f00bdef shader: use Khronos definitions and simplify compiler ownership 2026-09-12 13:42:15 +04:00
nmzik 47b26e497f pthread: fix per-thread wakeups 2026-09-12 13:40:10 +04:00
nmzik 733842cad4 kernel: index pthread key values per thread 2026-09-12 13:40:10 +04:00
nmzik 73363f1272 memory: remove per-page host protection bookkeeping 2026-09-12 13:40:10 +04:00
nmzik 0cd7682012 Rename texture transfer types and download APIs 2026-09-12 13:40:10 +04:00
nmzik 25a44371e3 refactor(graphics) 2026-09-12 13:40:10 +04:00
nmzik f2455c875e update tracy to 0.14.1 2026-09-10 19:34:37 +04:00
nmzik 2e315a3c62 ajm: parse gapless control data before codec initialization 2026-09-10 18:38:24 +04:00
nmzik d2fa86579b kernel: share directory streams across seeks and reads 2026-09-10 14:36:15 +04:00
nmzik 23e7df21b1 graphics: simplify texture upload and component mapping paths 2026-09-10 14:36:15 +04:00
nmzik a305a6c1c6 graphics: share packed texture and render-target channel layouts
Resolve PS5 5551 textures with alpha in bit 15 and compose host channel order with guest selectors. Share the existing render-target mapping with sampled textures so packed layouts cannot diverge.

Add native GPU readback coverage for 5551 and 4444, arbitrary selectors, constants, and existing 1555 render-target mapping. Verified PPSA24515 through launch.json with Vulkan validation, advancing scenes and over 12,700 guest flips. Independent correctness and cleanliness audits passed.
2026-09-10 14:36:15 +04:00
nmzik 635fb8882b systemservice: use configured language for PS5 parameter 400 2026-09-10 14:11:46 +04:00
nmzik fe6f49632c font: accept supported OpenType CFF faces 2026-09-10 14:11:46 +04:00
nmzikandBrandon Strong c3546577b6 shader: implement S_WQM_B32
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
2026-09-10 14:11:46 +04:00
nmzik c913951f7b shader: implement RDNA2 subvector loop mask and branch semantics 2026-09-10 14:11:46 +04:00
nmzik 3902b42096 shader: share direct and conditional branch classification 2026-09-10 14:11:46 +04:00
nmzik ae573f941a graphics: share vertex format mapping and support packed float colors 2026-09-10 14:11:46 +04:00
nmzik 20ea54cf0c Json2: new ABI 2026-09-10 14:11:46 +04:00
nmzik 0b4e78c0b2 graphics: execute DB_RENDER_OVERRIDE depth and stencil copies 2026-09-09 15:26:52 +04:00
nmzik 57844a07e2 buffer cache: grow stream reserves toward incoming requests 2026-09-09 15:26:52 +04:00
nmzik ba18551d6e ces: implement CP932 string conversion 2026-09-09 15:26:52 +04:00
nmzikandAli Almohaya c7b0d77970 loader: port guest fault diagnostics and remove host stack traces
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
2026-09-09 13:55:53 +04:00
nmzik 1af19eabf0 shader: fix incorrect per-lane VCCZ/EXECZ reads 2026-09-09 13:22:09 +04:00
nmzik 32ea0869e5 shader: implement S_ORN2_SAVEEXEC_B32 2026-09-09 13:11:48 +04:00
nmzik e04007a7c6 shader: implement DS_INC_RTN_U32 and DS_DEC_RTN_U32 2026-09-09 12:56:20 +04:00
frangametvandnmzik 1a3a15e4cf Fix mount root opens and empty APR prefixes (#439)
* Fix opening mount root directories

* Allow empty APR path prefixes

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-09 12:31:23 +04:00
nmzik 679adbfa5e graphics: fix linear render-target pitch alignment 2026-09-09 02:45:59 +02:00
nmzikandEttore Sartori d285efe3b5 graphics: allow absent programmable restart indices
Co-authored-by: Ettore Sartori <85788613+Ekt0re@users.noreply.github.com>
2026-09-08 21:26:59 +04:00
nmzik a64c8d3205 kernel: implement umtx wait/wake and reject malformed timeouts 2026-09-08 21:26:59 +04:00
nmzik 82c52c3fab window: round FPS in the title 2026-09-08 21:26:59 +04:00
nmzik dd1e90fcd7 video: keep decoding after Flush 2026-09-08 21:26:59 +04:00
nmzik ef0155636a shader: support single and split NGG shaders for PPSA11142 2026-09-08 21:26:59 +04:00
nmzik 9d550fbffa graphics: fix last provoking vertex on PS5 2026-09-08 21:26:59 +04:00
nmzik fcef691e0a shader: drop EmitTrueBool 2026-09-08 21:26:59 +04:00
nmzik 839c172612 shader: reuse operands and remove duplicate helpers 2026-09-08 21:26:59 +04:00
nmzik 9f49ac8249 shader: reuse SPIR-V helpers and memory resources 2026-09-08 21:26:59 +04:00
nmzik fde550ef66 graphics: simplify texture cleanup and swizzle decoding 2026-09-08 21:26:59 +04:00
nmzik 2b3993a595 shader: share emitter helpers 2026-09-08 21:26:59 +04:00
nmzik de3e86791c shader: reuse CFG and validation lookups 2026-09-08 21:26:59 +04:00
nmzik 6f66f77b09 shader: drop unused decoder fields 2026-09-08 21:26:59 +04:00
nmzik d853aba955 graphics: skip temporary cache lookup vectors 2026-09-08 21:26:59 +04:00
nmzik cd62693792 shader: share control flow labels 2026-09-08 21:26:59 +04:00
nmzik cb4c8f3878 shader: decode buffer formats once 2026-09-08 21:26:59 +04:00
nmzik 4d371d5f5f shader: reuse initial SSA types 2026-09-08 21:26:59 +04:00
nmzik 72963b01e1 shader: remove redundant decoder and translator state 2026-09-08 21:26:59 +04:00
nmzik 80a18e82b7 graphics: simplify image allocation 2026-09-08 21:26:58 +04:00
nmzik 574e687734 graphics: drop unused completion overload 2026-09-08 21:26:58 +04:00
nmzik c725ec50ff graphics: clean up draw setup 2026-09-08 21:26:58 +04:00
Brandon Strong c6f6e306e0 shader: raise the dense image resource limit to 64 (#457) 2026-09-08 19:37:41 +04:00
nmzik b847135321 launcher: guard optional Qt Vulkan support on macOS 2026-09-08 04:29:01 +04:00
nmzik ca3dc8792a libs: implement NpCommerce lifecycle 2026-09-08 04:25:35 +04:00
nmzik ac3156df3a graphics: fix Vulkan BC image compatibility checks` 2026-09-07 23:41:19 +02:00
nmzik 6aa6e390ee renderer: skip unsupported primitives (print log) 2026-09-07 15:20:09 +04:00
nmzik e4c10de8b6 launcher: add GPU selection 2026-09-07 15:12:35 +04:00
nmzik b3b3aeef73 graphics: simplify EOP recording 2026-09-07 15:00:52 +04:00
nmzik 056dcee70c graphics: simplify buffer allocations 2026-09-07 15:00:52 +04:00
nmzik 13c5bbe916 graphics: simplify pipeline keys 2026-09-07 15:00:52 +04:00
bbe3bd0304 Audio: support 12-channel AudioOut2 output with SDL (#498)
* Audio: support 12-channel AudioOut2 output via stereo downmix

* Audio: document 7.1.4 downmix matrix and align s16 coefficients

* audio: use shared 12-to-8 channel conversion for SDL

---------

Co-authored-by: Airsoft Project <airsoft@example.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-07 12:58:23 +04:00
Tarunandnmzik f9b279c49d launcher: verify updates against GitHub and check release dates (#482)
* launcher: verify with GitHub when website feed differs and compare tag dates

* launcher: restore kyty_emulator dependency for CI build and install

* launcher: simplify GitHub update verification

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-07 12:25:54 +04:00
nmzik b61360a0c0 fix: share thread signal stacks with macOS 2026-09-07 12:17:30 +04:00
Ali Almohaya 27ee6f02d6 fix: windows build failure in UTF-16 conversion (#499) 2026-09-07 11:52:22 +04:00
nmzik 2097ebb414 render: support overlapping fragment depth feedback 2026-09-07 11:15:47 +04:00
nmzik 587df9145f render: validate sampled depth descriptors independently of target history
Comparison textures can carry valid HTILE descriptors before any depth-target binding. Keep encoding checks independent of cached image metadata, matching SDK texture classification and shadPS4 depth promotion. Preserve HTILE address and control validation, and replace cache-history test fixtures with captured first-use coverage.
2026-09-07 11:12:48 +04:00
nmzik 2c5060e14f shader: remove masked-value pass and normalize ancillary fields 2026-09-07 11:12:48 +04:00
nmzik 75aaa963d2 update screenshot 2026-09-07 09:31:15 +04:00
nmzik 34ebfe40c3 shader: report compiled counts by guest stage 2026-09-07 07:34:01 +04:00
nmzikandMehmetCambaz 2a54310876 render: fix depth write state before choosing layouts
Co-authored-by: MehmetCambaz <mcambaz18@gmail.com>
2026-09-07 07:12:58 +04:00
nmzik 8bccd325ec shader: keep mask predicates across 64-bit moves 2026-09-07 07:04:55 +04:00
nmzik ad935311f0 Revert "render: add depth attachment feedback support" 2026-09-07 07:01:53 +04:00
nmzik 19fc0274de shader: validate shared terminal paths in selections 2026-09-07 06:57:55 +04:00
nmzikandBrandon Strong 6cc9e5de11 shader: fix wave32 VCC halves and scalar mask moves
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
2026-09-07 06:04:40 +04:00
nmzik bf7ca29487 shader: decode VOP3 FMAC source modifiers 2026-09-07 05:25:26 +04:00
nmzik 3cf426662e audio: fix microphone input format decoding 2026-09-07 05:04:15 +04:00
nmzik 1fd07c2c83 shader: assemble point inputs for merged geometry shaders 2026-09-07 05:04:15 +04:00
nmzikandDeathWhispers f880c58ccc render: add depth attachment feedback support
Co-authored-by: DeathWhispers <56944463+Death-Whispers-U@users.noreply.github.com>
2026-09-07 04:25:10 +04:00
49e59e572e shader: separate depth comparison and regular image descriptors
Co-authored-by: Lordix <lordix253634@gmail.com>
Co-authored-by: fxpw <fxpw@outlook.com>
Co-authored-by: Mac <me@itsmemac.com>
2026-09-07 04:06:14 +04:00
nmzikandfxpw 9d1fc8328f render: allow supported unrestricted float depth uploads
Co-authored-by: fxpw <fxpw@outlook.com>
2026-09-06 22:00:40 +04:00
c9ff09c7c2 textures: keep native formats unless depth promotion is known
Co-authored-by: Ettore Sartori <85788613+Ekt0re@users.noreply.github.com>
Co-authored-by: Mac <me@itsmemac.com>
2026-09-06 21:40:58 +04:00
nmzik ceb2b664e0 render: reset rasterization defaults on CLEAR_STATE 2026-09-06 21:25:12 +04:00
nmzik b5b4c06179 textures: share depth comparison view geometry 2026-09-06 20:18:31 +04:00
nmzik d70f9c0b2b launcher: add readback option 2026-09-06 19:48:21 +04:00
nmzik e847f82fab shader: initialize pixel position W as a reciprocal 2026-09-06 19:47:43 +04:00
nmzik 807a594c2d textures: keep atlas data across compressed block views 2026-09-06 18:57:26 +04:00
nmzik ced73c56cc shader: fix spilled mesh shader data loads 2026-09-06 17:17:51 +04:00
nmzik 9c6f6fa624 json2: add array and object value lifecycles 2026-09-06 14:48:49 +04:00
nmzik 533176f9ff shader: drop inactive pixel values under whole-quad masks 2026-09-06 14:38:32 +04:00
nmzik 66c1663495 audio: use the shared AAC decoder for Audiodec 2026-09-06 14:16:27 +04:00
nmzik 30c7715d3f audio: separate the decoder interface from AJM jobs 2026-09-06 14:07:12 +04:00
nmzik 093911a1dc audio: check packet buffers only during decode 2026-09-06 14:04:27 +04:00
nmzikandCheesyPoofs346 d83f1f8235 memory: handle GPU faults at the mapped boundary
Co-authored-by: CheesyPoofs346 <jjs777406@gmail.com>
2026-09-06 11:08:05 +04:00
nmzik 50e78d717d render: use guest polygon modes 2026-09-06 10:56:59 +04:00
nmzik 6ca8201982 textures: handle DCC memory reused as HTile 2026-09-06 10:34:02 +04:00
nmzik 99a87bd65b shader: keep loop branches during selection routing 2026-09-06 10:23:44 +04:00
nmzikandEttore Sartori f9f8e12c35 shader: add viewport-index exports and indexed raster state
Co-authored-by: Ettore Sartori <85788613+Ekt0re@users.noreply.github.com>
2026-09-06 10:16:38 +04:00
nmzik dcaf4205cb render: track pages for DMA fills and copies 2026-09-06 10:16:27 +04:00
nmzik 4735359466 textures: invalidate stencil links on CPU writes 2026-09-06 09:38:22 +04:00
nmzik 211242452b kernel: use per-thread signal stacks for guest stack faults 2026-09-06 09:28:19 +04:00
nmzik 6ae5cb7b64 system: keep controller focus in overlays 2026-09-06 09:15:29 +04:00
nmzik 997ad60c50 system: keep error dialogs open until dismissed 2026-09-06 08:34:58 +04:00
44f982bca7 render: apply DCC clears before binding images
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
2026-09-06 08:00:59 +04:00
nmzik 0456f5a06b libc: fix UTF-16 printf strings and precision limits 2026-09-06 07:28:19 +04:00
nmzik b346f58b73 shader: read sample IDs from pixel ancillary input 2026-09-06 06:54:38 +04:00
nmzik ace877ce28 shader: lower FMASK loads before binding images 2026-09-06 06:42:23 +04:00
nmzik cf0cbee02d shader: fix vertex attribute identity swizzles 2026-09-06 05:47:20 +04:00
nmzik 98e7eeb72a textures: keep dirty pages during image-to-buffer sync 2026-09-06 05:36:41 +04:00
nmzik 803c124136 textures: reuse range lookups for buffer sync 2026-09-06 05:07:44 +04:00
nmzik d9d17737c3 net: return native SO_ERROR 2026-09-06 05:07:44 +04:00
nmzik 46850f9574 net: fix socket wakeups and readiness checks 2026-09-06 05:00:12 +04:00
nmzik 314d37155a net: add TCP_NODELAY and translate socket errors 2026-09-06 04:51:07 +04:00
nmzik ddcdb654ad net: share native connect and accept 2026-09-06 04:47:15 +04:00
nmzik cdec5c515b net: use native listen on POSIX 2026-09-06 04:45:53 +04:00
nmzik 1eb13dfa5e render: call Vulkan diagnostics and capability checks directly 2026-09-06 04:11:46 +04:00
nmzik dda628ca81 render: simplify command and resource ownership 2026-09-06 04:05:35 +04:00
nmzik 13ad9f510d render: clean up cache and image internals 2026-09-06 03:55:13 +04:00
nmzik 4c769dfbf9 render: simplify buffer and memory tracking ownership 2026-09-06 03:46:34 +04:00
nmzikandDeathWhispers ccd19b3e56 render: choose depth layouts independently of load clears
Co-authored-by: DeathWhispers <56944463+Death-Whispers-U@users.noreply.github.com>
2026-09-06 03:35:56 +04:00
nmzik 81d0ea5697 render: ignore stale export formats with no pixel shader 2026-09-05 23:36:42 +04:00
nmzik 43de8bfaac shader: cache integer dependency checks 2026-09-05 20:35:18 +04:00
nmzik 86b539a9ea tests: clear BDA page tables before transfer updates 2026-09-05 16:04:58 +04:00
nmzik 81a5dd612f shader: fix embedded vertex fetch swizzles 2026-09-05 16:00:46 +04:00
nmzik 9c00c7077b shader: keep per-invocation mesh exports out of shared memory 2026-09-05 15:48:29 +04:00
nmzikandshadowbeat070 49ff962f69 shader: add conditional 64-bit scalar moves
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-09-05 15:38:51 +04:00
nmzik c83aac966e textures: keep incompatible mip layouts separate 2026-09-05 15:24:07 +04:00
nmzikandshadowbeat070 9918dc2591 render: set merged GS user data from shader registers
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-09-05 15:08:20 +04:00
nmzikandshadowbeat070 3ab372ccec render: build indexed triangles for merged ES/GS shaders
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-09-05 14:59:28 +04:00
nmzik 843c0c59be render: use Vulkan-Hpp setup defaults 2026-09-05 14:42:14 +04:00
nmzik d93812b0bd render: simplify pool wrapping and scissor clipping 2026-09-05 14:37:11 +04:00
nmzik 1c68c94c05 render: remove redundant cache setup 2026-09-05 14:37:04 +04:00
nmzik c66060be10 render: simplify pipeline setup 2026-09-05 14:36:52 +04:00
nmzik e7b269a9f6 render: clean up image setup and tiler recording 2026-09-05 14:36:42 +04:00
nmzik d5b19850fb render: clean up texture transfers and compute fills 2026-09-05 13:20:52 +04:00
nmzikandVyas Devgna e7fe8192bb tests: wait for interrupt callbacks before the next queue
Co-authored-by: Vyas Devgna <vyasdevgna@gmail.com>
2026-09-05 13:20:16 +04:00
nmzik ebe0979563 shader: share runtime value checks 2026-09-05 13:16:54 +04:00
nmzik a379304362 present: wait for image acquisition before transitioning 2026-09-05 12:40:54 +04:00
nmzik bec7c510c2 render: use native clears for stencil fill shaders 2026-09-05 12:36:13 +04:00
nmzikandBrandon Strong 6a187ac67e render: upload the full stride-zero vertex attribute
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
2026-09-05 12:17:39 +04:00
9641b51925 render: fix tiled depth comparison textures
Co-authored-by: Ettore Sartori <85788613+Ekt0re@users.noreply.github.com>
Co-authored-by: Mac <me@itsmemac.com>
2026-09-05 11:44:11 +04:00
nmzikandshadowbeat070 b96a30ed88 shader: keep fragment layer values under lane masks
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-09-05 11:43:49 +04:00
nmzik 2ec0e9e8f3 shader: fix packed image address reads 2026-09-05 11:32:32 +04:00
nmzik 51afce3a97 shader: zero-extend unsigned SOPK comparisons 2026-09-05 10:38:29 +04:00
nmzikandTechx3 dea80f34ca shader: detect full buffer fills from uniform stores
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-09-05 10:34:30 +04:00
nmzik 994271e2f6 renderdoc: start and stop captures on the present thread 2026-09-05 09:54:42 +04:00
59902d1fd8 shader: skip resources in untaken uniform branches
Co-authored-by: GodsWill <GodsWill@users.noreply.github.com>
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-09-05 09:38:50 +04:00
nmzikandDeathWhispers b92f0a5458 render: share layouts with sampled attachments
Co-authored-by: DeathWhispers <56944463+Death-Whispers-U@users.noreply.github.com>
2026-09-05 08:54:41 +04:00
nmzik 251f6d6121 shader: fix returns and descriptor placement 2026-09-05 08:54:35 +04:00
nmzik 0d8c83a5cb tests: enable Vulkan gather and derivative features 2026-09-05 08:54:14 +04:00
nmzik 3624d96c78 render: fix depth mapping for -1 to 1 viewports 2026-09-05 08:40:05 +04:00
3213934d42 shader: run merged geometry stages as mesh shaders
Co-authored-by: Brandon Strong <brandostrong@gmail.com>
Co-authored-by: fxpw <fxpw@outlook.com>
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-09-05 08:32:23 +04:00
nmzik 246212789f agc: merge resources from both shader stages 2026-09-05 07:39:30 +04:00
nmzikandLordix c7f7fd974b textures: use the backing format for alias uploads
Co-authored-by: Lordix <lordix253634@gmail.com>
2026-09-05 07:30:05 +04:00
5cf2f5767a render: fix volume attachment slice bounds
Co-authored-by: SueryRD <the.ai.tester2@gmail.com>
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-09-05 07:11:46 +04:00
nmzik e8e580cf6c fix ge 2026-09-05 06:53:59 +04:00
nmzik 74a78f37d0 renderer: grow repeatedly joined buffer ranges (perf) 2026-09-05 06:30:31 +04:00
nmzikandAli Almohaya 8d3ebb5a55 renderer: widen buffer readback windows
Co-authored-by: Ali Almohaya <17254073+Almo7aya@users.noreply.github.com>
2026-09-05 05:44:42 +04:00
nmzik 3628d2b4c1 renderer: remove dynamic draw state from pipeline keys 2026-09-05 04:41:41 +04:00
nmzik d96d0eeb18 libPad: ABIs 2026-09-04 07:42:23 +02:00
nmzik bd401a1407 shader: ignore inactive auxiliary position exports 2026-09-04 07:42:23 +02:00
nmzik 070730be0a kernel: report PS5 Base operation mode 2026-09-04 07:42:23 +02:00
nmzik 94e7d0a189 renderer: accept inactive coarse HTILE; drop dead MRT filter 2026-09-04 07:42:23 +02:00
nmzik c28b71f8e0 gpu: centralize depth shading-rate handling 2026-09-04 05:55:28 +02:00
nmzik 27f015ecd1 Revert "graphics: preserve unrestricted viewport depth ranges"
This reverts commit eccd6f6cac.
2026-09-04 05:22:17 +02:00
nmzik 8596627ced shader: share SPIR-V atomic update lowering 2026-09-04 05:10:31 +02:00
nmzik d4cca82a22 libs: stub WebBrowserDialogGetStatus 2026-09-04 05:07:58 +02:00
nmzik dd9ecd5f65 Revert "shader: separate comparison image bindings"
This reverts commit d762d24f73.
2026-09-04 03:09:00 +02:00
nmzik 81b0c1302d Revert "graphics: create depth images for comparison textures"
This reverts commit 0ce19357f6.
2026-09-04 03:08:56 +02:00
Tarunandnmzik b9b0b87545 Add update checker to launcher (#478)
launcher: add release update checker

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-04 02:38:02 +02:00
nmzik 040b2b9653 tests: align GPU fixtures with binding architecture 2026-09-04 02:06:06 +02:00
nmzik eccd6f6cac graphics: preserve unrestricted viewport depth ranges 2026-09-04 02:06:06 +02:00
nmzik 0ce19357f6 graphics: create depth images for comparison textures 2026-09-04 02:06:06 +02:00
nmzik d762d24f73 shader: separate comparison image bindings 2026-09-04 02:06:06 +02:00
nmzik eb917463ad fix File abstraction destructor 2026-09-04 02:06:06 +02:00
Mehmet Cambaz c2189de88f controller: give the DualShock 4 the DualSense pad path and release p… (#453)
controller: give the DualShock 4 the DualSense pad path and release pads on exit
2026-09-04 00:10:57 +02:00
GodsWill Devandnmzik 2fe5c150bc shader: implement BUFFER_ATOMIC_CMPSWAP (#357)
shader: implement RDNA2 buffer atomic compare swap

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-03 23:34:34 +02:00
nmzik 5b8e3b51b0 cross-vendor TLS fix: 0x66 prefixes are forced into 64bit call on AMD 2026-09-03 23:03:48 +02:00
nmzik f27a136430 Refactor(simplify) PM4 draw submission arguments 2026-09-03 22:23:13 +02:00
nmzik fb4d97e8a9 graphics: fix PS5 NGG vertex and instance draw offsets
Refs: https://github.com/KytyPS5/KytyPS5/issues/130
2026-09-03 21:52:02 +02:00
nmzik 5ffcd28ca4 launcher: fix rare use after free bug 2026-09-03 11:32:32 +02:00
nmzik b3c8f91a84 graphics: implement RDNA2 buffer atomic swap x2 2026-09-03 10:17:04 +02:00
nmzik c78a5a9fdf graphics: unify atomic feature and width handling 2026-09-03 05:45:45 +02:00
nmzik 858c18468b graphics: implement RDNA2 buffer atomic OR x2 2026-09-03 05:39:05 +02:00
nmzik cd74809086 launcher: fix light mode, group settings into resizable sections 2026-09-03 04:13:34 +02:00
Brandon Strongandnmzik e21c5f37d0 graphics: honour DCC fixed clear codes on 16-bit and float colour targets (#471)
* Honour DCC fixed clear codes on 16-bit and float colour targets

* tests: DCC fixed clear codes materialise on an RGBA16F target

* graphics: remove fixed clear format comment

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-03 03:41:28 +02:00
Brandon Strong 2d5023fda2 shader: support IMAGE_ATOMIC_SWAP (#456) 2026-09-03 03:37:55 +02:00
nmzik 98200fed19 shader: rework resource specialization and optimize pipeline caching 2026-09-03 03:04:17 +02:00
nmzik c48a8bd6ae shader: evaluate uniform first-lane sampler descriptors 2026-09-03 03:04:17 +02:00
Brandon Strongandnmzik 16ea36eb4b libkernel: register scePthreadRwlockTryrdlock and the timed rwlock variants (#472)
* libkernel: register scePthreadRwlockTryrdlock and the timed rwlock variants

* fix: initialize static rwlocks in newly registered APIs

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-03 01:58:38 +02:00
Brandon Strong 8f6f65f4c9 libs: implement Json Value::referValue by key and Value::toString (#455) 2026-09-03 01:58:23 +02:00
Brandon Strongandnmzik b81f1fc774 libs: implement sceConvertKeycodeGetImeKeyboardType (#469)
* libs: implement sceConvertKeycodeGetImeKeyboardType

* fix: correct ConvertKeycode error codes

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-03 01:57:10 +02:00
Brandon Strong c85bd45604 kernel: implement sceKernelClearVirtualRangeName (#466) 2026-09-03 01:50:14 +02:00
nmzik 5c86f894be shader: simplify typed image bindings 2026-09-02 15:45:16 +02:00
nmzik 86e3c90e32 shader: simplify image resource semantics 2026-09-02 15:45:15 +02:00
nmzik 93be8c201b shader: unify SPIR-V image dimension 2026-09-02 15:44:39 +02:00
nmzik 894bc7de3c shader: support signed sampled images 2026-09-02 15:39:30 +02:00
nmzik fc91fd2b1a libs: simplify ABI registration facades 2026-09-02 02:06:14 +04:00
nmzik a06a005124 controller: simplify input facade API 2026-09-02 02:06:14 +04:00
nmzikandPouare514 e9425e101d shader: add missing RDNA2 ALU opcodes
Add missing scalar and vector opcodes with compute tests.

Original work by @Pouare514 in #176.

Co-authored-by: Pouare514 <julienaulycee@gmail.com>
2026-09-02 01:56:55 +04:00
CheesyPoofs3460andnmzik 04b404f3c9 controller: enable the DualSense HIDAPI driver, light bar and adaptive triggers (#437)
* controller: enable the DualSense HIDAPI driver, light bar and adaptive triggers

Three additions. SDL was opening a DualSense through the generic gamepad path, so SDL_GameControllerHasLED and SDL_GameControllerGetNumTouchpads both reported nothing and no touchpad events ever arrived - enabling the PS5 HIDAPI driver before SDL_InitSubSystem is what makes the light bar and the existing touchpad support reachable. PadSetLightBar validated its arguments and returned OK without touching the pad; it now drives the LED. PadSetTriggerEffect did the same; it now translates the mode and parameters into a DualSense trigger block and sends it.

* controller: fix DualSense effects

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-02 00:53:19 +04:00
nmzik 80225015c5 renderer: enable storage for color images 2026-09-01 23:54:17 +04:00
nmzik 0c4dfd565e renderer: emulate clip-disabled screen-space positions 2026-09-01 21:39:11 +04:00
nmzik 0c8d7a1e49 shader: DPP row-xmask 2026-09-01 21:02:19 +04:00
nmzik a2150c70bb network: implement Linux getsockname 2026-09-01 20:50:57 +04:00
nmzik 160cf5352c http: new abi 2026-09-01 20:50:57 +04:00
nmzik 3b6b3c2040 pm4: handle reserved depth control fields 2026-09-01 20:50:57 +04:00
nmzik 63dd3917e2 reduce runtime logging 2026-09-01 20:50:57 +04:00
nmzik 2333c235f1 loader: TLS patch fix 2026-09-01 20:50:57 +04:00
nmzik dd062ceeeb prevent Escape from exiting 2026-09-01 07:50:37 +04:00
nmzik e591a66b67 Revert "shader: preserve raw VCC bits in S_MOV_B64 (#418)"
This reverts commit 6758d67942.

An EXEC pair loses its mask provenance and falls into the raw high-word path. The saved all-ones high word then keeps waterfall branches true and wedges GPU work.
2026-09-01 07:50:37 +04:00
Brandon Strongandnmzik b8a459e83d graphics: do not arm a fast clear from a read-modify-write dispatch (#428)
* graphics: do not arm a fast clear from a read-modify-write dispatch

* tests: cover compute metadata clear classification

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-01 05:48:06 +04:00
nmzik 7684c343d6 guest_gpu: fix private AGC register regression 2026-09-01 04:24:47 +04:00
259 changed files with 22342 additions and 16740 deletions

No files matched your search

+1 -1
+16 -8
View File
@@ -126,7 +126,7 @@ 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)
project(Kyty${KYTY_PROJECT_NAME}${CMAKE_BUILD_TYPE}${KYTY_COMPILER} VERSION 0.3.0)
include_directories(
${KYTY_THIRD_PARTY_DIR}/gtest/include
@@ -342,6 +342,7 @@ set(inc_headers
${KYTY_THIRD_PARTY_DIR}/SDL2/include
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
${KYTY_THIRD_PARTY_DIR}/SPIRV-Headers/include
${KYTY_THIRD_PARTY_DIR}/fmt/include
${KYTY_THIRD_PARTY_DIR}/xxhash
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
@@ -431,16 +432,15 @@ add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL
)
target_include_directories(shader_vertex_metadata_tests PRIVATE ${inc_headers})
add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ShaderStageRuntimeTests.cpp"
add_executable(resource_materialization_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ResourceMaterializationTests.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})
target_link_libraries(resource_materialization_tests fmt::fmt)
target_include_directories(resource_materialization_tests PRIVATE ${inc_headers})
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ResourceTrackingTests.cpp"
@@ -542,7 +542,7 @@ if(BUILD_TESTING)
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_materialization COMMAND $<TARGET_FILE:resource_materialization_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>)
@@ -551,6 +551,10 @@ if(BUILD_TESTING)
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 graphics_pipeline_rasterization
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --polygon-mode-only)
add_test(NAME graphics_draw_offsets
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --draw-offset-only)
add_test(NAME virtual_memory_allocation
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
add_test(NAME guest_red_zone_patcher
@@ -568,12 +572,16 @@ if(BUILD_TESTING)
add_test(NAME texture_cache_layered_image
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
add_test(NAME host_image_allocation
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --host-image-allocation-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 compute_meta_clear_classification
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --compute-meta-clear-only)
add_test(NAME buffer_cache_dirty_gc
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
@@ -596,7 +604,7 @@ if(BUILD_TESTING)
page_manager_tests
bit_array_tests
shader_vertex_metadata_tests
shader_stage_runtime_tests
resource_materialization_tests
resource_tracking_tests
event_queue_lifetime_tests
kernel_file_system_tests
+8 -2
View File
@@ -90,8 +90,14 @@ starting a large change.
Set up the clang-format hook after cloning:
```powershell
python -m pip install pre-commit
Install `pre-commit` using the method appropriate for your platform:
- **Arch Linux / CachyOS:** `sudo pacman -S pre-commit`
- **Other Linux / macOS / Windows:** `python -m pip install pre-commit`
Then install the Git hook:
```bash
python -m pre_commit install --install-hooks
```
Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 2.2 MiB

+25
View File
@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2014 Jannik Vogel <email@jannikvogel.de>
// SPDX-License-Identifier: CC0-1.0
#pragma once
#include <cstddef>
#include <type_traits>
namespace Common {
template <typename T>
[[nodiscard]] constexpr T AlignUp(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
auto mod{static_cast<T>(value % size)};
value -= mod;
return static_cast<T>(mod == T{0} ? value : value + size);
}
template <typename T>
[[nodiscard]] constexpr T AlignDown(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return static_cast<T>(value - value % size);
}
} // namespace Common
+3 -18
View File
@@ -1,6 +1,5 @@
#include "common/assert.h"
#include "common/debug.h"
#include "common/logging/log.h"
#include "common/subsystems.h"
#include "kytyGitVersion.h"
@@ -12,25 +11,10 @@
namespace Common {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
KYTY_COMPILER == KYTY_COMPILER_CLANG
constexpr int PRINT_STACK_FROM = 4;
#else
constexpr int PRINT_STACK_FROM = 2;
#endif
static std::string BuildFatalReport(const char* title, std::string_view text, const char* file,
int line) {
DebugStack stack;
DebugStack::Trace(&stack);
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)));
}
report += fmt::format("{}\n{} in {}:{}\n", title, text, file, line);
return report;
return fmt::format("--- Build ---\n{}\n{}\n{} in {}:{}\n", KYTY_BUILD_LABEL, title, text, file,
line);
}
static int DbgReport(const char* title, std::string_view text, const char* file, int line) {
@@ -59,6 +43,7 @@ int DbgExitHandler(const char* file, int line, fmt::text_style style, std::strin
}
void DbgExit(int status) {
Subsystems::EmergencyShutdownActive();
std::fflush(nullptr);
std::_Exit(status);
}
-4
View File
@@ -29,11 +29,7 @@ void DbgExit(int status);
} // namespace Common
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
#define EXIT_HALT() (Common::DbgExit(321), 1)
#else
#define EXIT_HALT() (std::_Exit(321), 1)
#endif
#ifndef KYTY_FINAL
#define EXIT_IF(x) \
-13
View File
@@ -1,20 +1,7 @@
#include "common/debug.h"
#include "common/platform/sysDbg.h"
namespace Common {
void DebugStack::Print(int from, [[maybe_unused]] bool with_name) const {
for (int i = from; i < depth; i++) {
printf("[%d] %016" PRIx64 "\n", i - from, static_cast<uint64_t>(GetAddr(i)));
}
}
void DebugStack::Trace(DebugStack* stack) {
stack->depth = DEBUG_MAX_STACK_DEPTH;
SysStackWalk(stack->stack.data(), &stack->depth);
}
std::string Debug::GetCompiler() {
#if KYTY_COMPILER == KYTY_COMPILER_CLANG
return "clang";
+1 -26
View File
@@ -2,42 +2,17 @@
#define KYTY_COMMON_DEBUG_H_
#include "common/common.h"
#include "common/stringUtils.h"
#include <array>
#include <cstring>
#include <string>
namespace Common {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
KYTY_COMPILER == KYTY_COMPILER_CLANG
constexpr int DEBUG_MAX_STACK_DEPTH = 20;
#else
constexpr int DEBUG_MAX_STACK_DEPTH = 15;
#endif
namespace Debug {
std::string GetCompiler();
std::string GetLinker();
std::string GetBitness();
} // namespace Debug
struct DebugStack {
int depth {0};
std::array<void*, DEBUG_MAX_STACK_DEPTH> stack {};
[[nodiscard]] uintptr_t GetAddr(int i) const { return reinterpret_cast<uintptr_t>(stack[i]); }
void Print(int from, bool with_name = true) const;
void CopyTo(DebugStack* s) const {
std::memcpy(s->stack.data(), stack.data(), sizeof(void*) * depth);
s->depth = depth;
}
static void Trace(DebugStack* stack);
};
} // namespace Common
#endif /* KYTY_COMMON_DEBUG_H_ */
+4 -4
View File
@@ -75,7 +75,7 @@ ShaderOptimizationType GetShaderOptimizationType() {
return g_config->shader_optimization_type;
}
ShaderLogDirection GetShaderLogDirection() {
LogDirection GetShaderLogDirection() {
return g_config->shader_log_direction;
}
@@ -95,7 +95,7 @@ bool GraphicsDebugDumpEnabled() {
return g_config->graphics_debug_dump_enabled;
}
OutputDirection GetPrintfDirection() {
LogDirection GetPrintfDirection() {
return g_config->printf_direction;
}
@@ -103,8 +103,8 @@ std::filesystem::path GetPrintfOutputFile() {
return g_config->printf_output_file;
}
ProfilerDirection GetProfilerDirection() {
return g_config->profiler_direction;
bool ProfilerEnabled() {
return g_config->profiler_enabled;
}
bool SpirvDebugPrintfEnabled() {
+8 -12
View File
@@ -21,11 +21,7 @@ struct Lifecycle {
enum class ShaderOptimizationType { None, Size, Performance };
enum class ShaderLogDirection { Silent, Console, File };
enum class ProfilerDirection { None, Network };
enum class OutputDirection { Silent, Console, File };
enum class LogDirection { Silent, Console, File };
enum class PresentMode { Fifo, Mailbox, Immediate };
@@ -47,7 +43,7 @@ struct ConfigOptions {
uint32_t screen_height = 720;
std::string user_name = "Kyty";
int32_t user_id = DEFAULT_USER_ID;
PresentMode present_mode = PresentMode::Fifo;
PresentMode present_mode = PresentMode::Mailbox;
int32_t gpu_index = -1;
bool fullscreen_enabled = false;
uint32_t vblank_frequency = 60;
@@ -55,14 +51,14 @@ struct ConfigOptions {
bool vulkan_validation_enabled = false;
bool shader_validation_enabled = false;
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::None;
ShaderLogDirection shader_log_direction = ShaderLogDirection::Silent;
LogDirection shader_log_direction = LogDirection::Silent;
std::filesystem::path shader_log_folder = "_Shaders";
bool command_buffer_dump_enabled = false;
std::filesystem::path command_buffer_dump_folder = "_Buffers";
bool graphics_debug_dump_enabled = false;
OutputDirection printf_direction = OutputDirection::Silent;
LogDirection printf_direction = LogDirection::Silent;
std::filesystem::path printf_output_file = "_kyty.txt";
ProfilerDirection profiler_direction = ProfilerDirection::None;
bool profiler_enabled = false;
bool spirv_debug_printf_enabled = false;
bool gpu_assisted_validation_enabled = false;
bool renderdoc_enabled = false;
@@ -89,7 +85,7 @@ bool VulkanValidationEnabled();
bool ShaderValidationEnabled();
ShaderOptimizationType GetShaderOptimizationType();
ShaderLogDirection GetShaderLogDirection();
LogDirection GetShaderLogDirection();
std::filesystem::path GetShaderLogFolder();
bool CommandBufferDumpEnabled();
@@ -97,10 +93,10 @@ std::filesystem::path GetCommandBufferDumpFolder();
bool GraphicsDebugDumpEnabled();
OutputDirection GetPrintfDirection();
LogDirection GetPrintfDirection();
std::filesystem::path GetPrintfOutputFile();
ProfilerDirection GetProfilerDirection();
bool ProfilerEnabled();
bool SpirvDebugPrintfEnabled();
+1 -1
View File
@@ -69,7 +69,7 @@ File::File(): m_p(std::make_unique<FilePrivate>()) {
}
File::~File() {
EXIT_IF(m_p->f != nullptr);
Close();
}
File::File(const std::filesystem::path& name): m_p(std::make_unique<FilePrivate>()) {
+54 -7
View File
@@ -5,14 +5,17 @@
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <windows.h> // IWYU pragma: keep
#elif defined(__APPLE__)
#else
#include <algorithm>
#include <csignal>
#include <cstdlib>
#include <initializer_list>
#include <unistd.h>
#if defined(__APPLE__)
#include <sys/ucontext.h>
#else
#include <csignal>
#include <initializer_list>
#include <ucontext.h> // IWYU pragma: keep
#include <unistd.h>
#endif
#endif
// IWYU pragma: no_include <errhandlingapi.h>
@@ -32,6 +35,51 @@ static_assert(decltype(g_handler)::is_always_lock_free);
static_assert(decltype(g_install_state)::is_always_lock_free);
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
// macOS uses the same POSIX platform setting and needs a signal stack too.
class ThreadSignalStack {
public:
ThreadSignalStack() {
const auto page_size = static_cast<size_t>(::getpagesize());
const auto stack_size =
(std::max<size_t>(64 * 1024, MINSIGSTKSZ) + page_size - 1) & ~(page_size - 1);
if (::posix_memalign(&m_memory, page_size, stack_size) != 0) {
return;
}
stack_t stack {};
stack.ss_sp = m_memory;
stack.ss_size = stack_size;
if (::sigaltstack(&stack, &m_previous) != 0) {
std::free(m_memory);
m_memory = nullptr;
}
}
~ThreadSignalStack() {
if (m_memory != nullptr && ::sigaltstack(&m_previous, nullptr) == 0) {
std::free(m_memory);
}
}
[[nodiscard]] bool IsInitialized() const { return m_memory != nullptr; }
KYTY_CLASS_NO_COPY(ThreadSignalStack)
private:
void* m_memory = nullptr;
stack_t m_previous {};
};
bool InitializeThreadSignalStack() {
// Keep fault handling off guest stacks, which GPU tracking can make read-only.
thread_local ThreadSignalStack signal_stack;
return signal_stack.IsInitialized();
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) noexcept {
@@ -253,7 +301,7 @@ bool InstallHandler(Handler handler) {
#elif defined(__APPLE__)
struct sigaction sa {};
sa.sa_sigaction = SignalHandler;
sa.sa_flags = SA_SIGINFO;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
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
@@ -274,8 +322,7 @@ bool InstallHandler(Handler handler) {
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;
action.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK;
for (const int signal_number: {SIGSEGV, SIGBUS, SIGILL}) {
if (::sigaction(signal_number, &action, nullptr) != 0) {
+4
View File
@@ -39,6 +39,10 @@ using Handler = bool (*)(const ExceptionInfo&);
bool InstallHandler(Handler handler);
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
bool InitializeThreadSignalStack();
#endif
} // namespace Common::HostException
#endif /* KYTY_COMMON_HOST_EXCEPTION_H_ */
+3 -3
View File
@@ -152,9 +152,9 @@ void WriteFatal(fmt::text_style style, std::string_view text) {
void Initialize() {
g_initialized = true;
switch (Config::GetPrintfDirection()) {
case Config::OutputDirection::Silent: g_direction = Direction::Silent; break;
case Config::OutputDirection::Console: g_direction = Direction::Console; break;
case Config::OutputDirection::File: g_direction = Direction::File; break;
case Config::LogDirection::Silent: g_direction = Direction::Silent; break;
case Config::LogDirection::Console: g_direction = Direction::Console; break;
case Config::LogDirection::File: g_direction = Direction::File; break;
}
g_output_file =
(g_direction == Direction::File ? Config::GetPrintfOutputFile() : std::filesystem::path {});
+1 -3
View File
@@ -30,8 +30,6 @@ struct sys_dbg_stack_info_t {
#endif
};
void SysStackWalk(void** stack, int* depth);
void SysStackUsage(sys_dbg_stack_info_t& s); // NOLINT(google-runtime-references)
void SysStackUsagePrint(sys_dbg_stack_info_t& stack); // NOLINT(google-runtime-references)
void SysStackUsage(sys_dbg_stack_info_t& s); // NOLINT(google-runtime-references)
#endif /* KYTY_COMMON_PLATFORM_SYSDBG_H_ */
-51
View File
@@ -8,7 +8,6 @@
#include <cstdlib>
#include <cstring>
#include <execinfo.h>
#include <pthread.h>
#include <sys/param.h>
#include <sys/types.h>
@@ -17,56 +16,6 @@
#include <libgen.h> // POSIX basename() lives here on macOS, not in <cstring>
#endif
// 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) {
printf("stack: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.commited_addr),
static_cast<uint64_t>(stack.commited_size));
printf("code: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.code_addr),
static_cast<uint64_t>(stack.code_size));
}
void SysStackUsage(sys_dbg_stack_info_t& s) {
pid_t pid = getpid();
+9 -76
View File
@@ -31,8 +31,7 @@
namespace Common {
static pthread_mutex_t g_virtual_mutex {};
static std::map<uintptr_t, size_t>* g_allocs = nullptr;
static std::map<uintptr_t, int>* g_protects = nullptr;
static std::map<uintptr_t, size_t>* g_allocs = nullptr;
void SysVirtualInit() {
pthread_mutexattr_t attr {};
@@ -46,8 +45,7 @@ void SysVirtualInit() {
pthread_mutex_init(&g_virtual_mutex, &attr);
pthread_mutexattr_destroy(&attr);
g_allocs = new std::map<uintptr_t, size_t>;
g_protects = new std::map<uintptr_t, int>;
g_allocs = new std::map<uintptr_t, size_t>;
}
static int get_protection_flag(VirtualMemory::Mode mode) {
@@ -68,21 +66,6 @@ static int get_protection_flag(VirtualMemory::Mode mode) {
return protect;
}
static VirtualMemory::Mode get_protection_flag(int mode) {
switch (mode) {
case PROT_NONE: return VirtualMemory::Mode::NoAccess;
case PROT_READ: return VirtualMemory::Mode::Read;
case PROT_WRITE: return VirtualMemory::Mode::Write;
case PROT_READ | PROT_WRITE: return VirtualMemory::Mode::ReadWrite; // NOLINT
case PROT_EXEC: return VirtualMemory::Mode::Execute;
case PROT_EXEC | PROT_WRITE: return VirtualMemory::Mode::ExecuteWrite; // NOLINT
case PROT_EXEC | PROT_READ: return VirtualMemory::Mode::ExecuteRead; // NOLINT
case PROT_EXEC | PROT_WRITE | PROT_READ:
return VirtualMemory::Mode::ExecuteReadWrite; // NOLINT
default: return VirtualMemory::Mode::NoAccess;
}
}
// 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
@@ -161,11 +144,6 @@ uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mo
if (ptr != MAP_FAILED) {
pthread_mutex_lock(&g_virtual_mutex);
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;
}
pthread_mutex_unlock(&g_virtual_mutex);
}
@@ -251,11 +229,6 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
pthread_mutex_lock(&g_virtual_mutex);
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;
}
pthread_mutex_unlock(&g_virtual_mutex);
return ret_addr;
@@ -337,11 +310,6 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
if (ptr != MAP_FAILED) {
pthread_mutex_lock(&g_virtual_mutex);
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;
}
pthread_mutex_unlock(&g_virtual_mutex);
return true;
@@ -516,18 +484,7 @@ bool SysVirtualFree(uint64_t address) {
return false;
}
if (munmap(reinterpret_cast<void*>(addr), size) == 0) {
uintptr_t page_start = addr >> 12u;
uintptr_t page_end = (addr + size - 1) >> 12u;
pthread_mutex_lock(&g_virtual_mutex);
for (uintptr_t page = page_start; page <= page_end; page++) {
g_protects->erase(page);
}
pthread_mutex_unlock(&g_virtual_mutex);
return true;
}
return false;
return munmap(reinterpret_cast<void*>(addr), size) == 0;
}
bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
@@ -582,40 +539,16 @@ bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
if (end < alloc_end) {
(*g_allocs)[end] = alloc_end - end;
}
for (uintptr_t page = addr >> 12u; page <= (end - 1u) >> 12u; page++) {
g_protects->erase(page);
}
pthread_mutex_unlock(&g_virtual_mutex);
return true;
}
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
VirtualMemory::Mode* old_mode) {
auto addr = static_cast<uintptr_t>(address);
pthread_mutex_lock(&g_virtual_mutex);
if (old_mode != nullptr) {
if (auto s = g_protects->find(addr >> 12u); s != g_protects->end()) {
*old_mode = get_protection_flag(s->second);
} else {
*old_mode = VirtualMemory::Mode::NoAccess;
}
}
pthread_mutex_unlock(&g_virtual_mutex);
uintptr_t page_start = addr >> 12u;
uintptr_t page_end = (addr + size - 1) >> 12u;
if (mprotect(reinterpret_cast<void*>(page_start << 12u), (page_end - page_start + 1) << 12u,
get_protection_flag(mode)) == 0) {
pthread_mutex_lock(&g_virtual_mutex);
for (uintptr_t page = page_start; page <= page_end; page++) {
(*g_protects)[page] = get_protection_flag(mode);
}
pthread_mutex_unlock(&g_virtual_mutex);
return true;
}
return false;
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
const auto addr = static_cast<uintptr_t>(address);
const auto page_start = addr >> 12u;
const auto page_end = (addr + size - 1) >> 12u;
return mprotect(reinterpret_cast<void*>(page_start << 12u), (page_end - page_start + 1) << 12u,
get_protection_flag(mode)) == 0;
}
bool SysVirtualFlushInstructionCache(uint64_t /*address*/, uint64_t /*size*/) {
+1 -2
View File
@@ -19,8 +19,7 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size);
bool SysVirtualDecommit(uint64_t address, uint64_t size);
bool SysVirtualFree(uint64_t address);
bool SysVirtualFreeRange(uint64_t address, uint64_t size);
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
VirtualMemory::Mode* old_mode = nullptr);
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size);
} // namespace Common
-12
View File
@@ -15,18 +15,6 @@
#include "common/assert.h"
#include "common/platform/sysDbg.h"
void SysStackWalk(void** stack, int* depth) {
*depth = static_cast<int>(CaptureStackBackTrace(0, static_cast<DWORD>(*depth), stack, nullptr));
}
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
printf("stack: (0x%" PRIx64 ", %" PRIu64 ") + (0x%" PRIx64 ", %" PRIu64 ") + (0x%" PRIx64
", %" PRIu64 ")\n",
static_cast<uint64_t>(stack.reserved_addr), static_cast<uint64_t>(stack.reserved_size),
static_cast<uint64_t>(stack.guard_addr), static_cast<uint64_t>(stack.guard_size),
static_cast<uint64_t>(stack.commited_addr), static_cast<uint64_t>(stack.commited_size));
}
void SysStackUsage(sys_dbg_stack_info_t& s) {
MEMORY_BASIC_INFORMATION mbi {};
[[maybe_unused]] size_t ss = VirtualQuery(&mbi, &mbi, sizeof(mbi));
+1 -17
View File
@@ -45,18 +45,6 @@ static DWORD GetProtectionFlag(VirtualMemory::Mode mode) {
return protect;
}
static VirtualMemory::Mode GetProtectionFlag(DWORD mode) {
switch (mode) {
case PAGE_NOACCESS: return VirtualMemory::Mode::NoAccess;
case PAGE_READONLY: return VirtualMemory::Mode::Read;
case PAGE_READWRITE: return VirtualMemory::Mode::ReadWrite;
case PAGE_EXECUTE: return VirtualMemory::Mode::Execute;
case PAGE_EXECUTE_READ: return VirtualMemory::Mode::ExecuteRead;
case PAGE_EXECUTE_READWRITE: return VirtualMemory::Mode::ExecuteReadWrite;
default: return VirtualMemory::Mode::NoAccess;
}
}
void SysVirtualInit() {}
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
@@ -328,17 +316,13 @@ bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
return current - address == size && SysVirtualFree(address);
}
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
VirtualMemory::Mode* old_mode) {
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
DWORD old_protect = 0;
if (VirtualProtect(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
GetProtectionFlag(mode), &old_protect) == 0) {
printf("VirtualProtect() failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
return false;
}
if (old_mode != nullptr) {
*old_mode = GetProtectionFlag(old_protect);
}
return true;
}
+7 -13
View File
@@ -55,19 +55,13 @@ void SetThreadName(const char* name) {
}
void Initialize() {
switch (Config::GetProfilerDirection()) {
case Config::ProfilerDirection::Network:
if (!tracy::ProfilerAvailable()) {
tracy::StartupProfiler();
TracySetProgramName("KytyPS5");
::printf("Tracy profiler enabled: client %d.%d.%d, protocol %u, "
"broadcast %u, connect to 127.0.0.1:8086\n",
tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch,
tracy::ProtocolVersion, tracy::BroadcastVersion);
}
break;
case Config::ProfilerDirection::None:
default: break;
if (Config::ProfilerEnabled() && !tracy::ProfilerAvailable()) {
tracy::StartupProfiler();
TracySetProgramName("KytyPS5");
::printf("Tracy profiler enabled: client %d.%d.%d, protocol %u, "
"broadcast %u, connect to 127.0.0.1:8086\n",
tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch,
tracy::ProtocolVersion, tracy::BroadcastVersion);
}
}
+5 -3
View File
@@ -316,10 +316,12 @@ inline std::string SafeCsv(std::string_view text) {
return std::string(text);
}
inline std::string Utf16ToUtf8(const char16_t* utf16) {
std::u16string input(utf16);
inline std::string Utf16ToUtf8(std::u16string_view utf16) {
if (utf16.empty()) {
return {};
}
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
return convert.to_bytes(input);
return convert.to_bytes(utf16.data(), utf16.data() + utf16.size());
}
inline ByteBuffer HexToBin(std::string_view text) {
-1
View File
@@ -1,7 +1,6 @@
#include "common/threads.h"
#include "common/assert.h"
#include "common/debug.h"
#include <algorithm>
#include <atomic>
+2 -2
View File
@@ -50,8 +50,8 @@ bool FreeRange(uint64_t address, uint64_t size) {
return SysVirtualFreeRange(address, size);
}
bool Protect(uint64_t address, uint64_t size, Mode mode, Mode* old_mode) {
return SysVirtualProtect(address, size, mode, old_mode);
bool Protect(uint64_t address, uint64_t size, Mode mode) {
return SysVirtualProtect(address, size, mode);
}
bool FlushInstructionCache(uint64_t address, uint64_t size) {
+1 -1
View File
@@ -35,7 +35,7 @@ bool ReserveFixed(uint64_t address, uint64_t size);
bool Decommit(uint64_t address, uint64_t size);
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 Protect(uint64_t address, uint64_t size, Mode mode);
bool FlushInstructionCache(uint64_t address, uint64_t size);
} // namespace VirtualMemory
@@ -73,13 +73,9 @@ public:
void SetDrawIndirectArgsBaseAddress(uint64_t draw_indirect_args_base_addr);
void SetDispatchIndirectArgsBaseAddress(uint64_t dispatch_indirect_args_base_addr);
void SetNumInstances(uint32_t num_instances);
void DrawIndex(uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
uint32_t instance_count = 0, const void* object_ids = nullptr,
uint32_t render_target_slice_offset = 0, int32_t vertex_offset_add = 0,
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);
void DrawIndex(DrawIndexArgs args);
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count);
void DrawIndexAuto(DrawAutoArgs args);
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,
@@ -149,14 +145,9 @@ 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(); }
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
void CheckBuffer() const { GetScheduler().CheckActive(); }
GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
RenderContext& m_renderer;
HW::Context m_ctx;
@@ -24,7 +24,6 @@ constexpr auto MakeContextDispatchTable() {
g_hw_ctx_func[Pm4::DB_DEPTH_CLEAR] = HwCtxSetDepthClear;
g_hw_ctx_func[Pm4::PA_SC_SCREEN_SCISSOR_TL] = HwCtxSetScreenScissor;
g_hw_ctx_func[Pm4::DB_STENCIL_INFO] = HwCtxSetStencilInfo;
g_hw_ctx_func[Pm4::DB_SHADING_RATE_ENCODING] = HwCtxSetDepthShadingRateEncoding;
g_hw_ctx_func[Pm4::PA_SU_HARDWARE_SCREEN_OFFSET] = HwCtxSetHardwareScreenOffset;
g_hw_ctx_func[Pm4::PA_SC_WINDOW_OFFSET] = HwCtxSetWindowOffset;
g_hw_ctx_func[Pm4::PA_SC_WINDOW_SCISSOR_TL] = HwCtxSetWindowScissor;
@@ -140,13 +139,13 @@ constexpr auto MakeShaderDispatchTable() {
g_hw_sh_func[Pm4::COMPUTE_TMPRING_SIZE] = HwShSetCsRegisters;
g_hw_sh_func[Pm4::COMPUTE_PGM_RSRC3] = HwShSetCsRegisters;
g_hw_sh_func[Pm4::COMPUTE_PACE_ID] = HwShSetCsRegisters;
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_PS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_GS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_HS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS] = HwShIgnoreRegisters;
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_PS] = HwShSetRegisters;
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_GS] = HwShSetRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS] = HwShSetRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS] = HwShSetRegisters;
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_HS] = HwShSetRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS] = HwShSetRegisters;
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS] = HwShSetRegisters;
return g_hw_sh_func;
}
@@ -45,8 +45,6 @@ uint32_t HwCtxSetStencilClear(CommandProcessor&, uint32_t, uint32_t, const uint3
uint32_t HwCtxSetDepthClear(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetScreenScissor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetStencilInfo(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwCtxSetDepthShadingRateEncoding(CommandProcessor&, uint32_t, uint32_t,
const uint32_t*, uint32_t);
uint32_t HwCtxSetHardwareScreenOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t);
uint32_t HwCtxSetWindowOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
@@ -100,7 +98,7 @@ uint32_t HwShSetCsUserSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t
uint32_t HwShIgnoreUserAccumulator(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
uint32_t);
uint32_t HwShSetCsRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwShIgnoreRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwShSetRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwUcSetPrimitiveType(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwUcSetIndexType(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
uint32_t HwUcSetObjectId(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
@@ -136,7 +136,6 @@ static HW::ColorControl DecodeColorControl(uint32_t value) {
static HW::DepthControl DecodeDepthControl(uint32_t value) {
HW::DepthControl r {};
EXIT_NOT_IMPLEMENTED((value & 0xc0000000u) != 0);
r.stencil_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, STENCIL_ENABLE) != 0;
r.z_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, Z_ENABLE) != 0;
@@ -316,8 +315,17 @@ static void HwCtxSetDepthBoundsRegister(CommandProcessor& cp, uint32_t cmd_offse
}
}
static void HwCtxIgnoreDepthMetadataRegister([[maybe_unused]] uint32_t cmd_offset,
[[maybe_unused]] uint32_t value) {}
static void HwCtxSetDepthMetadataRegister(CommandProcessor& cp, uint32_t cmd_offset,
uint32_t value) {
if (cmd_offset == Pm4::DB_RENDER_OVERRIDE) {
HW::DepthRenderOverride control;
control.force_z_valid = (value & 0x20000000u) != 0;
control.force_z_dirty = (value & 0x08000000u) != 0;
control.force_stencil_valid = (value & 0x40000000u) != 0;
control.force_stencil_dirty = (value & 0x10000000u) != 0;
cp.GetCtx().SetDepthRenderOverride(control);
}
}
static void HwCtxIgnoreFovWindow([[maybe_unused]] uint32_t value) {}
@@ -390,7 +398,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetDepthMetadataRegisters) {
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
for (uint32_t i = 0; i < num_values; i++) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset + i, buffer[i]);
HwCtxSetDepthMetadataRegister(cp, cmd_offset + i, buffer[i]);
}
return num_values;
@@ -600,17 +608,6 @@ static void HwCtxSetDepthShadingRateEncodingRegister(CommandProcessor& cp, uint3
ctx.SetDepthRenderTarget(target);
}
KYTY_HW_CTX_PARSER(HwCtxSetDepthShadingRateEncoding) {
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
EXIT_NOT_IMPLEMENTED(num_values != 1);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::DB_SHADING_RATE_ENCODING);
HwCtxSetDepthShadingRateEncodingRegister(cp, buffer[0]);
return num_values;
}
static HW::EqaaControl ParseEqaaControl(uint32_t value) {
HW::EqaaControl r;
@@ -992,7 +989,7 @@ static void HwShIgnoreComputeRegister([[maybe_unused]] uint32_t cmd_offset,
static void HwShIgnoreShaderRegister([[maybe_unused]] uint32_t cmd_offset,
[[maybe_unused]] uint32_t value) {}
KYTY_HW_SH_PARSER(HwShIgnoreRegisters) {
KYTY_HW_SH_PARSER(HwShSetRegisters) {
const auto reg_num = (cmd_id >> 16u) & 0x3fffu;
EXIT_NOT_IMPLEMENTED(reg_num == 0);
@@ -1619,15 +1616,15 @@ KYTY_CP_OP_PARSER(CpOpDrawIndex) {
auto* index_addr =
reinterpret_cast<void*>(buffer[1] | (static_cast<uint64_t>(buffer[2]) << 32u));
uint32_t max_instance_count = buffer[3];
auto* object_ids =
reinterpret_cast<void*>(buffer[4] | (static_cast<uint64_t>(buffer[5]) << 32u));
uint32_t instance_count = buffer[6];
uint32_t flags = buffer[7];
uint32_t instance_count = buffer[6];
uint32_t flags = buffer[7];
EXIT_NOT_IMPLEMENTED(instance_count > max_instance_count);
EXIT_NOT_IMPLEMENTED((flags & ~0xa0u) != 0);
cp.DrawIndex(index_count, index_addr, 0, 1, instance_count, object_ids);
cp.DrawIndex({.index_count = index_count,
.index_addr = index_addr,
.instance_count = instance_count});
return 8;
}
@@ -1642,7 +1639,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndex) {
EXIT_NOT_IMPLEMENTED(index_count > max_index_count);
EXIT_NOT_IMPLEMENTED((flags & ~0x20u) != 0);
cp.DrawIndex(index_count, index_addr, 0, 1);
cp.DrawIndex({.index_count = index_count, .index_addr = index_addr});
return 5;
}
@@ -1701,7 +1698,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndexOffset) {
EXIT_NOT_IMPLEMENTED(index_count > max_index_size);
EXIT_NOT_IMPLEMENTED((flags & ~0x20u) != 0);
cp.DrawIndexOffset(index_offset, index_count, 0);
cp.DrawIndexOffset(index_offset, index_count);
return 4;
}
@@ -1716,7 +1713,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndexAuto) {
EXIT_NOT_IMPLEMENTED((flags & ~0x22u) != 0);
cp.DrawIndexAuto(index_count, 0);
cp.DrawIndexAuto({.vertex_count = index_count});
return 2;
}
@@ -3015,28 +3012,28 @@ void GraphicsInitJmpTablesCxIndirect() {
HwCtxIgnoreCbDccControl(value);
};
g_hw_ctx_indirect_func[Pm4::DB_COUNT_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
for (auto cmd_offset = Pm4::DB_SRESULTS_COMPARE_STATE0;
cmd_offset <= Pm4::DB_SRESULTS_COMPARE_STATE1; cmd_offset++) {
g_hw_ctx_indirect_func[cmd_offset] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
}
g_hw_ctx_indirect_func[Pm4::DB_RENDER_OVERRIDE] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::DB_RENDER_OVERRIDE2] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::DB_DFSM_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::DB_RMI_L2_CACHE_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::CB_RMI_GL2_CACHE_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDepthMetadataRegister(cmd_offset, value);
HwCtxSetDepthMetadataRegister(cp, cmd_offset, value);
};
g_hw_ctx_indirect_func[Pm4::TA_BC_BASE_ADDR] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreBorderColorTableAddr(cmd_offset, value);
@@ -3635,13 +3632,23 @@ void GraphicsInitJmpTablesShIndirect() {
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PACE_ID_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC4_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
for (uint32_t offset = Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS;
offset <= Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS; offset++) {
g_hw_sh_indirect_func[offset] = [](KYTY_HW_SH_INDIRECT_ARGS) {
cp.GetShCtx().SetGsUserDataAddress(cmd_offset - Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS,
value);
};
}
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_CHKSUM_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC4_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HwShIgnoreShaderRegister(cmd_offset, value);
};
g_hw_sh_indirect_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
@@ -3818,6 +3825,11 @@ void GraphicsInitJmpTablesUcIndirect() {
r.vertex_group_size = KYTY_PM4_GET(value, GE_CNTL, VERT_GRP_SIZE);
cp.GetUcfg().SetGeControl(r);
};
g_hw_uc_indirect_func[Pm4::UC_PARAMETER_OVERSUBSCRIPTION] = [](KYTY_HW_UC_INDIRECT_ARGS) {
(void)cp;
(void)cmd_offset;
(void)value;
};
g_hw_uc_indirect_func[Pm4::GE_USER_VGPR_EN] = [](KYTY_HW_UC_INDIRECT_ARGS) {
HW::GeUserVgprEn r;
r.vgpr1 = KYTY_PM4_GET(value, GE_USER_VGPR_EN, EN_USER_VGPR1) != 0;
+18 -16
View File
@@ -15,6 +15,7 @@ struct FormatInfo {
uint32_t render_target_bytes_per_element;
bool sampled_texture;
bool uint_texture;
bool sint_texture = false;
};
constexpr FormatInfo kFormatInfo[] = {
@@ -24,19 +25,19 @@ constexpr FormatInfo kFormatInfo[] = {
{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::k16SInt, 2, 0, 2, true, false, true},
{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::k8_8SInt, 2, 0, 2, true, false, true},
{BufferFormat::k32UInt, 4, 0, 4, true, true},
{BufferFormat::k32SInt, 4, 0, 4, false, false},
{BufferFormat::k32SInt, 4, 0, 4, true, false, true},
{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_16SInt, 4, 0, 4, true, false, true},
{BufferFormat::k16_16Float, 4, 0, 4, true, false},
{BufferFormat::k11_11_10UInt, 4, 0, 4, true, true},
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
@@ -45,20 +46,20 @@ constexpr FormatInfo kFormatInfo[] = {
{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::k8_8_8_8SInt, 4, 0, 4, true, false, true},
{BufferFormat::k32_32UInt, 8, 0, 8, true, true},
{BufferFormat::k32_32SInt, 8, 0, 8, false, false},
{BufferFormat::k32_32SInt, 8, 0, 8, true, false, true},
{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_16SInt, 8, 0, 8, true, false, true},
{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_32SInt, 12, 0, 12, true, false, true},
{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_32SInt, 16, 0, 16, true, false, true},
{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},
@@ -218,14 +219,15 @@ bool IsFmaskTextureFormat(BufferFormat format) {
return format >= BufferFormat::kFmask8_S2_F1 && format <= BufferFormat::kFmask64_S16_F8;
}
bool IsSampledTextureFormat(BufferFormat format) {
TextureNumericClass SampledTextureNumericClass(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr && info->sampled_texture;
}
bool IsUintTextureFormat(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr && info->uint_texture;
if (info == nullptr || !info->sampled_texture) {
return TextureNumericClass::Unsupported;
}
if (info->sint_texture) {
return TextureNumericClass::Sint;
}
return info->uint_texture ? TextureNumericClass::Uint : TextureNumericClass::Float;
}
BufferFormat RemapTextureFormat(BufferFormat format) {
+3 -2
View File
@@ -37,13 +37,14 @@ struct RenderTargetFormatEncoding {
}
};
enum class TextureNumericClass { Unsupported, Float, Uint, Sint };
RenderTargetFormatEncoding ResolveRenderTargetFormat(ChannelLayout layout, ChannelType type);
uint32_t NumBytesPerElement(BufferFormat format);
uint32_t BlockCompressedBytesPerBlock(BufferFormat format);
uint32_t RenderTargetBytesPerElement(BufferFormat format);
bool IsFmaskTextureFormat(BufferFormat format);
bool IsSampledTextureFormat(BufferFormat format);
bool IsUintTextureFormat(BufferFormat format);
TextureNumericClass SampledTextureNumericClass(BufferFormat format);
BufferFormat RemapTextureFormat(BufferFormat format);
} // namespace Libs::Graphics::Prospero
+59 -85
View File
@@ -13,7 +13,6 @@
#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"
@@ -40,6 +39,21 @@ 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;
struct DrawIndirectArgs {
uint32_t vertex_count_per_instance;
uint32_t instance_count;
uint32_t start_vertex_location;
uint32_t start_instance_location;
};
struct DrawIndexedIndirectArgs {
uint32_t index_count_per_instance;
uint32_t instance_count;
uint32_t start_index_location;
uint32_t base_vertex_location;
uint32_t start_instance_location;
};
class GpuMutexLock final {
public:
explicit GpuMutexLock(Common::Mutex& mutex): m_mutex(mutex) {
@@ -63,7 +77,7 @@ private:
static bool GraphicsRunDebugDumpEnabled() {
return Config::GraphicsDebugDumpEnabled() &&
Config::GetPrintfDirection() != Config::OutputDirection::Silent;
Config::GetPrintfDirection() != Config::LogDirection::Silent;
}
GuestGpu::GuestGpu(RenderContext& renderer): m_renderer(renderer) {
@@ -413,7 +427,7 @@ void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cach
if (!decode_gds(dst_sel, dst_gds)) {
EXIT("unsupported dmaData destination selector 0x%02" PRIx8 "\n", dst_sel);
}
auto& buffer_cache = GetGpuResources().GetBufferCache();
auto& buffer_cache = m_renderer.GetBufferCache();
if (src_sel == 2) {
buffer_cache.FillBuffer(
dst_address_or_offset, num_bytes,
@@ -546,10 +560,6 @@ void GuestGpu::ThreadRun(void* data) {
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) {
@@ -593,11 +603,11 @@ bool GuestGpu::Process(Submission& submission) {
}
if (progressed) {
if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
m_renderer.RunGarbageCollector();
}
cp.BufferFlush();
} else if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
m_renderer.RunGarbageCollector();
}
break;
}
@@ -619,16 +629,16 @@ bool GuestGpu::Process(Submission& submission) {
Pm4ProcessResult::Complete;
if (submission.command_execution.MadeProgress()) {
if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
m_renderer.RunGarbageCollector();
}
cp.BufferFlush();
} else if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
m_renderer.RunGarbageCollector();
}
break;
}
case SubmissionType::FlipPreparation:
m_renderer.GetGpuResources().RunGarbageCollector();
m_renderer.RunGarbageCollector();
cp.PrepareCpuFlip(submission.flip_request_id);
break;
}
@@ -847,36 +857,21 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
}
}
void CommandProcessor::DrawIndex(uint32_t index_count, const void* index_addr, uint32_t flags,
uint32_t type, uint32_t instance_count, const void* object_ids,
uint32_t render_target_slice_offset, int32_t vertex_offset_add,
uint32_t first_instance) {
void CommandProcessor::DrawIndex(DrawIndexArgs args) {
CheckBuffer();
if (instance_count == 0) {
instance_count = m_num_instances;
args.index_type_and_size = m_index_type_and_size;
if (args.instance_count == 0) {
args.instance_count = m_num_instances;
}
if (object_ids != nullptr) {
LOGF("\t draw indexed multi-instanced objectIds = 0x%016" PRIx64 "\n",
reinterpret_cast<uint64_t>(object_ids));
if (args.base_vertex != 0 || args.first_instance != 0) {
LOGF("\t draw indexed offsets: base_vertex = %" PRId32 ", first_instance = %" PRIu32 "\n",
args.base_vertex, args.first_instance);
}
if (render_target_slice_offset != 0) {
LOGF("\t draw render target slice offset = %" PRIu32 "\n", render_target_slice_offset);
}
if (vertex_offset_add != 0 || first_instance != 0) {
LOGF("\t draw indexed offsets: vertex_offset_add = %" PRId32 ", first_instance = %" PRIu32
"\n",
vertex_offset_add, first_instance);
}
m_renderer.GetRenderExecutor().DrawIndex(
m_submit_id, CurrentBuffer(), m_index_type_and_size, index_count, index_addr, flags, type,
instance_count, render_target_slice_offset, vertex_offset_add, first_instance);
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), args);
}
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count,
uint32_t flags) {
CheckBuffer();
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) {
uint64_t index_size = 0;
switch (m_index_type_and_size) {
case 0: index_size = 2; break;
@@ -888,25 +883,10 @@ 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);
DrawIndex({.index_count = index_count, .index_addr = index_addr});
}
void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed) {
struct DrawIndirectArgs {
uint32_t vertex_count_per_instance;
uint32_t instance_count;
uint32_t start_vertex_location;
uint32_t start_instance_location;
};
struct DrawIndexedIndirectArgs {
uint32_t index_count_per_instance;
uint32_t instance_count;
uint32_t start_index_location;
uint32_t base_vertex_location;
uint32_t start_instance_location;
};
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != 2u);
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
@@ -928,8 +908,11 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
}
}
m_num_instances = args.instance_count;
SubmitNonIndexedDraw(args.vertex_count_per_instance, 0, 0, args.start_vertex_location,
args.start_instance_location);
DrawIndexAuto({.vertex_count = args.vertex_count_per_instance,
.instance_count = args.instance_count,
.first_vertex = args.start_vertex_location,
.first_instance = args.start_instance_location,
.offset_source = DrawOffsetSource::IndirectArgs});
return;
}
@@ -970,28 +953,18 @@ 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);
DrawIndex({.index_count = index_count,
.index_addr = index_addr,
.instance_count = args.instance_count,
.base_vertex = static_cast<int32_t>(args.base_vertex_location),
.first_instance = args.start_instance_location,
.offset_source = DrawOffsetSource::IndirectArgs});
}
void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
const volatile uint32_t* count_addr,
uint32_t stride_in_bytes, uint32_t draw_initiator,
bool indexed) {
struct DrawIndirectArgs {
uint32_t vertex_count_per_instance;
uint32_t instance_count;
uint32_t start_vertex_location;
uint32_t start_instance_location;
};
struct DrawIndexedIndirectArgs {
uint32_t index_count_per_instance;
uint32_t instance_count;
uint32_t start_index_location;
uint32_t base_vertex_location;
uint32_t start_instance_location;
};
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != 2u);
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
@@ -1028,8 +1001,11 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
}
}
m_num_instances = args->instance_count;
SubmitNonIndexedDraw(args->vertex_count_per_instance, 0, 0, args->start_vertex_location,
args->start_instance_location);
DrawIndexAuto({.vertex_count = args->vertex_count_per_instance,
.instance_count = args->instance_count,
.first_vertex = args->start_vertex_location,
.first_instance = args->start_instance_location,
.offset_source = DrawOffsetSource::IndirectArgs});
continue;
}
@@ -1071,8 +1047,12 @@ 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);
DrawIndex({.index_count = index_count,
.index_addr = index_addr,
.instance_count = args->instance_count,
.base_vertex = static_cast<int32_t>(args->base_vertex_location),
.first_instance = args->start_instance_location,
.offset_source = DrawOffsetSource::IndirectArgs});
}
}
@@ -1163,19 +1143,13 @@ void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) {
DispatchDirect(args->thread_group_x, args->thread_group_y, args->thread_group_z, mode);
}
void CommandProcessor::DrawIndexAuto(uint32_t index_count, uint32_t flags,
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) {
void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
CheckBuffer();
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), vertex_count, flags,
render_target_slice_offset, m_num_instances,
first_vertex, first_instance);
if (args.instance_count == 0) {
args.instance_count = m_num_instances;
}
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), args);
}
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
+46 -25
View File
@@ -202,6 +202,13 @@ struct RenderControl {
uint8_t copy_sample = 0;
};
struct DepthRenderOverride {
bool force_z_valid = false;
bool force_z_dirty = false;
bool force_stencil_valid = false;
bool force_stencil_dirty = false;
};
struct GdsOaCounter {
uint32_t counter = 0;
uint32_t address = 0;
@@ -362,16 +369,16 @@ struct AaConfig {
struct Viewport {
float zmin = 0.0f;
float zmax = 0.0f;
float xscale = 0.0f;
float xscale = 1.0f;
float xoffset = 0.0f;
float yscale = 0.0f;
float yscale = 1.0f;
float yoffset = 0.0f;
float zscale = 0.0f;
float zscale = 1.0f;
float zoffset = 0.0f;
int viewport_scissor_left = 0;
int viewport_scissor_top = 0;
int viewport_scissor_right = 0;
int viewport_scissor_bottom = 0;
int viewport_scissor_right = 16384;
int viewport_scissor_bottom = 16384;
bool viewport_scissor_window_offset_enable = false;
};
@@ -380,26 +387,26 @@ struct ScreenViewport {
uint32_t transform_control = 1087;
int screen_scissor_left = 0;
int screen_scissor_top = 0;
int screen_scissor_right = 0;
int screen_scissor_bottom = 0;
int screen_scissor_right = 16384;
int screen_scissor_bottom = 16384;
int window_scissor_left = 0;
int window_scissor_top = 0;
int window_scissor_right = 0;
int window_scissor_bottom = 0;
int window_scissor_right = 16384;
int window_scissor_bottom = 16384;
bool window_scissor_window_offset_enable = false;
int generic_scissor_left = 0;
int generic_scissor_top = 0;
int generic_scissor_right = 0;
int generic_scissor_bottom = 0;
int generic_scissor_right = 16384;
int generic_scissor_bottom = 16384;
bool generic_scissor_window_offset_enable = false;
int window_offset_x = 0;
int window_offset_y = 0;
uint32_t hw_offset_x = 0;
uint32_t hw_offset_y = 0;
float guard_band_horz_clip = 0.0f;
float guard_band_vert_clip = 0.0f;
float guard_band_horz_discard = 0.0f;
float guard_band_vert_discard = 0.0f;
float guard_band_horz_clip = 1.0f;
float guard_band_vert_clip = 1.0f;
float guard_band_horz_discard = 1.0f;
float guard_band_vert_discard = 1.0f;
uint16_t clip_rect_rule = 0xffffu;
int clip_rect_left[4] = {};
int clip_rect_top[4] = {};
@@ -520,7 +527,8 @@ struct GsShaderResource2 {
};
struct GsStageRegisters {
uint64_t data_addr = 0;
uint64_t data_addr = 0;
uint64_t user_data_addr = 0;
GsShaderResource1 rsrc1;
GsShaderResource2 rsrc2;
};
@@ -808,6 +816,12 @@ public:
void SetClipControl(const ClipControl& control) { m_clip_control = control; }
[[nodiscard]] const RenderControl& GetRenderControl() const { return m_render_control; }
void SetRenderControl(const RenderControl& control) { m_render_control = control; }
[[nodiscard]] const DepthRenderOverride& GetDepthRenderOverride() const {
return m_depth_render_override;
}
void SetDepthRenderOverride(const DepthRenderOverride& control) {
m_depth_render_override = control;
}
[[nodiscard]] const DepthControl& GetDepthControl() const { return m_depth_control; }
void SetDepthControl(const DepthControl& control) { m_depth_control = control; }
[[nodiscard]] const ModeControl& GetModeControl() const { return m_mode_control; }
@@ -903,15 +917,16 @@ private:
uint32_t m_shader_stages = 0;
DepthRenderTarget m_depth_render_target;
RenderControl m_render_control;
DepthControl m_depth_control;
StencilControl m_stencil_control;
StencilMask m_stencil_mask;
float m_depth_clear_value = 0.0f;
float m_depth_bounds_min = 0.0f;
float m_depth_bounds_max = 1.0f;
uint8_t m_stencil_clear_value = 0;
DepthRenderTarget m_depth_render_target;
RenderControl m_render_control;
DepthRenderOverride m_depth_render_override;
DepthControl m_depth_control;
StencilControl m_stencil_control;
StencilMask m_stencil_mask;
float m_depth_clear_value = 0.0f;
float m_depth_bounds_min = 0.0f;
float m_depth_bounds_max = 1.0f;
uint8_t m_stencil_clear_value = 0;
ModeControl m_mode_control;
PolyOffset m_poly_offset;
@@ -976,6 +991,12 @@ public:
void SetHsShaderResource1(const HsShaderResource1& rsrc1) { m_vs.hs_regs.rsrc1 = rsrc1; }
void SetHsShaderResource2(const HsShaderResource2& rsrc2) { m_vs.hs_regs.rsrc2 = rsrc2; }
void SetGsShaderBase(uint64_t addr) { m_vs.gs_regs.data_addr = addr; }
void SetGsUserDataAddress(uint32_t word, uint32_t value) {
const auto shift = word * 32u;
auto& address = m_vs.gs_regs.user_data_addr;
address = (address & ~(uint64_t {0xffffffffu} << shift)) |
(static_cast<uint64_t>(value) << shift);
}
void SetGsShaderResource1(const GsShaderResource1& rsrc1) { m_vs.gs_regs.rsrc1 = rsrc1; }
void SetGsShaderResource2(const GsShaderResource2& rsrc2) { m_vs.gs_regs.rsrc2 = rsrc2; }
+3 -2
View File
@@ -834,8 +834,9 @@ 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_LO_ES = 0xC8;
constexpr uint32_t SPI_SHADER_PGM_HI_ES = 0xC9;
// Retained only as an opaque AGC compiler-record tag; 0x100 is not a PS5 SH register.
// Private AGC registers used by shader binaries submitted through SET_SH_REG_INDIRECT.
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_GRAPHICS_SHADER_CONTROL_HS = 0x107;
@@ -976,7 +977,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 UC_PARAMETER_OVERSUBSCRIPTION = 0x260;
constexpr uint32_t GE_USER_VGPR_EN = 0x262;
constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR1_SHIFT = 0;
+40 -20
View File
@@ -1,5 +1,6 @@
#include "graphics/guest_gpu/tile.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/profiler.h"
@@ -15,10 +16,6 @@
namespace Libs::Graphics {
static uint32_t AlignUp(uint32_t value, uint32_t alignment) {
return (value + alignment - 1u) & ~(alignment - 1u);
}
static uint32_t ShiftCeil(uint32_t value, uint32_t shift) {
return static_cast<uint32_t>((static_cast<uint64_t>(value) + (1ull << shift) - 1ull) >> shift);
}
@@ -33,7 +30,7 @@ static uint32_t CalcLinearAlignedLevelPitch(uint32_t base_width, uint32_t base_h
const uint32_t level_width = ShiftCeil(base_width, level);
const uint32_t level_height = ShiftCeil(base_height, level);
const uint32_t padded_width =
AlignUp(std::max(level_width, 1u), CalcLinearBlockWidth(bytes_per_element));
Common::AlignUp(std::max(level_width, 1u), CalcLinearBlockWidth(bytes_per_element));
const uint64_t size =
static_cast<uint64_t>(padded_width) * std::max(level_height, 1u) * bytes_per_element;
EXIT_NOT_IMPLEMENTED(size > 0xffffffffull);
@@ -61,7 +58,7 @@ static uint32_t SetLinearMipChainLayout(uint32_t levels, const uint32_t* mip_pit
offset += mip_size[level];
}
return AlignUp(offset, 256u);
return Common::AlignUp(offset, 256u);
}
struct Log2BlockDimensions {
@@ -366,8 +363,8 @@ bool TileGetTiledTextureLayout(const TileSurfaceDescription& description, TileSu
((description.width >> level) + texture.texel_width - 1u) / texture.texel_width, 1u);
mip.height = std::max(
((description.height >> level) + texture.texel_height - 1u) / texture.texel_height, 1u);
mip.padded_width = AlignUp(std::max(ShiftCeil(width0, level), 1u), block.block_width);
mip.padded_height = AlignUp(std::max(ShiftCeil(height0, level), 1u), block.block_height);
mip.padded_width = Common::AlignUp(std::max(ShiftCeil(width0, level), 1u), block.block_width);
mip.padded_height = Common::AlignUp(std::max(ShiftCeil(height0, level), 1u), block.block_height);
mip.size = static_cast<uint64_t>(block.block_depth) * mip.padded_width * mip.padded_height *
block.bytes_per_element;
result.block_slice_size += mip.size;
@@ -1121,8 +1118,8 @@ static bool TileGetHtileSize(uint32_t width, uint32_t height, TileSizeAlign& hti
}
// Prospero HTile stores one DWORD per depth tile. Its 32 KiB allocation blocks cover
// 1024x512 pixels, independently of the attachment's fragment count.
const uint64_t size = static_cast<uint64_t>(AlignUp(width, 1024u) / 1024u) *
(AlignUp(height, 512u) / 512u) * 32768u;
const uint64_t size = static_cast<uint64_t>(Common::AlignUp(width, 1024u) / 1024u) *
(Common::AlignUp(height, 512u) / 512u) * 32768u;
if (size == 0 || size > UINT32_MAX) {
return false;
}
@@ -1154,13 +1151,13 @@ bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch,
&stencil_block_height));
const uint32_t fragments = 1u << num_fragments_log2;
const uint64_t depth_bytes_total =
valid_blocks ? static_cast<uint64_t>(AlignUp(width, depth_block_width)) *
AlignUp(height, depth_block_height) * depth_bytes * fragments
valid_blocks ? static_cast<uint64_t>(Common::AlignUp(width, depth_block_width)) *
Common::AlignUp(height, depth_block_height) * depth_bytes * fragments
: 0;
const uint64_t stencil_bytes_total =
stencil_format == Prospero::StencilFormat::k8UInt && valid_blocks
? static_cast<uint64_t>(AlignUp(width, stencil_block_width)) *
AlignUp(height, stencil_block_height) * fragments
? static_cast<uint64_t>(Common::AlignUp(width, stencil_block_width)) *
Common::AlignUp(height, stencil_block_height) * fragments
: 0;
TileSizeAlign calculated_htile {};
const bool htile_valid = !htile || TileGetHtileSize(width, height, calculated_htile);
@@ -1187,8 +1184,7 @@ uint32_t TileGetRenderTargetPitch(uint32_t width, uint32_t bytes_per_element,
&block_width, &block_height)) {
return 0;
}
const uint64_t pitch = (static_cast<uint64_t>(width) + block_width - 1u) &
~static_cast<uint64_t>(block_width - 1u);
const uint64_t pitch = Common::AlignUp(static_cast<uint64_t>(width), block_width);
return pitch <= UINT32_MAX ? static_cast<uint32_t>(pitch) : 0;
}
@@ -1209,8 +1205,7 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
pitch != TileGetRenderTargetPitch(width, bytes_per_element, num_fragments_log2)) {
return false;
}
const uint64_t padded_height = (static_cast<uint64_t>(height) + block_height - 1u) &
~static_cast<uint64_t>(block_height - 1u);
const uint64_t padded_height = Common::AlignUp(static_cast<uint64_t>(height), block_height);
const uint64_t size = static_cast<uint64_t>(pitch) * padded_height * bytes_per_element *
(1u << num_fragments_log2);
if (size == 0 || size > UINT32_MAX) {
@@ -1221,6 +1216,31 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
return true;
}
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
TileSizeAlign& total_size, uint32_t num_fragments_log2) {
total_size = {};
if (width == 0 || height == 0 || slices == 0 || levels != 1 || num_fragments_log2 != 0 ||
!std::has_single_bit(bytes_per_element) || bytes_per_element > 16 ||
(tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth)) {
return false;
}
// Gen5 color metadata uses 4 KiB blocks, with one byte per 256 bytes of color data.
// Metadata stays thin for volume surfaces, so every depth slice has its own block raster.
const uint32_t coverage_bits = 20u - std::countr_zero(bytes_per_element);
const uint32_t block_width = 1u << ((coverage_bits + 1u) / 2u);
const uint32_t block_height = 1u << (coverage_bits / 2u);
const uint64_t blocks_x = (static_cast<uint64_t>(width) + block_width - 1u) / block_width;
const uint64_t blocks_y = (static_cast<uint64_t>(height) + block_height - 1u) / block_height;
const uint64_t blocks = blocks_x * blocks_y;
if (blocks > UINT32_MAX / 4096u / slices) {
return false;
}
total_size.size = static_cast<uint32_t>(blocks * slices * 4096u);
total_size.align = 4096;
return true;
}
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,
@@ -1347,7 +1367,7 @@ uint32_t TileGetTexturePitch(Prospero::BufferFormat format, uint32_t width,
TileTextureElementLayout element {};
if (TileGetTextureElementLayout(format, element) && element.texel_width == 1 &&
element.texel_height == 1) {
pitch = AlignUp(pitch, CalcLinearBlockWidth(element.bytes));
pitch = Common::AlignUp(pitch, CalcLinearBlockWidth(element.bytes));
}
break;
}
@@ -1358,7 +1378,7 @@ uint32_t TileGetTexturePitch(Prospero::BufferFormat format, uint32_t width,
case Prospero::TileMode::kRenderTarget: {
TileTextureBlockLayout layout {};
if (TileGetTextureBlockLayout(format, tile, false, layout)) {
pitch = AlignUp(pitch, layout.block.block_width * layout.texel_width);
pitch = Common::AlignUp(pitch, layout.block.block_width * layout.texel_width);
}
break;
}
+3
View File
@@ -122,6 +122,9 @@ uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element,
bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
uint32_t bytes_per_element, TileSizeAlign& total_size,
uint32_t num_fragments_log2 = 0);
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
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,
+11 -31
View File
@@ -14,9 +14,7 @@
namespace Libs::Graphics {
struct VulkanBuffer;
struct VulkanImage;
struct VulkanMemory;
inline constexpr uint32_t VULKAN_TARGET_API_VERSION = VK_API_VERSION_1_3;
@@ -29,10 +27,13 @@ struct GraphicContext {
vk::Device device = nullptr;
VmaAllocator allocator = nullptr;
bool memory_budget_ext_enabled = false;
bool rt_extensions_enabled = false;
bool compute_subgroup_size_control_enabled = false;
bool compute_wave64_supported = false;
bool sample_rate_shading_enabled = false;
bool attachment_feedback_loop_enabled = false;
bool provoking_vertex_last_enabled = false;
bool supports_block_texel_view = false;
bool mesh_shader_enabled = false;
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
uint32_t subgroup_size = 0;
uint32_t min_subgroup_size = 0;
uint32_t max_subgroup_size = 0;
@@ -80,6 +81,10 @@ struct GraphicContext {
return it->second.first;
}
[[nodiscard]] bool SupportsComputeWave64() const noexcept {
return subgroup_size == 64u || compute_subgroup_size_control_enabled;
}
[[nodiscard]] vk::DeviceSize StorageMinAlignment() const {
const auto alignment = physical_device_properties.limits.minStorageBufferOffsetAlignment;
return alignment != 0 ? alignment : 1;
@@ -91,15 +96,8 @@ struct GraphicContext {
[[nodiscard]] bool CanReportMemoryUsage() const noexcept { return memory_budget_ext_enabled; }
[[nodiscard]] uint64_t GetDeviceMemoryUsage() const;
[[nodiscard]] uint64_t GetTotalMemoryBudget() const;
void CreateBuffer(uint64_t size, VulkanBuffer& buffer);
[[nodiscard]] bool CreateImage(const vk::ImageCreateInfo& info, VulkanImage& image);
void DeleteImage(VulkanImage& image);
void MapMemory(VulkanMemory& memory, void*& data);
void UnmapMemory(VulkanMemory& memory);
void AppendHardwareRayTracingDeviceExtensions(
const std::vector<vk::ExtensionProperties>& available_extensions,
std::vector<const char*>& device_extensions);
void LoadHardwareRayTracingFunctions() const;
uint32_t screen_width = 0;
uint32_t screen_height = 0;
@@ -114,18 +112,6 @@ private:
m_image_format_properties;
};
struct VulkanMemory {
vk::MemoryRequirements requirements = {};
vk::MemoryPropertyFlags property = {};
vk::MemoryPropertyFlags preferred_property = {};
vk::DeviceMemory memory = nullptr;
VmaAllocation allocation = nullptr;
VmaAllocationInfo allocation_info = {};
vk::DeviceSize offset = 0;
uint32_t type = 0;
uint64_t unique_id = 0;
};
struct VulkanImageState {
vk::PipelineStageFlags2 pl_stage = vk::PipelineStageFlagBits2::eAllCommands;
vk::AccessFlags2 access_mask = vk::AccessFlagBits2::eNone;
@@ -139,7 +125,6 @@ struct 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;
@@ -148,15 +133,10 @@ struct VulkanImage {
vk::Image image = nullptr;
VulkanImageState state;
std::vector<VulkanImageState> subresource_states;
Graphics::VulkanMemory memory;
VmaAllocation allocation = nullptr;
};
struct VulkanBuffer {
vk::Buffer buffer = nullptr;
VulkanMemory memory;
vk::BufferUsageFlags usage = {};
uint64_t buffer_size = 0;
};
} // namespace Libs::Graphics
+7 -19
View File
@@ -1,5 +1,6 @@
#include "graphics/host_gpu/memoryTracker.h"
#include "common/alignment.h"
#include "common/assert.h"
namespace Libs::Graphics {
@@ -8,9 +9,6 @@ static_assert(std::atomic<void*>::is_always_lock_free);
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);
}
}
MemoryTracker::~MemoryTracker() = default;
@@ -23,10 +21,7 @@ void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr,
EXIT("MemoryTracker: invalid dirty-page validation range\n");
}
for (auto page = vaddr; page < vaddr + size; page += TRACKER_PAGE_SIZE) {
bool found = false;
dirty.ForEachIntersection(page, TRACKER_PAGE_SIZE,
[&found](RangeSet::Range) { found = true; });
if (!found) {
if (!dirty.Intersects(page, TRACKER_PAGE_SIZE)) {
EXIT("MemoryTracker: GPU-dirty tracker page has no dirty bytes, operation=%s "
"addr=0x%016" PRIx64 "\n",
operation, page);
@@ -37,12 +32,10 @@ void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr,
void MemoryTracker::ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation) {
ValidateRange(vaddr, size);
const auto begin = vaddr & ~(TRACKER_PAGE_SIZE - 1);
const auto end = (vaddr + size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
const auto begin = Common::AlignDown(vaddr, TRACKER_PAGE_SIZE);
const auto end = Common::AlignUp(vaddr + size, TRACKER_PAGE_SIZE);
for (auto page = begin; page < end; page += TRACKER_PAGE_SIZE) {
bool has_dirty_bytes = false;
dirty.ForEachIntersection(page, TRACKER_PAGE_SIZE,
[&has_dirty_bytes](RangeSet::Range) { has_dirty_bytes = true; });
const bool has_dirty_bytes = dirty.Intersects(page, TRACKER_PAGE_SIZE);
if (IsRegionGpuModified(page, TRACKER_PAGE_SIZE) != has_dirty_bytes) {
EXIT("MemoryTracker: tracker and byte ownership disagree, operation=%s "
"addr=0x%016" PRIx64 "\n",
@@ -113,7 +106,8 @@ void MemoryTracker::UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
});
}
void MemoryTracker::UntrackMemoryImpl(uint64_t vaddr, uint64_t size) {
void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
std::vector<RegionManager*> managers;
managers.reserve((vaddr % TRACKER_REGION_SIZE + size + TRACKER_REGION_SIZE - 1) /
TRACKER_REGION_SIZE);
@@ -134,12 +128,6 @@ void MemoryTracker::UntrackMemoryImpl(uint64_t vaddr, uint64_t size) {
Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
});
locks.clear();
}
void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
UntrackMemoryImpl(vaddr, size);
}
} // namespace Libs::Graphics
+10 -37
View File
@@ -34,7 +34,6 @@ public:
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 = [&] {
@@ -63,43 +62,18 @@ public:
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::vector<RegionManager*> managers;
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) {
managers.push_back(manager);
});
std::vector<std::unique_lock<TrackingSpinLock>> locks;
locks.reserve(managers.size());
for (auto* manager: managers) {
locks.emplace_back(manager->lock);
}
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
const auto address = manager->GetCpuAddr() + offset;
manager->template ForEachModifiedRange<DirtySource::Gpu, false>(address, bytes,
preflight);
});
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
manager->template ForEachModifiedRange<DirtySource::Gpu, false>(
manager->GetCpuAddr() + offset, bytes, func);
});
if constexpr (clear) {
Iterate<false>(vaddr, size,
[&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
const auto address = manager->GetCpuAddr() + offset;
manager->template ForEachModifiedRange<DirtySource::Gpu, true>(
address, bytes, [](uint64_t, uint64_t) noexcept {});
});
}
}
template <bool clear, typename Func>
void ForEachDownloadRange(uint64_t vaddr, uint64_t size, Func&& func) {
ForEachDownloadRange<clear>(
vaddr, size, [](uint64_t, uint64_t) noexcept {}, std::forward<Func>(func));
static_assert(std::is_nothrow_invocable_v<Func&, uint64_t, uint64_t>);
CheckNotInUploadCallback();
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
const auto address = manager->GetCpuAddr() + offset;
manager->template ForEachModifiedRange<DirtySource::Gpu, false>(address, bytes, func);
if constexpr (clear) {
manager->template ChangeState<DirtySource::Gpu, false>(address, bytes);
}
});
}
template <typename RangeFunc, typename UploadFunc>
@@ -171,7 +145,6 @@ private:
}
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;
+15 -68
View File
@@ -1,6 +1,7 @@
#include "graphics/host_gpu/pageManager.h"
#include "common/platform/sysDbg.h"
#include "common/alignment.h"
#include "common/virtualMemory.h"
#include "graphics/host_gpu/regionDefinitions.h"
#include "kernel/memory.h"
@@ -21,10 +22,7 @@
#include <windows.h>
#undef min
#undef max
#elif defined(__APPLE__)
#include <unistd.h>
#else
#include <execinfo.h>
#include <unistd.h>
#endif
@@ -36,38 +34,12 @@ constexpr uint64_t REGION_SIZE = TRACKER_REGION_SIZE;
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.
// 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;
#endif
constexpr uint64_t REGION_PAGES = REGION_SIZE / PAGE_SIZE;
constexpr uint32_t NO_ACCESS_PROTECTION = PAGE_NOACCESS;
constexpr uint32_t READ_ONLY_PROTECTION = PAGE_READONLY;
constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
[[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
const auto image_base = reinterpret_cast<uintptr_t>(GetModuleHandleW(nullptr));
for (int i = 0; i < frame_count; i++) {
const auto address = reinterpret_cast<uintptr_t>(frames[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
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
@@ -86,15 +58,6 @@ constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
std::_Exit(322);
}
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 {
public:
explicit SpinGuard(std::atomic_flag& lock): m_lock(lock) {
@@ -115,15 +78,6 @@ void ValidateRange(uint64_t vaddr, uint64_t size) {
}
}
uint64_t PageStart(uint64_t vaddr) {
return vaddr & ~(PAGE_SIZE - 1);
}
uint64_t PageEnd(uint64_t vaddr, uint64_t size) {
ValidateRange(vaddr, size);
return PageStart(vaddr + size - 1) + PAGE_SIZE;
}
} // namespace
struct PageManager::Impl {
@@ -131,14 +85,14 @@ struct PageManager::Impl {
uint8_t write_watchers : 7 = 0;
uint8_t access_watchers : 1 = 0;
[[nodiscard]] uint32_t Perms() const noexcept {
[[nodiscard]] Common::VirtualMemory::Mode Perms() const noexcept {
if (access_watchers != 0) {
return NO_ACCESS_PROTECTION;
return Common::VirtualMemory::Mode::NoAccess;
}
if (write_watchers != 0) {
return READ_ONLY_PROTECTION;
return Common::VirtualMemory::Mode::Read;
}
return READ_WRITE_PROTECTION;
return Common::VirtualMemory::Mode::ReadWrite;
}
template <int delta, bool is_read>
@@ -202,9 +156,6 @@ struct PageManager::Impl {
}
#endif
regions = std::make_unique<std::atomic<Region*>[]>(REGION_COUNT);
for (uint64_t i = 0; i < REGION_COUNT; i++) {
regions[i].store(nullptr, std::memory_order_relaxed);
}
}
~Impl() {
@@ -239,11 +190,10 @@ struct PageManager::Impl {
return ptr;
}
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);
void Protect(uint64_t vaddr, uint64_t size, Common::VirtualMemory::Mode mode) noexcept {
if (!Libs::LibKernel::Memory::ProtectGuestHostMemory(vaddr, size, mode)) {
Fatal("address-space protection failed at 0x%016" PRIx64 ", mode=0x%08" PRIx32, vaddr,
static_cast<uint32_t>(mode));
}
}
@@ -300,11 +250,12 @@ struct PageManager::Impl {
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);
ValidateRange(vaddr, size);
const auto begin = Common::AlignDown(vaddr, PAGE_SIZE);
const auto end = Common::AlignUp(vaddr + size, PAGE_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;
const auto chunk_end = std::min(end, Common::AlignUp(chunk_begin + 1, REGION_SIZE));
const auto region_base = Common::AlignDown(chunk_begin, REGION_SIZE);
auto* region = track ? GetOrCreateRegion(chunk_begin) : FindRegion(chunk_begin);
if (region == nullptr) {
Fatal("untracking unknown page 0x%016" PRIx64, chunk_begin);
@@ -371,8 +322,4 @@ template void PageManager::UpdatePageWatchersForRegion<true, false>(uint64_t, Re
template void PageManager::UpdatePageWatchersForRegion<false, true>(uint64_t, RegionBits&);
template void PageManager::UpdatePageWatchersForRegion<false, false>(uint64_t, RegionBits&);
void PageManager::OnGpuMap(uint64_t, uint64_t) {}
void PageManager::OnGpuUnmap(uint64_t, uint64_t) {}
} // namespace Libs::Graphics
-2
View File
@@ -24,8 +24,6 @@ public:
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:
struct Impl;
+2 -14
View File
@@ -6,17 +6,11 @@
#include <algorithm>
#include <cstdint>
#include <map>
#include <vector>
namespace Libs::Graphics {
class RangeSet final {
public:
struct Range {
uint64_t address = 0;
uint64_t size = 0;
};
void Add(uint64_t address, uint64_t size) {
const auto end = End(address, size);
auto it = m_ranges.lower_bound(address);
@@ -62,12 +56,6 @@ public:
}
}
[[nodiscard]] std::vector<Range> Intersections(uint64_t address, uint64_t size) const {
std::vector<Range> result;
ForEachIntersection(address, size, [&result](Range range) { result.push_back(range); });
return result;
}
[[nodiscard]] bool Intersects(uint64_t address, uint64_t size) const {
const auto end = End(address, size);
auto it = m_ranges.lower_bound(address);
@@ -88,7 +76,7 @@ public:
}
template <typename Func>
void ForEachIntersection(uint64_t address, uint64_t size, Func&& func) const {
void ForEachInRange(uint64_t address, uint64_t size, Func&& func) const {
const auto end = End(address, size);
auto it = m_ranges.upper_bound(address);
if (it != m_ranges.begin()) {
@@ -98,7 +86,7 @@ public:
const auto begin = std::max(address, it->first);
const auto last = std::min(end, it->second);
if (begin < last) {
func(Range {begin, last - begin});
func(begin, last);
}
}
}
+19 -39
View File
@@ -111,56 +111,43 @@ public:
bits.UnsetRange(start, end);
}
if constexpr (source == DirtySource::Cpu) {
UpdateCpuProtection<!enable>();
UpdateProtection<!enable, false>();
} else {
UpdateGpuProtection<enable>();
UpdateProtection<enable, true>();
}
}
template <DirtySource source, bool clear, typename Func>
void ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) {
const auto [start, end] = GetPageRange(vaddr, size);
RegionBits mask(GetBits<source>(), start, end);
auto& bits = GetBits<source>();
RegionBits mask(bits, start, end);
if constexpr (clear) {
GetBits<source>().UnsetRange(start, end);
bits.UnsetRange(start, end);
if constexpr (source == DirtySource::Cpu) {
UpdateProtection<true, false>();
} else {
UpdateProtection<false, true>();
}
}
if constexpr (source == DirtySource::Cpu && clear) {
UpdateCpuProtection<true>();
ForEachRange(mask, std::forward<Func>(func));
return;
for (const auto [first, last]: mask) {
func(m_cpu_addr + first * TRACKER_PAGE_SIZE, (last - first) * TRACKER_PAGE_SIZE);
}
if constexpr (source == DirtySource::Gpu && clear) {
UpdateGpuProtection<false>();
}
ForEachRange(mask, std::forward<Func>(func));
}
TrackingSpinLock lock;
private:
template <bool track>
void UpdateCpuProtection() {
auto mask = m_cpu_dirty ^ m_writable;
m_writable = m_cpu_dirty;
template <bool track, bool is_read>
void UpdateProtection() {
const auto protection = is_read ? ~m_gpu_dirty : m_cpu_dirty;
auto& previous = is_read ? m_readable : m_writable;
auto mask = protection ^ previous;
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);
}
previous = protection;
m_page_manager.UpdatePageWatchersForRegion<track, is_read>(m_cpu_addr, mask);
}
template <DirtySource source>
@@ -191,13 +178,6 @@ private:
static_cast<size_t>((offset + size + TRACKER_PAGE_SIZE - 1) / TRACKER_PAGE_SIZE)};
}
template <typename Func>
void ForEachRange(const RegionBits& bits, Func&& func) const {
for (const auto [start, end]: bits) {
func(m_cpu_addr + start * TRACKER_PAGE_SIZE, (end - start) * TRACKER_PAGE_SIZE);
}
}
PageManager& m_page_manager;
uint64_t m_cpu_addr = 0;
RegionBits m_cpu_dirty;
+197 -313
View File
@@ -1,5 +1,6 @@
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/profiler.h"
@@ -13,7 +14,6 @@
#include "kernel/memory.h"
#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstring>
#include <memory>
@@ -43,13 +43,6 @@ void BufferCache::WriteDataBuffer(Buffer& buffer, uint64_t address, const void*
}
}
struct BufferCache::DownloadCopy {
Buffer* buffer = nullptr;
uint64_t source_offset = 0;
uint64_t address = 0;
uint64_t size = 0;
};
void BufferCache::Register(BufferId id) {
ChangeRegister<true>(id);
}
@@ -104,8 +97,7 @@ void BufferCache::TouchBuffer(const Buffer& buffer) {
}
void BufferCache::DeleteBuffer(BufferId id) {
auto* buffer = m_slot_buffers.try_get(id);
if (buffer == nullptr || buffer->is_deleted) {
if (IsBufferInvalid(id)) {
return;
}
Unregister(id);
@@ -116,94 +108,84 @@ void BufferCache::DeleteBuffer(BufferId id) {
}
}
std::pair<uint64_t, uint64_t> BufferCache::DownloadEnvelope(const DownloadCopy& copy) {
if (copy.buffer == nullptr || copy.size == 0 || copy.source_offset > copy.buffer->Size() ||
copy.size > copy.buffer->Size() - copy.source_offset) {
EXIT("BufferCache: invalid download copy\n");
bool BufferCache::DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t size) {
std::vector<vk::BufferCopy> copies;
uint64_t total_size = 0;
const auto buffer_address = buffer.CpuAddress();
m_memory_tracker.ForEachDownloadRange<false>(
vaddr, size, [&](uint64_t address, uint64_t bytes) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
"buffer download");
m_gpu_modified_ranges.ForEachInRange(address, bytes, [&](uint64_t start, uint64_t end) {
copies.emplace_back(start - buffer_address, total_size, end - start);
// Keep packed ranges on separate cache lines, as in shadPS4.
total_size += Common::AlignUp(end - start, 64);
});
m_gpu_modified_ranges.Subtract(address, bytes);
});
if (copies.empty()) {
return false;
}
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.buffer->Size()) {
EXIT("BufferCache: aligned download copy exceeds its owner\n");
}
return {begin, end - begin};
}
void BufferCache::DownloadBufferMemory(std::span<const DownloadCopy> copies) {
std::vector<DownloadCopy> batch;
batch.reserve(copies.size());
uint64_t packed_size = 0;
auto& download = m_download_buffer;
const auto flush = [&] {
const auto [mapped, base_offset] = download.Map(packed_size, DOWNLOAD_ALIGNMENT);
EXIT_IF(mapped == nullptr);
uint64_t cursor = 0;
for (const auto& copy: batch) {
const auto [source_begin, envelope_size] = DownloadEnvelope(copy);
download.CopyFrom(m_scheduler.Current(), *copy.buffer, source_begin, base_offset + cursor,
envelope_size, vk::AccessFlagBits::eMemoryWrite, vk::AccessFlags {},
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlagBits::eHostRead);
cursor += AlignDownload(envelope_size);
}
download.Commit();
const auto completion_tick = m_scheduler.CurrentTick();
m_scheduler.Finish();
m_scheduler.WaitPriorityOperations(completion_tick);
cursor = 0;
for (const auto& copy: batch) {
const auto [source_begin, envelope_size] = DownloadEnvelope(copy);
const auto offset = cursor + copy.source_offset - source_begin;
download.Invalidate(base_offset + offset, copy.size);
Libs::LibKernel::Memory::WriteBacking(copy.address, mapped + offset, copy.size);
cursor += AlignDownload(envelope_size);
}
batch.clear();
packed_size = 0;
};
for (auto copy: copies) {
while (copy.size != 0) {
const auto available = download.Size() - packed_size;
const auto prefix = copy.source_offset & 3u;
const auto bytes = std::min(copy.size, available - prefix);
DownloadCopy part {copy.buffer, copy.source_offset, copy.address, bytes};
const auto [source_begin, envelope_size] = DownloadEnvelope(part);
(void)source_begin;
packed_size += AlignDownload(envelope_size);
batch.push_back(part);
copy.source_offset += bytes;
copy.address += bytes;
copy.size -= bytes;
if (packed_size == download.Size()) {
flush();
}
}
const auto [mapped, offset] = m_download_buffer.Map(total_size, 64);
if (mapped == nullptr) {
EXIT("BufferCache: download exceeds 32 MiB staging buffer capacity\n");
}
if (!batch.empty()) {
flush();
}
for (const auto& copy: copies) {
m_gpu_modified_ranges.Subtract(copy.address, copy.size);
m_download_buffer.Commit();
for (auto& copy: copies) {
copy.dstOffset += offset;
}
auto& command = m_scheduler.Current();
command.EndRendering();
const auto native = command.Handle();
vk::BufferMemoryBarrier before {};
before.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
before.dstAccessMask = vk::AccessFlagBits::eTransferRead;
before.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
before.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
before.buffer = buffer.Handle();
before.offset = 0;
before.size = buffer.Size();
native.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eTransfer, {}, 0, nullptr, 1, &before, 0,
nullptr);
native.copyBuffer(buffer.Handle(), m_download_buffer.Handle(),
static_cast<uint32_t>(copies.size()), copies.data());
auto after = before;
after.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
after.dstAccessMask = vk::AccessFlagBits::eHostRead;
after.buffer = m_download_buffer.Handle();
after.offset = offset;
after.size = total_size;
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eAllCommands |
vk::PipelineStageFlagBits::eHost,
{}, 0, nullptr, 1, &after, 0, nullptr);
m_scheduler.DeferPriorityOperation([this, mapped, offset, total_size, buffer_address,
copies = std::move(copies)] {
m_download_buffer.Invalidate(offset, total_size);
for (const auto& copy: copies) {
Libs::LibKernel::Memory::WriteBacking(buffer_address + copy.srcOffset,
mapped + (copy.dstOffset - offset), copy.size);
}
});
return true;
}
BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
PageManager& page_manager, TextureCache& texture_cache)
: m_graphics(graphics), m_scheduler(scheduler),
m_fault_manager(graphics, scheduler, *this, CACHING_PAGEBITS, CACHING_NUMPAGES),
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
m_bda_pagetable_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
BDA_PAGETABLE_SIZE),
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) {
: m_graphics(graphics), m_scheduler(scheduler), m_fault_manager(graphics, scheduler, *this),
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
m_bda_pagetable_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
BDA_PAGETABLE_SIZE),
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());
SetVulkanObjectNameF(m_graphics.device, m_bda_pagetable_buffer.Handle(),
@@ -254,59 +236,29 @@ void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size, bool is_write) {
"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;
const auto* owner = m_page_table.Find(copy_address >> PageTable::kPageBits);
if (owner == nullptr || !*owner) {
EXIT("BufferCache: invalidation readback has no buffer owner\n");
}
auto& buffer = m_slot_buffers[*owner];
if (!buffer.IsInBounds(copy_address, 1)) {
EXIT("BufferCache: invalidation readback is outside its buffer owner\n");
}
const auto copy_size = std::min(
range.size - copied, buffer.CpuAddress() + buffer.Size() - copy_address);
copies.push_back(
{&buffer, buffer.Offset(copy_address), copy_address, copy_size});
copied += copy_size;
}
}
});
if (copies.empty()) {
if (!is_write) {
m_scheduler.Context().GetGpu().SendCommandSync([this, vaddr, size, is_write] {
if (is_write && !IsRegionRegistered(vaddr, size)) {
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;
}
DownloadBufferMemory(copies);
// 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);
}
auto& buffer = m_slot_buffers[FindBuffer(vaddr, size)];
// Widen nearby CPU reads so they share one GPU drain.
constexpr uint64_t WindowSize = 512 * 1024;
const auto buffer_begin = buffer.CpuAddress();
const auto buffer_end = buffer_begin + buffer.Size();
const auto window_begin = std::max(Common::AlignDown(vaddr, WindowSize), buffer_begin);
const auto window_end = std::min(std::max(window_begin + WindowSize, vaddr + size), buffer_end);
if (DownloadBufferMemory(buffer, window_begin, window_end - window_begin)) {
const auto tick = m_scheduler.CurrentTick();
m_scheduler.Wait(tick);
m_scheduler.WaitPriorityOperations(tick);
m_memory_tracker.UnmarkRegionAsGpuModified(window_begin, window_end - window_begin);
}
if (is_write) {
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
}
});
}
BufferId BufferCache::FindBuffer(uint64_t vaddr, uint64_t size) {
@@ -326,38 +278,83 @@ BufferId BufferCache::FindBuffer(uint64_t vaddr, uint64_t size) {
return CreateBuffer(vaddr, size);
}
BufferId BufferCache::CreateBuffer(uint64_t vaddr, uint64_t size) {
auto& command = m_scheduler.Current();
EXIT_IF(command.IsInvalid());
auto begin = vaddr & ~(CACHING_PAGESIZE - 1);
auto end = (vaddr + size + CACHING_PAGESIZE - 1) & ~(CACHING_PAGESIZE - 1);
auto first = m_buffers.lower_bound(begin);
if (first != m_buffers.begin()) {
const auto previous = std::prev(first);
if (const auto& buffer = m_slot_buffers[previous->second];
buffer.CpuAddress() + buffer.Size() > begin) {
first = previous;
BufferCache::OverlapResult BufferCache::ResolveOverlaps(uint64_t vaddr, uint64_t size) {
static constexpr int StreamLeapThreshold = 16;
static constexpr uint64_t StreamLeapSize = CACHING_PAGESIZE * 128;
auto begin = vaddr;
auto end = vaddr + size;
const auto find_first = [&](uint64_t address) {
auto first = m_buffers.lower_bound(address);
if (first != m_buffers.begin()) {
const auto previous = std::prev(first);
const auto& buffer = m_slot_buffers[previous->second];
if (buffer.CpuAddress() + buffer.Size() > address) {
first = previous;
}
}
return first;
};
auto first = find_first(begin);
auto last = first;
int stream_score = 0;
bool has_stream_leap = false;
for (; last != m_buffers.end() && last->first < end; ++last) {
const auto& buffer = m_slot_buffers[last->second];
const auto buffer_begin = buffer.CpuAddress();
const auto buffer_end = buffer_begin + buffer.Size();
const bool expands_left = buffer_begin < begin;
const bool expands_right = buffer_end > end;
begin = std::min(begin, buffer_begin);
end = std::max(end, buffer_end);
if (!has_stream_leap && (stream_score += buffer.StreamScore()) > StreamLeapThreshold) {
has_stream_leap = true;
// Reserve space in the incoming stream's direction of growth.
// The old buffer extending left of the request predicts growth to the right, and vice versa.
if (expands_left) {
end += std::min(StreamLeapSize, PageTable::kAddressSpaceSize - end);
}
if (expands_right) {
const auto minimum = CACHING_PAGESIZE * 2;
if (begin > minimum) {
begin -= std::min(StreamLeapSize, begin - minimum);
}
first = find_first(begin);
begin = std::min(begin, first->first);
}
}
}
auto last = first;
for (; last != m_buffers.end() && last->first < end; ++last) {
const auto& buffer = m_slot_buffers[last->second];
begin = std::min(begin, buffer.CpuAddress());
end = std::max(end, buffer.CpuAddress() + buffer.Size());
return {first, last, begin, end, has_stream_leap};
}
void BufferCache::JoinOverlap(BufferId new_id, BufferId overlap_id, bool accumulate_stream_score) {
auto& new_buffer = m_slot_buffers[new_id];
auto& overlap = m_slot_buffers[overlap_id];
if (accumulate_stream_score) {
new_buffer.IncreaseStreamScore(overlap.StreamScore() + 1);
}
new_buffer.CopyFrom(m_scheduler.Current(), overlap, 0,
overlap.CpuAddress() - new_buffer.CpuAddress(), overlap.Size());
DeleteBuffer(overlap_id);
}
BufferId BufferCache::CreateBuffer(uint64_t vaddr, uint64_t size) {
EXIT_IF(m_scheduler.Current().IsInvalid());
const auto end = Common::AlignUp(vaddr + size, CACHING_PAGESIZE);
vaddr = Common::AlignDown(vaddr, CACHING_PAGESIZE);
size = end - vaddr;
const auto overlap = ResolveOverlaps(vaddr, size);
const auto id = m_slot_buffers.insert(
m_graphics, m_scheduler, MemoryUsage::DeviceLocal, begin,
AllFlags | vk::BufferUsageFlagBits::eShaderDeviceAddress, end - begin);
auto& buffer = m_slot_buffers[id];
m_graphics, m_scheduler, MemoryUsage::DeviceLocal, overlap.begin,
AllFlags | vk::BufferUsageFlagBits::eShaderDeviceAddress, overlap.end - overlap.begin);
const auto& buffer = m_slot_buffers[id];
SetVulkanObjectNameF(m_graphics.device, buffer.Handle(),
"Kyty.GameBuffer[guest=0x{:016x} size=0x{:x}]", begin, end - begin);
for (auto overlap = first; overlap != last;) {
const auto current = overlap++;
const auto old_id = current->second;
const auto& old = m_slot_buffers[old_id];
buffer.CopyFrom(command, old, 0, old.CpuAddress() - begin, old.Size());
DeleteBuffer(old_id);
"Kyty.GameBuffer[guest=0x{:016x} size=0x{:x}]", overlap.begin,
overlap.end - overlap.begin);
for (auto it = overlap.first; it != overlap.last;) {
const auto old_id = (it++)->second;
JoinOverlap(id, old_id, !overlap.has_stream_leap);
}
Register(id);
return id;
@@ -380,7 +377,6 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, uint64_t vaddr, uint64_t siz
command.EndRendering();
const auto native = command.Handle();
vk::BufferMemoryBarrier before {};
before.sType = vk::StructureType::eBufferMemoryBarrier;
before.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite |
vk::AccessFlagBits::eTransferRead |
vk::AccessFlagBits::eTransferWrite;
@@ -458,54 +454,34 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t
}
}
auto* buffer = m_slot_buffers.try_get(id);
if (buffer == nullptr || buffer->is_deleted || !buffer->IsInBounds(vaddr, size)) {
id = FindBuffer(vaddr, size);
buffer = &m_slot_buffers[id];
if (IsBufferInvalid(id) || !m_slot_buffers[id].IsInBounds(vaddr, size)) {
id = FindBuffer(vaddr, size);
}
TouchBuffer(*buffer);
(void)SynchronizeBuffer(*buffer, vaddr, size, is_written, is_texel_buffer);
auto& buffer = m_slot_buffers[id];
TouchBuffer(buffer);
(void)SynchronizeBuffer(buffer, vaddr, size, is_written, is_texel_buffer);
if (is_written) {
m_gpu_modified_ranges.Add(vaddr, size);
}
return {buffer, buffer->Offset(vaddr)};
return {&buffer, buffer.Offset(vaddr)};
}
std::pair<Buffer*, uint64_t> BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t size) {
if (!GuestRange {vaddr, size}.Valid()) {
EXIT("BufferCache: invalid image source\n");
}
auto find_owner = [&]() -> Buffer* {
const auto* owner = m_page_table.Find(vaddr >> PageTable::kPageBits);
if (owner == nullptr || !*owner) {
return nullptr;
}
const auto* owner = m_page_table.Find(vaddr >> PageTable::kPageBits);
if (owner != nullptr && *owner) {
auto& buffer = m_slot_buffers[*owner];
return buffer.IsInBounds(vaddr, size) ? &buffer : nullptr;
};
{
const bool cpu_modified = m_memory_tracker.IsRegionCpuModified(vaddr, size);
const bool gpu_modified = m_memory_tracker.IsRegionGpuModified(vaddr, size);
const bool has_dirty_buffer_source = m_gpu_modified_ranges.Intersects(vaddr, size);
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");
}
owner = &m_slot_buffers[FindBuffer(vaddr, size)];
}
if (owner != nullptr && !cpu_modified && (!gpu_modified || has_dirty_buffer_source)) {
TouchBuffer(*owner);
return {owner, owner->Offset(vaddr)};
}
if (has_dirty_buffer_source && owner == nullptr) {
EXIT("BufferCache: GPU-dirty image source could not resolve its native owner\n");
if (buffer.IsInBounds(vaddr, size)) {
TouchBuffer(buffer);
(void)SynchronizeBuffer(buffer, vaddr, size, false, false);
return {&buffer, buffer.Offset(vaddr)};
}
}
if (IsRegionGpuModified(vaddr, size)) {
return ObtainBuffer(vaddr, size, false, false);
}
auto [staging, stage_offset] = m_staging_buffer.Map(size, 16);
if (staging == nullptr || (!Libs::LibKernel::Memory::TryReadBacking(vaddr, staging, size) &&
@@ -513,52 +489,7 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBufferForImage(uint64_t vaddr, u
EXIT("BufferCache: failed to read mapped guest image backing\n");
}
m_staging_buffer.Commit();
const bool has_dirty_buffer_source = m_gpu_modified_ranges.Intersects(vaddr, size);
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};
}
TouchBuffer(*owner);
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) {
owner->CopyFrom(m_scheduler.Current(), m_staging_buffer,
stage_offset + address - vaddr, owner->Offset(address), upload_size,
vk::AccessFlagBits::eHostWrite);
}
});
return {owner, owner->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 (const auto& [address, id]: m_buffers) {
auto& buffer = m_slot_buffers[id];
const auto buffer_end = address + buffer.Size();
const auto begin = std::max(vaddr, address);
const auto range_end = std::min(end, buffer_end);
if (begin >= range_end) {
continue;
}
WriteDataBuffer(buffer, begin, data.data() + begin - vaddr, range_end - begin);
TouchBuffer(buffer);
}
return {&m_staging_buffer, stage_offset};
}
void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds) {
@@ -576,29 +507,15 @@ void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool
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;
}
if (!IsRegionGpuModified(vaddr, size)) {
// Access the guest mapping so write faults invalidate cached buffers and images.
auto* destination = reinterpret_cast<uint32_t*>(vaddr);
std::fill(destination, destination + size / sizeof(uint32_t), value);
return;
}
m_texture_cache.InvalidateMemoryFromGPU(vaddr, size);
const auto id = FindBuffer(vaddr, size);
auto [dst, dst_offset] = ObtainBuffer(vaddr, size, true, true, id);
EXIT_IF(dst == nullptr);
auto [dst, dst_offset] = ObtainBuffer(vaddr, size, true, true);
dst->Fill(dst_offset, size, value);
}
@@ -615,29 +532,11 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
" 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;
}
if (src_memory && dst_memory && !IsRegionGpuModified(dst_vaddr, size) &&
!IsRegionGpuModified(src_vaddr, size) && !m_texture_cache.FindImageFromRange(src_vaddr, size)) {
std::memcpy(reinterpret_cast<void*>(dst_vaddr), reinterpret_cast<const void*>(src_vaddr),
size);
return;
}
auto& command = m_scheduler.Current();
@@ -650,10 +549,6 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
: std::pair {&m_gds_buffer, src_vaddr};
auto [dst, dst_offset] = dst_memory ? ObtainBuffer(dst_vaddr, size, true, true, dst_id)
: std::pair {&m_gds_buffer, dst_vaddr};
EXIT_IF(src == nullptr || dst == nullptr);
if (src == dst && src_offset < dst_offset + size && dst_offset < src_offset + size) {
EXIT("BufferCache: resolved Vulkan copy ranges overlap\n");
}
dst->CopyFrom(command, *src, src_offset, dst_offset, size);
}
@@ -697,8 +592,7 @@ void BufferCache::RunGarbageCollector() {
const size_t limit = aggressive ? 64 : 32;
std::vector<BufferId> dirty_buffers;
std::vector<DownloadCopy> copies;
size_t retire_count = 0;
size_t retire_count = 0;
m_lru_cache.ForEachItemBelow(tick - age, [&](BufferId id) {
auto& buffer = m_slot_buffers[id];
EXIT_IF(buffer.is_deleted);
@@ -709,19 +603,7 @@ void BufferCache::RunGarbageCollector() {
return false;
}
if (dirty) {
m_memory_tracker.ForEachDownloadRange<false>(
buffer.CpuAddress(), buffer.Size(),
[&](uint64_t dirty_address, uint64_t dirty_size) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, dirty_address,
dirty_size, "garbage collection");
},
[&](uint64_t dirty_address, uint64_t dirty_size) noexcept {
m_gpu_modified_ranges.ForEachIntersection(
dirty_address, dirty_size, [&](RangeSet::Range range) {
copies.push_back({&buffer, range.address - buffer.CpuAddress(),
range.address, range.size});
});
});
EXIT_IF(!DownloadBufferMemory(buffer, buffer.CpuAddress(), buffer.Size()));
dirty_buffers.push_back(id);
} else {
m_memory_tracker.UntrackMemory(buffer.CpuAddress(), buffer.Size());
@@ -733,8 +615,10 @@ void BufferCache::RunGarbageCollector() {
return;
}
EXIT_IF(copies.empty());
DownloadBufferMemory(copies);
// Publish all queued downloads before releasing their tracked pages and owners.
const auto completion_tick = m_scheduler.CurrentTick();
m_scheduler.Wait(completion_tick);
m_scheduler.WaitPriorityOperations(completion_tick);
for (const auto id: dirty_buffers) {
auto& buffer = m_slot_buffers[id];
m_memory_tracker.UnmarkRegionAsGpuModified(buffer.CpuAddress(), buffer.Size());
+19 -10
View File
@@ -74,16 +74,26 @@ public:
private:
friend struct BufferCacheTestAccess;
struct DownloadCopy;
bool IsBufferInvalid(BufferId id) const {
const auto* buffer = m_slot_buffers.try_get(id);
return buffer == nullptr || buffer->is_deleted;
}
using BufferMap = std::map<uint64_t, BufferId>;
struct OverlapResult {
BufferMap::iterator first;
BufferMap::iterator last;
uint64_t begin;
uint64_t end;
bool has_stream_leap;
};
using PageTable = MultiLevelPageTable<BufferId, CACHING_PAGEBITS, 40, 16>;
static_assert(CACHING_PAGESIZE == (uint64_t {1} << PageTable::kPageBits));
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
}
[[nodiscard]] static std::pair<uint64_t, uint64_t> DownloadEnvelope(const DownloadCopy& copy);
void WriteDataBuffer(Buffer& buffer, uint64_t address, const void* source, uint64_t size);
void TouchBuffer(const Buffer& buffer);
[[nodiscard]] OverlapResult ResolveOverlaps(uint64_t vaddr, uint64_t size);
void JoinOverlap(BufferId new_id, BufferId overlap_id, bool accumulate_stream_score);
[[nodiscard]] BufferId CreateBuffer(uint64_t vaddr, uint64_t size);
void Register(BufferId id);
void Unregister(BufferId id);
@@ -95,9 +105,8 @@ private:
[[nodiscard]] vk::Buffer UploadCopies(Buffer& buffer, std::span<vk::BufferCopy> copies,
uint64_t total_size);
[[nodiscard]] bool SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size);
void DownloadBufferMemory(std::span<const DownloadCopy> copies);
void WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data);
void ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write);
// Queues backing publication; callers wait before clearing dirty pages or reusing their data.
[[nodiscard]] bool DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t size);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
@@ -106,7 +115,7 @@ private:
Buffer m_bda_pagetable_buffer;
Common::SlotVector<Buffer> m_slot_buffers;
Common::LeastRecentlyUsedCache<BufferId, uint64_t> m_lru_cache;
std::map<uint64_t, BufferId> m_buffers;
BufferMap m_buffers;
PageTable m_page_table;
RangeSet m_gpu_modified_ranges;
MemoryTracker m_memory_tracker;
+8 -19
View File
@@ -23,13 +23,10 @@ constexpr size_t PageFaultAreaSize = MaxPageFaults * sizeof(uint64_t);
} // namespace
FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler,
BufferCache& buffer_cache, uint32_t caching_pagebits,
uint64_t caching_num_pages)
BufferCache& buffer_cache)
: m_graphics(graphics), m_scheduler(scheduler), m_buffer_cache(buffer_cache),
m_caching_pagesize(uint64_t {1} << caching_pagebits),
m_caching_num_pages(caching_num_pages), m_fault_buffer_size(caching_num_pages / 8),
m_fault_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
m_fault_buffer_size),
BufferCache::CACHING_NUMPAGES / 8),
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 0, AllFlags,
MaxPendingFaults * PageFaultAreaSize) {
SetVulkanObjectNameF(m_graphics.device, m_fault_buffer.Handle(), "Fault Buffer");
@@ -39,7 +36,6 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
{1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr},
};
vk::DescriptorSetLayoutCreateInfo layout_info {};
layout_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
layout_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
layout_info.bindingCount = std::size(bindings);
layout_info.pBindings = bindings;
@@ -49,7 +45,6 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
"create fault-buffer descriptor layout");
vk::ShaderModuleCreateInfo module_info {};
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
module_info.codeSize = std::size(FAULT_BUFFER_PROCESS_SPV) * sizeof(uint32_t);
module_info.pCode = FAULT_BUFFER_PROCESS_SPV;
vk::ShaderModule module = nullptr;
@@ -57,7 +52,6 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
"create fault-buffer shader module");
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
pipeline_layout_info.setLayoutCount = 1;
pipeline_layout_info.pSetLayouts = &m_fault_process_desc_layout;
RequireVulkanSuccess(
@@ -66,12 +60,10 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
"create fault-buffer pipeline layout");
vk::PipelineShaderStageCreateInfo stage {};
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
stage.pName = "main";
vk::ComputePipelineCreateInfo pipeline_info {};
pipeline_info.sType = vk::StructureType::eComputePipelineCreateInfo;
pipeline_info.stage = stage;
pipeline_info.layout = m_fault_process_pipeline_layout;
const auto result = m_graphics.device.createComputePipelines(
@@ -99,14 +91,13 @@ void FaultManager::ProcessFaultBuffer() {
m_download_buffer.Flush(offset, PageFaultAreaSize);
vk::BufferMemoryBarrier2 pre_barrier {};
pre_barrier.sType = vk::StructureType::eBufferMemoryBarrier2;
pre_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
pre_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
pre_barrier.dstStageMask = vk::PipelineStageFlagBits2::eComputeShader;
pre_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead;
pre_barrier.buffer = m_fault_buffer.Handle();
pre_barrier.offset = 0;
pre_barrier.size = m_fault_buffer_size;
pre_barrier.size = m_fault_buffer.Size();
auto post_barrier = pre_barrier;
post_barrier.srcStageMask = vk::PipelineStageFlagBits2::eComputeShader;
post_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
@@ -114,12 +105,11 @@ void FaultManager::ProcessFaultBuffer() {
post_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderWrite;
const vk::DescriptorBufferInfo infos[] {
{m_fault_buffer.Handle(), 0, m_fault_buffer_size},
{m_fault_buffer.Handle(), 0, m_fault_buffer.Size()},
{m_download_buffer.Handle(), offset, PageFaultAreaSize},
};
std::array<vk::WriteDescriptorSet, 2> writes {};
for (uint32_t index = 0; index < writes.size(); ++index) {
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
writes[index].dstBinding = index;
writes[index].descriptorCount = 1;
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
@@ -129,7 +119,6 @@ void FaultManager::ProcessFaultBuffer() {
m_scheduler.EndRendering();
auto command = m_scheduler.Current().Handle();
vk::DependencyInfo dependency {};
dependency.sType = vk::StructureType::eDependencyInfo;
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &pre_barrier;
@@ -137,7 +126,7 @@ void FaultManager::ProcessFaultBuffer() {
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_fault_process_pipeline);
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute,
m_fault_process_pipeline_layout, 0, writes);
const auto num_threads = m_caching_num_pages / 32;
const auto num_threads = BufferCache::CACHING_NUMPAGES / 32;
const auto num_workgroups = (num_threads + 63) / 64;
command.dispatch(static_cast<uint32_t>(num_workgroups), 1, 1);
dependency.pBufferMemoryBarriers = &post_barrier;
@@ -146,14 +135,14 @@ void FaultManager::ProcessFaultBuffer() {
const auto area = m_current_area;
m_scheduler.DeferOperation([this, mapped, offset, area] {
m_download_buffer.Invalidate(offset, PageFaultAreaSize);
m_fault_ranges.Clear();
RangeSet fault_ranges;
const auto* faults = std::bit_cast<const uint64_t*>(mapped);
const auto count = static_cast<uint32_t>(faults[0]);
for (uint32_t index = 1; index <= count; ++index) {
m_fault_ranges.Add(faults[index], m_caching_pagesize);
fault_ranges.Add(faults[index], BufferCache::CACHING_PAGESIZE);
LOGF("Accessed non-GPU cached memory at 0x%016" PRIx64 "\n", faults[index]);
}
m_fault_ranges.ForEach([this](uint64_t start, uint64_t end) {
fault_ranges.ForEach([this](uint64_t start, uint64_t end) {
EXIT_IF(end - start > std::numeric_limits<uint32_t>::max());
(void)m_buffer_cache.FindBuffer(start, end - start);
});
+1 -7
View File
@@ -2,7 +2,6 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
#include "common/abi.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include <array>
@@ -16,8 +15,7 @@ class FaultManager {
static constexpr size_t MaxPendingFaults = 8;
public:
FaultManager(GraphicContext& graphics, CommandScheduler& scheduler, BufferCache& buffer_cache,
uint32_t caching_pagebits, uint64_t caching_num_pages);
FaultManager(GraphicContext& graphics, CommandScheduler& scheduler, BufferCache& buffer_cache);
~FaultManager();
KYTY_CLASS_NO_COPY(FaultManager);
@@ -28,10 +26,6 @@ private:
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
BufferCache& m_buffer_cache;
RangeSet m_fault_ranges;
uint64_t m_caching_pagesize;
uint64_t m_caching_num_pages;
uint64_t m_fault_buffer_size;
Buffer m_fault_buffer;
Buffer m_download_buffer;
std::array<uint64_t, MaxPendingFaults> m_fault_areas {};
@@ -1,96 +0,0 @@
#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 (!GuestRange {vaddr, size}.Valid()) {
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.Finish();
m_scheduler.WaitPriorityOperations(tick);
}
m_buffer_cache.InvalidateMemory(vaddr, size);
m_texture_cache.UnmapMemory(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size);
std::lock_guard lock(m_mapped_ranges_mutex);
m_mapped_ranges.Subtract(vaddr, size);
};
if (m_gpu == nullptr) {
unmap();
return;
}
m_gpu->SendCommandSync(unmap);
}
void GpuResourceManager::PrepareBda() {
std::shared_lock lock(m_mapped_ranges_mutex);
m_mapped_ranges.ForEach([this](uint64_t start, uint64_t end) {
m_buffer_cache.SynchronizeBuffersInRange(start, end - start);
});
m_fault_process_pending = true;
}
void GpuResourceManager::RunGarbageCollector() {
if (m_fault_process_pending) {
m_fault_process_pending = false;
m_buffer_cache.ProcessFaultBuffer();
}
m_texture_cache.ProcessDownloadImages();
m_texture_cache.RunGarbageCollector();
m_buffer_cache.RunGarbageCollector();
}
} // namespace Libs::Graphics
@@ -1,49 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/host_gpu/pageManager.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 GuestGpu;
class GpuResourceManager {
public:
GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler);
~GpuResourceManager();
KYTY_CLASS_NO_COPY(GpuResourceManager);
[[nodiscard]] BufferCache& GetBufferCache() { return m_buffer_cache; }
[[nodiscard]] TextureCache& GetTextureCache() { return m_texture_cache; }
void SetGpu(GuestGpu* gpu) noexcept { m_gpu = gpu; }
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
[[nodiscard]] bool InvalidateMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
void MapMemory(uint64_t vaddr, uint64_t size);
void UnmapMemory(uint64_t vaddr, uint64_t size);
void PrepareBda();
void RunGarbageCollector();
private:
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;
bool m_fault_process_pending = false;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GPURESOURCEMANAGER_H_
@@ -256,17 +256,6 @@ private:
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_
-3
View File
@@ -116,9 +116,6 @@ vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
default: EXIT("unknown border color: %d", static_cast<int>(r.BorderColorType()));
}
sampler_info.sType = vk::StructureType::eSamplerCreateInfo;
sampler_info.pNext = nullptr;
sampler_info.flags = {};
sampler_info.magFilter = to_vk_filter(mag_filter);
sampler_info.minFilter = to_vk_filter(min_filter);
sampler_info.mipmapMode =
+29 -67
View File
@@ -1,13 +1,12 @@
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "common/profiler.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/vma.h"
#include <cstring>
#include <limits>
#include <numeric>
#include <vk_mem_alloc.h>
@@ -46,16 +45,11 @@ constexpr size_t WATCHES_RESERVE_CHUNK = 0x1000;
result = value;
return true;
}
const auto remainder = value % alignment;
if (remainder == 0) {
result = value;
return true;
}
const auto increment = alignment - remainder;
if (value > std::numeric_limits<uint64_t>::max() - increment) {
const auto aligned = Common::AlignUp(value, alignment);
if (aligned < value) {
return false;
}
result = value + increment;
result = aligned;
return true;
}
@@ -64,14 +58,13 @@ constexpr size_t WATCHES_RESERVE_CHUNK = 0x1000;
Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t cpu_address, vk::BufferUsageFlags flags, uint64_t size)
: m_graphics(&graphics), m_scheduler(&scheduler), m_usage(usage), m_cpu_address(cpu_address),
m_size(size), m_buffer(std::make_unique<VulkanBuffer>()) {
m_size(size) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(graphics.allocator == nullptr || size == 0);
vk::BufferCreateInfo buffer_info {};
buffer_info.size = size;
buffer_info.usage = flags;
buffer_info.sharingMode = vk::SharingMode::eExclusive;
const bool with_bda = bool(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress);
const VmaAllocationCreateFlags bda_flag =
@@ -88,93 +81,70 @@ Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsag
VkBuffer native_buffer = VK_NULL_HANDLE;
const auto result = static_cast<vk::Result>(vmaCreateBuffer(
graphics.allocator, static_cast<const VkBufferCreateInfo*>(buffer_info), &allocation_info,
&native_buffer, &m_buffer->memory.allocation, &allocation_result));
&native_buffer, &m_allocation, &allocation_result));
if (result != vk::Result::eSuccess) {
graphics.LogMemoryBudget();
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
m_buffer->buffer = native_buffer;
m_buffer->usage = flags;
m_buffer->buffer_size = size;
m_buffer->memory.allocation_info = allocation_result;
m_buffer->memory.memory = allocation_result.deviceMemory;
m_buffer->memory.offset = allocation_result.offset;
m_buffer->memory.type = allocation_result.memoryType;
m_buffer->memory.unique_id = VulkanNextMemoryUniqueId();
graphics.device.getBufferMemoryRequirements(m_buffer->buffer, &m_buffer->memory.requirements);
if (static_cast<bool>(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress)) {
m_buffer = native_buffer;
if (with_bda) {
vk::BufferDeviceAddressInfo address_info {};
address_info.sType = vk::StructureType::eBufferDeviceAddressInfo;
address_info.buffer = m_buffer->buffer;
address_info.buffer = m_buffer;
m_device_address = graphics.device.getBufferAddress(address_info);
EXIT_IF(m_device_address == 0);
}
VkMemoryPropertyFlags properties = 0;
vmaGetAllocationMemoryProperties(graphics.allocator, m_buffer->memory.allocation, &properties);
m_buffer->memory.property = vk::MemoryPropertyFlags(properties);
m_is_coherent = (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
vmaGetAllocationMemoryProperties(graphics.allocator, m_allocation, &properties);
m_coherent = (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
if (allocation_result.pMappedData != nullptr) {
m_mapped = {static_cast<uint8_t*>(allocation_result.pMappedData),
static_cast<size_t>(size)};
}
VulkanTrackAllocation(m_buffer->memory);
}
Buffer::~Buffer() {
if (m_buffer->buffer != nullptr) {
VulkanUntrackAllocation(m_buffer->memory);
vmaDestroyBuffer(m_graphics->allocator, m_buffer->buffer, m_buffer->memory.allocation);
if (m_buffer != nullptr) {
vmaDestroyBuffer(m_graphics->allocator, m_buffer, m_allocation);
}
}
vk::Buffer Buffer::Handle() const noexcept {
return m_buffer->buffer;
}
vk::DeviceAddress Buffer::BufferDeviceAddress() const noexcept {
EXIT_IF(m_device_address == 0);
return m_device_address;
}
bool Buffer::IsInBounds(uint64_t address, uint64_t size) const noexcept {
return address >= m_cpu_address && size <= m_size && address - m_cpu_address <= m_size - size;
}
void Buffer::Write(uint64_t offset, const void* source, uint64_t size) {
EXIT_IF(source == nullptr || m_mapped.empty() || offset > m_size || size > m_size - offset);
std::memcpy(m_mapped.data() + offset, source, static_cast<size_t>(size));
Flush(offset, size);
return address >= m_cpu_address && size <= Size() && address - m_cpu_address <= Size() - 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) {
EXIT_IF(m_mapped.empty() || offset > Size() || size > Size() - offset);
if (!IsCoherent() && size != 0) {
const auto result =
vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
vmaFlushAllocation(m_graphics->allocator, m_allocation, offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
}
void Buffer::Invalidate(uint64_t offset, uint64_t size) {
EXIT_IF(m_usage != MemoryUsage::Download || offset > m_size || size > m_size - offset);
if (!m_is_coherent && size != 0) {
EXIT_IF(m_usage != MemoryUsage::Download || offset > Size() || size > Size() - offset);
if (!IsCoherent() && size != 0) {
const auto result =
vmaInvalidateAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
vmaInvalidateAllocation(m_graphics->allocator, m_allocation, offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
}
vk::BufferMemoryBarrier Buffer::Barrier(uint64_t offset, uint64_t size, vk::AccessFlags source,
vk::AccessFlags destination) const {
if (Handle() == nullptr || size == 0 || offset > m_size || size > m_size - offset) {
if (Handle() == nullptr || size == 0 || offset > Size() || size > Size() - offset) {
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);
static_cast<const void*>(Handle()), offset, size, Size());
}
vk::BufferMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
barrier.srcAccessMask = source;
barrier.dstAccessMask = destination;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
@@ -189,8 +159,8 @@ void Buffer::CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t sou
uint64_t destination_offset, uint64_t size, vk::AccessFlags source_before,
vk::AccessFlags destination_before, vk::AccessFlags source_after,
vk::AccessFlags destination_after) {
if (size == 0 || source_offset > source.m_size || size > source.m_size - source_offset ||
destination_offset > m_size || size > m_size - destination_offset) {
if (size == 0 || source_offset > source.Size() || size > source.Size() - source_offset ||
destination_offset > Size() || size > Size() - destination_offset) {
EXIT("Buffer: invalid copy range\n");
}
if (source.Handle() == Handle() && source_offset < destination_offset + size &&
@@ -247,10 +217,8 @@ void Buffer::Fill(uint64_t offset, uint64_t size, uint32_t value) {
StreamBuffer::StreamBuffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t size)
: Buffer(graphics, scheduler, usage, 0, AllFlags, size) {
ReserveWatches(m_current_watches, WATCHES_INITIAL_RESERVE);
ReserveWatches(m_previous_watches, WATCHES_INITIAL_RESERVE);
}
: Buffer(graphics, scheduler, usage, 0, AllFlags, size),
m_current_watches(WATCHES_INITIAL_RESERVE), m_previous_watches(WATCHES_INITIAL_RESERVE) {}
bool StreamBuffer::NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
uint64_t& alignment) {
@@ -315,10 +283,8 @@ std::pair<uint8_t*, uint64_t> StreamBuffer::Map(uint64_t size, uint64_t alignmen
}
void StreamBuffer::Commit() {
if (!IsCoherent() && Usage() != MemoryUsage::Download && m_mapped_size != 0) {
const auto result = vmaFlushAllocation(
Graphics().allocator, NativeBuffer().memory.allocation, m_offset, m_mapped_size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
if (Usage() != MemoryUsage::Download && m_mapped_size != 0) {
Flush(m_offset, m_mapped_size);
}
m_offset += m_mapped_size;
@@ -328,7 +294,7 @@ void StreamBuffer::Commit() {
return;
}
if (m_current_watch_cursor + 1 >= m_current_watches.size()) {
ReserveWatches(m_current_watches, WATCHES_RESERVE_CHUNK);
m_current_watches.resize(m_current_watches.size() + WATCHES_RESERVE_CHUNK);
}
auto& watch = m_current_watches[m_current_watch_cursor++];
watch.upper_bound = m_offset;
@@ -344,10 +310,6 @@ uint64_t StreamBuffer::Copy(const void* source, uint64_t size, uint64_t alignmen
return offset;
}
void StreamBuffer::ReserveWatches(std::vector<Watch>& watches, size_t grow_size) {
watches.resize(watches.size() + grow_size);
}
bool StreamBuffer::WaitPendingOperations(const std::vector<Watch>& watches,
std::optional<size_t> invalidation_mark,
uint64_t requested_upper_bound, bool allow_wait,
+13 -12
View File
@@ -6,19 +6,19 @@
#include "graphics/host_gpu/vulkanCommon.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <span>
#include <utility>
#include <vector>
VK_DEFINE_HANDLE(VmaAllocation)
namespace Libs::Graphics {
class CommandBuffer;
class CommandScheduler;
struct StreamBufferTestAccess;
struct GraphicContext;
struct VulkanBuffer;
enum class MemoryUsage : uint8_t {
DeviceLocal,
@@ -42,10 +42,10 @@ public:
~Buffer();
KYTY_CLASS_NO_COPY(Buffer);
[[nodiscard]] vk::Buffer Handle() const noexcept;
[[nodiscard]] vk::Buffer Handle() const noexcept { return m_buffer; }
[[nodiscard]] uint64_t Size() const noexcept { return m_size; }
[[nodiscard]] std::span<uint8_t> Mapped() const noexcept { return m_mapped; }
[[nodiscard]] bool IsCoherent() const noexcept { return m_is_coherent; }
[[nodiscard]] bool IsCoherent() const noexcept { return m_coherent; }
[[nodiscard]] MemoryUsage Usage() const noexcept { return m_usage; }
[[nodiscard]] uint64_t CpuAddress() const noexcept { return m_cpu_address; }
[[nodiscard]] vk::DeviceAddress BufferDeviceAddress() const noexcept;
@@ -53,7 +53,8 @@ public:
return address - m_cpu_address;
}
[[nodiscard]] bool IsInBounds(uint64_t address, uint64_t size) const noexcept;
void Write(uint64_t offset, const void* source, uint64_t size);
void IncreaseStreamScore(int score) noexcept { stream_score += score; }
[[nodiscard]] int StreamScore() const noexcept { return stream_score; }
void Flush(uint64_t offset, uint64_t size);
void Invalidate(uint64_t offset, uint64_t size);
void CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t source_offset,
@@ -68,13 +69,13 @@ public:
void Fill(uint64_t offset, uint64_t size, uint32_t value);
// BufferCache state lives directly on the resource.
bool is_deleted = false;
size_t lru_id = 0;
bool is_deleted = false;
int stream_score = 0;
size_t lru_id = 0;
protected:
[[nodiscard]] GraphicContext& Graphics() const noexcept { return *m_graphics; }
[[nodiscard]] CommandScheduler& Scheduler() const noexcept { return *m_scheduler; }
[[nodiscard]] VulkanBuffer& NativeBuffer() noexcept { return *m_buffer; }
private:
[[nodiscard]] vk::BufferMemoryBarrier Barrier(uint64_t offset, uint64_t size,
@@ -85,11 +86,12 @@ private:
CommandScheduler* m_scheduler = nullptr;
MemoryUsage m_usage = MemoryUsage::DeviceLocal;
uint64_t m_cpu_address = 0;
uint64_t m_size = 0;
vk::DeviceAddress m_device_address = 0;
std::unique_ptr<VulkanBuffer> m_buffer;
vk::Buffer m_buffer = nullptr;
VmaAllocation m_allocation = nullptr;
uint64_t m_size;
bool m_coherent = false;
std::span<uint8_t> m_mapped;
bool m_is_coherent = false;
};
class StreamBuffer final: public Buffer {
@@ -110,7 +112,6 @@ private:
uint64_t upper_bound = 0;
};
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,
File diff suppressed because it is too large. Load diff
+37 -22
View File
@@ -13,6 +13,7 @@
#include "graphics/host_gpu/renderer/image/tiler.h"
#include <map>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@@ -37,12 +38,6 @@ public:
BindingType type = BindingType::Texture;
};
struct RegionInfo {
bool image_pages = false;
bool image_bytes = false;
bool gpu_image_bytes = false;
};
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
BufferCache& buffer_cache);
~TextureCache();
@@ -66,15 +61,14 @@ public:
uint32_t packed_clear);
void InvalidateMemory(uint64_t address, uint64_t size);
void InvalidateMemoryFromGPU(uint64_t address, uint64_t size);
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
[[nodiscard]] bool IsRegionGpuModified(uint64_t address, uint64_t size);
[[nodiscard]] bool IsMeta(uint64_t address);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice,
uint32_t* fill_value = nullptr);
[[nodiscard]] bool ClearMeta(uint64_t address);
// Returns true when registered DCC absorbed the fill and the caller may skip the dispatch.
// False may still record PendingDcc state, but the guest dispatch must execute.
[[nodiscard]] bool TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t fill_value);
// Record deferred DCC state while the original guest dispatch writes the metadata.
void TrackDccFill(uint64_t address, uint64_t size, uint32_t fill_value);
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
void UnmapMemory(uint64_t address, uint64_t size);
@@ -83,12 +77,12 @@ public:
private:
enum class TransferDirection { Upload, Download };
struct ColorTransferPlan;
struct DownloadPlan;
struct TextureTransfer;
struct ImageDownload;
struct MetaDataInfo {
// A guest metadata-fill dispatch may initialize DCC before its render target is bound.
// PendingDcc retains that exact fill until FindRenderTarget classifies the address,
// PendingDcc retains that exact fill until an image binding classifies the address,
// without exposing an unconfirmed buffer address to the normal metadata heuristics.
// Keep all surface metadata in one entry so CMask/FMask can be
// registered beside HTile and DCC without introducing parallel tracking paths.
@@ -109,6 +103,23 @@ private:
using ImageIds = InlinePageOwnerList<ImageId, 16>;
using ImagePageTable = MultiLevelPageTable<ImageIds, 20, 40, 10>;
// Callers have validated the nonempty 40-bit range with TryGetPageRange.
template <typename Func>
static void ForEachPage(uint64_t address, size_t size, Func&& func) {
using FuncReturn = typename std::invoke_result<Func, uint64_t>::type;
static constexpr bool RETURNS_BOOL = std::is_same_v<FuncReturn, bool>;
const uint64_t page_end = (address + size - 1) >> ImagePageTable::kPageBits;
for (uint64_t page = address >> ImagePageTable::kPageBits; page <= page_end; ++page) {
if constexpr (RETURNS_BOOL) {
if (func(page)) {
break;
}
} else {
func(page);
}
}
}
[[nodiscard]] ImageId InsertImage(const ImageInfo& info);
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
void RegisterImage(ImageId id);
@@ -137,16 +148,20 @@ private:
[[nodiscard]] ImageId ResolveDepthOverlap(const ImageInfo& requested, BindingType binding,
ImageId cached);
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source);
void RefreshImage(ImageId id, const ImageDesc& desc);
void InitializeImage(ImageId id, const ImageDesc& desc);
[[nodiscard]] ColorTransferPlan BuildColorTransfer(const Image& image, BindingType binding,
TransferDirection direction) const;
[[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,
uint64_t destination_size, DownloadPlan plan);
void RefreshImage(ImageId id);
void PrepareDccClear(ImageId id, const ImageDesc& desc);
void InitializeImage(ImageId id);
[[nodiscard]] TextureTransfer
BuildTextureTransfer(const Image& image, BindingType binding, TransferDirection direction) const;
[[nodiscard]] ImageDownload BuildDownload(const Image& image) const;
void UploadImage(Image& image, Buffer& source, uint64_t source_offset);
void DownloadImage(Image& image, Buffer& destination, uint64_t destination_offset,
uint64_t destination_size, ImageDownload transfer);
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
void CommitGpuWrite(Image& image);
// Caller holds m_lock. Volume layer ranges select depth slices.
void ClearImage(CommandBuffer& command, ImageId id, const vk::ImageSubresourceRange& range,
const vk::ClearValue& clear);
void PrepareImageCopy(Image& image);
void RefreshCopySource(ImageId id);
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
@@ -156,7 +171,7 @@ private:
void ValidateImageDesc(const ImageDesc& desc) const;
void InvalidateCpuAliases(uint64_t address, uint64_t size);
[[nodiscard]] bool TryDownloadImage(ImageId id);
[[nodiscard]] bool DownloadImageMemory(ImageId id);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
@@ -4,6 +4,7 @@
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
@@ -21,47 +22,40 @@ namespace Libs::Graphics {
static std::atomic<uint32_t> g_render_color_log_count = 0;
static void ResolveDccClearInfo(RenderColorInfo& info, vk::Format format, bool has_dcc,
uint32_t packed_clear) {
// Register-backed DCC clears use the target's packed clear value. Decode one-word guest
// formats here; unsupported encodings remain tracked without unsafe materialization.
info.metadata_clear_supported =
has_dcc && DecodePackedColorClear(format, packed_clear, info.color_clear_value);
switch (format) {
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Srgb:
case vk::Format::eB8G8R8A8Unorm:
case vk::Format::eB8G8R8A8Srgb:
case vk::Format::eA2B10G10R10UnormPack32:
case vk::Format::eA2R10G10B10UnormPack32:
info.metadata_fixed_clear_supported = has_dcc;
break;
default: info.metadata_fixed_clear_supported = false; break;
static bool DccAlphaOnMsb(const HW::ColorInfo& info) {
switch (info.format) {
case Prospero::ChannelLayout::k10_10_10_2:
case Prospero::ChannelLayout::k10_10_10_2Float:
case Prospero::ChannelLayout::k5_5_5_1: return true;
case Prospero::ChannelLayout::k2_10_10_10:
case Prospero::ChannelLayout::k1_5_5_5: return false;
default: break;
}
if (!info.metadata_clear_supported) {
info.color_clear_value = {};
const auto components =
Prospero::ResolveRenderTargetFormat(info.format, info.channel_type).components;
if (components == 1) {
return info.channel_order != Prospero::ChannelOrder::kStandard;
}
return components == 3 || info.channel_order == Prospero::ChannelOrder::kStandard ||
info.channel_order == Prospero::ChannelOrder::kAlt;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderColorInfo& r,
void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& r,
uint32_t render_target_slice_offset,
uint32_t render_target_slot, bool ignore_target_mask,
uint32_t rt_slot, bool ignore_target_mask,
bool exact_format) {
KYTY_PROFILER_FUNCTION();
const auto& hw = buffer.GetRegisters();
const auto rt_slot = (render_target_slot == UINT32_MAX ? render_target_first_bound_slot(buffer)
: render_target_slot);
const auto& rt = hw.GetRenderTarget(rt_slot);
auto mask = render_target_mask_slot(hw.GetRenderTargetMask(), rt_slot);
if (ignore_target_mask && rt.base.addr != 0 && mask == 0) {
mask = 0x0f;
}
r.target_slot = rt_slot;
r.export_mapping = {};
r = {};
r.target_slot = rt_slot;
if (rt.base.addr == 0 || mask == 0) {
if (graphics_debug_dump_enabled()) {
@@ -76,23 +70,6 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
}
}
// No color output
r.type = RenderColorType::NoColorOutput;
r.desc = {};
r.base_addr = 0;
r.image_id = {};
r.image_view = nullptr;
r.format = vk::Format::eUndefined;
r.extent = {};
r.base_mip_level = 0;
r.base_array_layer = 0;
r.buffer_size = 0;
r.samples = 1;
r.export_mapping = {};
r.color_clear_enable = false;
r.metadata_clear_supported = false;
r.metadata_fixed_clear_supported = false;
r.color_clear_value = {};
return;
}
const auto samples = render_sample_count(rt.attrib.num_fragments);
@@ -100,50 +77,11 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
EXIT("unsupported render-target sample configuration: samples=%u fragments=%u\n",
rt.attrib.num_samples, rt.attrib.num_fragments);
}
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:
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,
render_target_slice_offset);
}
r.base_array_layer = view.base_layer;
const uint32_t levels = rt.attrib2.num_mip_levels + 1u;
if (levels == 0 || levels > 16 || rt.view.current_mip_level >= levels) {
EXIT("unsupported render-target mip range: current=%u levels=%u\n",
rt.view.current_mip_level, levels);
}
if (graphics_debug_dump_enabled()) {
static std::atomic_uint log_count = 0;
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
if (log_id < 128) {
LOGF("RenderColorTarget: inspect slot=%" PRIu32 " base=0x%010" PRIx64
" mask=0x%01" PRIx32 " attrib2_width=%" PRIu32 " attrib2_height=%" PRIu32
" 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,
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));
}
}
// Color-control state selects the color-buffer operation and logical blend operation.
// The normal copy operation is a regular color write, not an attachment clear.
// Nonlinear clear values are still stored as normalized components.
// Fast color clears are metadata driven and must be handled explicitly when
// that metadata path is implemented; render-pass load must preserve contents.
r.color_clear_enable = false;
r.color_clear_value = {};
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint64_t size = 0;
bool tile = false;
static constexpr std::array image_types {Prospero::ImageType::kColor1D,
Prospero::ImageType::kColor2D,
Prospero::ImageType::kColor3D};
@@ -165,7 +103,48 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
if (volume && samples != 1) {
EXIT("multisampled 3D render targets are unsupported\n");
}
const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u;
const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u;
// For volumes, CB_COLOR_VIEW bounds exported slices; ATTRIB3 defines storage depth.
// The host attachment contains only the selected slices that exist in this mip.
const uint32_t last_layer = volume
? std::min(rt.view.last_array_slice_index,
std::max(depth >> rt.view.current_mip_level, 1u) - 1u)
: rt.view.last_array_slice_index;
const auto view = ResolveTargetViewInfo(
rt.view.base_array_slice_index, last_layer, render_target_slice_offset);
switch (view.type) {
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,
render_target_slice_offset);
}
if (graphics_debug_dump_enabled()) {
static std::atomic_uint log_count = 0;
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
if (log_id < 128) {
LOGF("RenderColorTarget: inspect slot=%" PRIu32 " base=0x%010" PRIx64
" mask=0x%01" PRIx32 " attrib2_width=%" PRIu32 " attrib2_height=%" PRIu32
" 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,
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));
}
}
// Color-control state selects the color-buffer operation and logical blend operation.
// The normal copy operation is a regular color write, not an attachment clear.
// Nonlinear clear values are still stored as normalized components.
// Fast color clears are metadata driven and must be handled explicitly when
// that metadata path is implemented; render-pass load must preserve contents.
uint32_t width = 0;
uint32_t height = 0;
uint32_t pitch = 0;
uint64_t size = 0;
bool tile = false;
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;
@@ -224,19 +203,14 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
" layer=%u/%u\n",
rt.attrib3.dimension, rt.attrib3.depth, view.base_layer, view.image_layers);
}
if (tile) {
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);
}
// PPSA28068: the linear EULA target and its sampled view must share the padded pitch.
if (!tile || volume || texture_tile) {
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
} else {
pitch = width;
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);
}
TileSizeOffset mip_sizes[16] {};
@@ -305,12 +279,6 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
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) {
@@ -337,10 +305,14 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
desc.info.tile_mode = rt.attrib3.tile_mode;
const bool has_dcc = rt.info.dcc_compression_enable && rt.dcc_addr.addr != 0;
if (has_dcc) {
// DCC uses a separate metadata allocation. Carry its address through ImageInfo so
// TextureCache can associate metadata fills observed before target registration.
desc.info.metadata.kind = ImageMetadataKind::Dcc;
desc.info.metadata.range.address = rt.dcc_addr.addr;
TileSizeAlign metadata_size {};
(void)TileGetDccSize(width, height, volume ? depth : view.image_layers, bytes_per_element,
levels, rt.attrib3.tile_mode, metadata_size, rt.attrib.num_fragments);
desc.info.metadata.kind = ImageMetadataKind::Dcc;
desc.info.metadata.range = {rt.dcc_addr.addr, metadata_size.size};
desc.info.metadata.dcc_clear_word = rt.clear_word0.word0;
desc.info.metadata.dcc_clear_register_valid = true;
desc.info.metadata.dcc_alpha_msb = DccAlphaOnMsb(rt.info);
}
for (uint32_t level = 0; level < levels; level++) {
if (volume) {
@@ -382,18 +354,10 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
auto& texture_cache = m_context.GetTextureCache();
r.desc = std::move(desc);
r.guest_mip_level = rt.view.current_mip_level;
r.guest_array_layer = view.base_layer;
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;
ResolveDccClearInfo(r, target_format.format, has_dcc, rt.clear_word0.word0);
BindRenderTarget(r.image_id);
}
@@ -6,33 +6,24 @@
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <cstdint>
namespace Libs::Graphics {
enum class RenderColorType {
NoColorOutput,
RenderTexture,
};
struct RenderColorInfo {
RenderColorType type = RenderColorType::NoColorOutput;
// Discovery keeps guest image information but can remap the view into a larger cache image.
TextureCache::ImageDesc desc;
ImageId image_id;
vk::ImageView image_view = nullptr;
vk::Format format = vk::Format::eUndefined;
vk::Extent2D extent = {};
uint32_t base_mip_level = 0;
uint32_t base_array_layer = 0;
uint64_t base_addr = 0;
uint64_t buffer_size = 0;
uint32_t target_slot = 0;
uint32_t samples = 1;
uint32_t guest_mip_level = 0;
uint32_t guest_array_layer = 0;
Prospero::ColorComponentMapping export_mapping;
bool color_clear_enable = false;
bool metadata_clear_supported = false;
bool metadata_fixed_clear_supported = false;
vk::ClearColorValue color_clear_value {};
[[nodiscard]] vk::Extent2D Extent() const {
return {std::max(desc.info.extent.width >> guest_mip_level, 1u),
std::max(desc.info.extent.height >> guest_mip_level, 1u)};
}
};
} // namespace Libs::Graphics
@@ -19,11 +19,11 @@ void ReportVulkanFatal(const char* what, vk::Result result, uint64_t tick, uint3
uint32_t arg3, uint64_t arg4) {
LOGF("%s failed: %s (%d), tick=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
what, VulkanToString(result).c_str(), static_cast<int>(result), tick, debug_op,
what, vk::to_string(result).c_str(), static_cast<int>(result), tick, debug_op,
debug_submit, arg0, arg1, arg2, arg3, arg4);
std::printf("%s failed: %s (%d), tick=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
what, VulkanToString(result).c_str(), static_cast<int>(result), tick, debug_op,
what, vk::to_string(result).c_str(), static_cast<int>(result), tick, debug_op,
debug_submit, arg0, arg1, arg2, arg3, arg4);
std::fflush(stdout);
}
@@ -34,7 +34,6 @@ CommandScheduler::CommandPool::CommandPool(GraphicContext& graphics, MasterSemap
: m_graphics(graphics), m_master(master) {
EXIT_IF(graphics.queue_family == static_cast<uint32_t>(-1));
vk::CommandPoolCreateInfo create {};
create.sType = vk::StructureType::eCommandPoolCreateInfo;
create.queueFamilyIndex = graphics.queue_family;
create.flags = vk::CommandPoolCreateFlagBits::eTransient |
vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
@@ -52,7 +51,6 @@ size_t CommandScheduler::CommandPool::Grow() {
m_buffers.resize(first + GrowStep);
vk::CommandBufferAllocateInfo allocate {};
allocate.sType = vk::StructureType::eCommandBufferAllocateInfo;
allocate.commandPool = m_pool;
allocate.level = vk::CommandBufferLevel::ePrimary;
allocate.commandBufferCount = static_cast<uint32_t>(GrowStep);
@@ -87,10 +85,7 @@ vk::CommandBuffer CommandScheduler::CommandPool::Commit() {
m_ticks[*found] = m_master.CurrentTick();
}
m_hint = *found + 1;
if (m_hint == m_ticks.size()) {
m_hint = 0;
}
m_hint = (*found + 1) % m_ticks.size();
return m_buffers[*found];
}
@@ -153,14 +148,10 @@ void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config
std::lock_guard lock(m_operation_mutex);
EXIT_IF(m_operation_state != OperationState::Open);
}
m_registers = &registers;
m_user_config = &user_config;
m_shaders = &shaders;
m_command.Bind(registers, user_config, shaders);
if (m_command.IsInvalid()) {
BeginNext();
} else {
BindCurrent();
}
}
@@ -217,13 +208,7 @@ void CommandScheduler::Wait(uint64_t tick) {
}
void CommandScheduler::PopPendingOperations() {
PopPendingOperations(true);
}
void CommandScheduler::PopPendingOperations(bool refresh_gpu_tick) {
if (refresh_gpu_tick) {
m_master.Refresh();
}
m_master.Refresh();
for (;;) {
PendingOperation operation;
{
@@ -351,11 +336,6 @@ CommandBuffer& CommandScheduler::Current() {
return m_command;
}
void CommandScheduler::BindCurrent() {
EXIT_IF(m_registers == nullptr || m_user_config == nullptr || m_shaders == nullptr);
m_command.Bind(*m_registers, *m_user_config, *m_shaders);
}
CommandBuffer& CommandScheduler::BeginCommand() {
EXIT_IF(!m_command.IsInvalid());
m_command.m_buffer = m_command_pool.Commit();
@@ -381,14 +361,12 @@ uint64_t CommandScheduler::Submit(SubmitInfo submit) {
submit.AddSignal(m_master.Handle(), tick);
vk::TimelineSemaphoreSubmitInfo timeline_info {};
timeline_info.sType = vk::StructureType::eTimelineSemaphoreSubmitInfo;
timeline_info.waitSemaphoreValueCount = submit.num_wait_semaphores;
timeline_info.pWaitSemaphoreValues = submit.wait_ticks.data();
timeline_info.signalSemaphoreValueCount = submit.num_signal_semaphores;
timeline_info.pSignalSemaphoreValues = submit.signal_ticks.data();
vk::SubmitInfo submit_info {};
submit_info.sType = vk::StructureType::eSubmitInfo;
submit_info.pNext = &timeline_info;
submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
submit_info.pWaitSemaphores = submit.wait_semaphores.data();
@@ -414,8 +392,7 @@ uint64_t CommandScheduler::Submit(SubmitInfo submit) {
}
void CommandScheduler::BeginNext() {
EXIT_IF(!m_command.IsInvalid());
BindCurrent();
CheckActive();
BeginCommand();
}
@@ -42,7 +42,7 @@ public:
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] static bool InDeferredOperation() noexcept;
[[nodiscard]] bool Active() const noexcept { return m_registers != nullptr; }
[[nodiscard]] bool Active() const noexcept { return m_command.m_registers != nullptr; }
void CheckActive() const;
CommandBuffer& Current();
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
@@ -80,9 +80,7 @@ private:
uint64_t tick = 0;
};
void BindCurrent();
void BeginNext();
void PopPendingOperations(bool refresh_gpu_tick);
void PriorityOperationsThread(std::stop_token stop);
void RunOperation(Common::UniqueFunction<void>&& operation);
@@ -99,9 +97,6 @@ private:
bool m_priority_active = false;
uint64_t m_priority_active_tick = 0;
OperationState m_operation_state = OperationState::Open;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
};
} // namespace Libs::Graphics
+3 -11
View File
@@ -9,7 +9,6 @@
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
@@ -34,10 +33,7 @@ void CommandBuffer::Begin() {
auto buffer = Handle();
vk::CommandBufferBeginInfo begin_info {};
begin_info.sType = vk::StructureType::eCommandBufferBeginInfo;
begin_info.pNext = nullptr;
begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
begin_info.pInheritanceInfo = nullptr;
begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
auto result = buffer.begin(&begin_info);
@@ -65,17 +61,16 @@ void CommandBuffer::SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0,
}
void CommandBuffer::BeginRendering(const RenderState& state) const {
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
state.num_color_attachments > RENDER_COLOR_ATTACHMENTS_MAX);
if (m_rendering && m_render_state == state) {
return;
}
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
state.num_color_attachments > RENDER_COLOR_ATTACHMENTS_MAX);
EndRendering();
std::array<vk::RenderingAttachmentInfo, RENDER_COLOR_ATTACHMENTS_MAX> colors {};
for (uint32_t i = 0; i < state.num_color_attachments; i++) {
const auto& attachment = state.color_attachments[i];
colors[i].sType = vk::StructureType::eRenderingAttachmentInfo;
colors[i].imageView = attachment.image_view;
colors[i].imageLayout = attachment.image_layout;
colors[i].loadOp =
@@ -86,7 +81,6 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
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 =
@@ -95,7 +89,6 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
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 =
@@ -104,7 +97,6 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
stencil.clearValue.depthStencil.stencil = depth_stencil.clear_value[1];
vk::RenderingInfo rendering {};
rendering.sType = vk::StructureType::eRenderingInfo;
rendering.renderArea.extent = {state.width, state.height};
rendering.layerCount = state.num_layers;
rendering.colorAttachmentCount = state.num_color_attachments;
+94 -279
View File
@@ -4,7 +4,6 @@
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/stringUtils.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/renderer/render.h"
@@ -17,8 +16,6 @@
namespace Libs::Graphics {
static std::atomic<uint32_t> g_scissor_default_log_count = 0;
uint32_t render_target_mask_slot(uint32_t mask, uint32_t slot) {
return (mask >> (slot * 4u)) & 0x0fu;
}
@@ -59,7 +56,7 @@ uint32_t render_target_first_bound_slot(const CommandBuffer& buffer) {
bool graphics_debug_dump_enabled() {
return Config::GraphicsDebugDumpEnabled() &&
Config::GetPrintfDirection() != Config::OutputDirection::Silent;
Config::GetPrintfDirection() != Config::LogDirection::Silent;
}
void uc_print(const char* func, const HW::UserConfig& uc) {
@@ -91,91 +88,75 @@ void uc_check(const HW::UserConfig& uc) {
EXIT_NOT_IMPLEMENTED(user_en.vgpr3 != false);
}
void sh_print(const char* func, const HW::Shader& /*uc*/) {
LOGF("%s\n", func);
}
std::string rt_print(const char* func, const HW::RenderTarget& rt) {
std::string dst;
dst.reserve(4096);
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt) {
std::vector<std::string> dst;
dst.reserve(40);
dst += fmt::format("{}\n", func);
dst.push_back(fmt::format("{}\n", func));
dst += fmt::format("\t base.addr = 0x{:016x}\n", rt.base.addr);
dst += fmt::format("\t view.base_array_slice_index = 0x{:08x}\n",
rt.view.base_array_slice_index);
dst += fmt::format("\t view.last_array_slice_index = 0x{:08x}\n",
rt.view.last_array_slice_index);
dst += fmt::format("\t view.current_mip_level = 0x{:08x}\n", rt.view.current_mip_level);
dst += fmt::format("\t info.fmask_compression_enable = {}\n",
rt.info.fmask_compression_enable ? "true" : "false");
dst.push_back(fmt::format("\t base.addr = 0x{:016x}\n", rt.base.addr));
dst.push_back(fmt::format("\t view.base_array_slice_index = 0x{:08x}\n",
rt.view.base_array_slice_index));
dst.push_back(fmt::format("\t view.last_array_slice_index = 0x{:08x}\n",
rt.view.last_array_slice_index));
dst.push_back(
fmt::format("\t view.current_mip_level = 0x{:08x}\n", rt.view.current_mip_level));
dst.push_back(fmt::format("\t info.fmask_compression_enable = {}\n",
rt.info.fmask_compression_enable ? "true" : "false"));
dst += fmt::format("\t info.fmask_data_compression_disable = {}\n",
rt.info.fmask_data_compression_disable ? "true" : "false");
dst += fmt::format("\t info.fmask_one_frag_mode = {}\n",
rt.info.fmask_one_frag_mode ? "true" : "false");
// dst.push_back(fmt::format("\t info.fmask_compression_mode = 0x{:08x}\n", //
// rt.info.fmask_compression_mode));
dst.push_back(fmt::format("\t info.fmask_data_compression_disable = {}\n",
rt.info.fmask_data_compression_disable ? "true" : "false"));
dst.push_back(fmt::format("\t info.fmask_one_frag_mode = {}\n",
rt.info.fmask_one_frag_mode ? "true" : "false"));
dst.push_back(fmt::format("\t info.cmask_fast_clear_enable = {}\n",
rt.info.cmask_fast_clear_enable ? "true" : "false"));
dst.push_back(fmt::format("\t info.dcc_compression_enable = {}\n",
rt.info.dcc_compression_enable ? "true" : "false"));
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",
rt.info.blend_clamp ? "true" : "false"));
dst.push_back(fmt::format("\t info.round_mode = {}\n",
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.num_samples = 0x{:08x}\n", rt.attrib.num_samples));
dst.push_back(
fmt::format("\t attrib.num_fragments = 0x{:08x}\n", rt.attrib.num_fragments));
dst.push_back(fmt::format("\t attrib2.width = 0x{:08x}\n", rt.attrib2.width));
dst.push_back(
fmt::format("\t attrib2.height = 0x{:08x}\n", rt.attrib2.height));
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",
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.metadata_pipe_aligned = {}\n",
rt.attrib3.metadata_pipe_aligned ? "true" : "false"));
dst.push_back(fmt::format("\t attrib3.write_vrs_rate_hint_to_cmask = {}\n",
rt.attrib3.write_vrs_rate_hint_to_cmask ? "true" : "false"));
dst.push_back(fmt::format("\t dcc.max_uncompressed_block_size = 0x{:08x}\n",
rt.dcc.max_uncompressed_block_size));
dst.push_back(fmt::format("\t dcc.max_compressed_block_size = 0x{:08x}\n",
rt.dcc.max_compressed_block_size));
dst.push_back(
fmt::format("\t dcc.color_transform = 0x{:08x}\n", rt.dcc.color_transform));
dst.push_back(fmt::format("\t dcc.overwrite_combiner_disable = {}\n",
rt.dcc.overwrite_combiner_disable ? "true" : "false"));
dst.push_back(fmt::format("\t dcc.independent_block_size = 0x{:02x}\n",
static_cast<uint8_t>(rt.dcc.independent_block_size)));
dst.push_back(fmt::format("\t data_write_on_dcc_clear_to_reg = {}\n",
rt.dcc.data_write_on_dcc_clear_to_reg ? "true" : "false"));
dst.push_back(fmt::format("\t dcc.dcc_clear_key_enable = {}\n",
rt.dcc.dcc_clear_key_enable ? "true" : "false"));
dst.push_back(fmt::format("\t cmask.addr = 0x{:016x}\n", rt.cmask.addr));
dst.push_back(fmt::format("\t fmask.addr = 0x{:016x}\n", rt.fmask.addr));
dst.push_back(
fmt::format("\t clear_word0.word0 = 0x{:08x}\n", rt.clear_word0.word0));
dst.push_back(
fmt::format("\t clear_word1.word1 = 0x{:08x}\n", rt.clear_word1.word1));
dst.push_back(
fmt::format("\t dcc_addr.addr = 0x{:016x}\n", rt.dcc_addr.addr));
dst += fmt::format("\t info.cmask_fast_clear_enable = {}\n",
rt.info.cmask_fast_clear_enable ? "true" : "false");
dst += fmt::format("\t info.dcc_compression_enable = {}\n",
rt.info.dcc_compression_enable ? "true" : "false");
dst += fmt::format("\t info.format = 0x{:08x}\n",
static_cast<uint32_t>(rt.info.format));
dst += fmt::format("\t info.channel_type = 0x{:08x}\n",
static_cast<uint32_t>(rt.info.channel_type));
dst += fmt::format("\t info.channel_order = 0x{:08x}\n",
static_cast<uint32_t>(rt.info.channel_order));
dst += fmt::format("\t info.blend_bypa = {}\n",
rt.info.blend_bypass ? "true" : "false");
dst += fmt::format("\t info.blend_clamp = {}\n",
rt.info.blend_clamp ? "true" : "false");
dst += fmt::format("\t info.round_mode = {}\n",
rt.info.round_mode ? "true" : "false");
dst += fmt::format("\t attrib.force_dest_alpha_to_one = {}\n",
rt.attrib.force_dest_alpha_to_one ? "true" : "false");
dst += fmt::format("\t attrib.num_samples = 0x{:08x}\n", rt.attrib.num_samples);
dst += fmt::format("\t attrib.num_fragments = 0x{:08x}\n", rt.attrib.num_fragments);
dst += fmt::format("\t attrib2.width = 0x{:08x}\n", rt.attrib2.width);
dst += fmt::format("\t attrib2.height = 0x{:08x}\n", rt.attrib2.height);
dst += fmt::format("\t attrib2.num_mip_levels = 0x{:08x}\n", rt.attrib2.num_mip_levels);
dst += fmt::format("\t attrib3.depth = 0x{:08x}\n", rt.attrib3.depth);
dst += fmt::format("\t attrib3.tile_mode = 0x{:08x}\n",
static_cast<uint32_t>(rt.attrib3.tile_mode));
dst += fmt::format("\t attrib3.dimension = 0x{:08x}\n", rt.attrib3.dimension);
dst += fmt::format("\t attrib3.metadata_pipe_aligned = {}\n",
rt.attrib3.metadata_pipe_aligned ? "true" : "false");
dst += fmt::format("\t attrib3.write_vrs_rate_hint_to_cmask = {}\n",
rt.attrib3.write_vrs_rate_hint_to_cmask ? "true" : "false");
dst += fmt::format("\t dcc.max_uncompressed_block_size = 0x{:08x}\n",
rt.dcc.max_uncompressed_block_size);
dst += fmt::format("\t dcc.max_compressed_block_size = 0x{:08x}\n",
rt.dcc.max_compressed_block_size);
dst += fmt::format("\t dcc.color_transform = 0x{:08x}\n", rt.dcc.color_transform);
dst += fmt::format("\t dcc.overwrite_combiner_disable = {}\n",
rt.dcc.overwrite_combiner_disable ? "true" : "false");
dst += fmt::format("\t dcc.independent_block_size = 0x{:02x}\n",
static_cast<uint8_t>(rt.dcc.independent_block_size));
dst += fmt::format("\t data_write_on_dcc_clear_to_reg = {}\n",
rt.dcc.data_write_on_dcc_clear_to_reg ? "true" : "false");
dst += fmt::format("\t dcc.dcc_clear_key_enable = {}\n",
rt.dcc.dcc_clear_key_enable ? "true" : "false");
dst += fmt::format("\t cmask.addr = 0x{:016x}\n", rt.cmask.addr);
dst += fmt::format("\t fmask.addr = 0x{:016x}\n", rt.fmask.addr);
dst += fmt::format("\t clear_word0.word0 = 0x{:08x}\n", rt.clear_word0.word0);
dst += fmt::format("\t clear_word1.word1 = 0x{:08x}\n", rt.clear_word1.word1);
dst += fmt::format("\t dcc_addr.addr = 0x{:016x}\n", rt.dcc_addr.addr);
return dst;
}
@@ -387,81 +368,6 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
z.size.valid ? "true" : "false");
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
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 || rc.copy_depth_to_color;
const bool stencil_active =
dc.stencil_enable || rc.stencil_clear_enable || rc.copy_stencil_to_color;
if (!depth_active && !stencil_active) {
return;
}
EXIT_NOT_IMPLEMENTED(rc.copy_depth_to_color || rc.copy_stencil_to_color);
EXIT_NOT_IMPLEMENTED(!z.z_info.HasValidTextureCompatibility());
EXIT_NOT_IMPLEMENTED(!z.stencil_info.HasValidTextureCompatibility());
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 != Prospero::DepthFormat::kZ16 &&
z.z_info.format != Prospero::DepthFormat::kZ32F);
if (z.z_info.num_samples != 0x00000000) {
static bool logged = false;
if (!logged) {
LOGF("DepthTarget: using native num_samples=0x%08" PRIx32 "\n",
z.z_info.num_samples);
logged = true;
}
}
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);
}
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 != 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 != Prospero::DepthFormat::kInvalid ||
z.stencil_info.format != Prospero::StencilFormat::kInvalid) {
EXIT_NOT_IMPLEMENTED(z.shading_rate_encoding != 0);
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) {
LOGF("DepthTarget: temporary: ignoring PS5 current mip level=0x%08" PRIx32 "\n",
z.depth_view.current_mip_level);
}
}
if (z.depth_view.depth_write_disable || z.depth_view.stencil_write_disable) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("DepthTarget: honoring write disable depth=%s, stencil=%s\n",
z.depth_view.depth_write_disable ? "true" : "false",
z.depth_view.stencil_write_disable ? "true" : "false");
}
}
EXIT_NOT_IMPLEMENTED(!z.depth_view.depth_write_disable &&
z.z_read_base_addr != z.z_write_base_addr);
EXIT_NOT_IMPLEMENTED(!z.depth_view.stencil_write_disable &&
z.stencil_read_base_addr != z.stencil_write_base_addr);
EXIT_NOT_IMPLEMENTED(!z.depth_view.depth_write_disable && z.z_write_base_addr == 0);
// EXIT_NOT_IMPLEMENTED(z.htile_data_base_addr == 0);
EXIT_NOT_IMPLEMENTED(!z.size.valid);
}
}
static void ClipPrint(const char* func, const HW::ClipControl& c) {
LOGF("%s\n", func);
@@ -490,7 +396,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.user_clip_plane_negate_y || c.clip_disable ||
c.vertex_kill_any || c.user_clip_plane_negate_y ||
c.user_clip_plane_cull_only || c.cull_on_clipping_error_disable ||
c.force_viewport_index_from_vs_enable);
}
@@ -546,16 +452,6 @@ static void McCheck(const HW::ModeControl& c) {
// EXIT_NOT_IMPLEMENTED(c.cull_front != false);
// EXIT_NOT_IMPLEMENTED(c.cull_back != false);
// EXIT_NOT_IMPLEMENTED(c.face != false);
if (c.poly_mode != 0) {
static bool logged = false;
if (!logged) {
LOGF("\t temporary: PA_SU_SC_MODE_CNTL.POLY_MODE is not fully implemented; continuing "
"with filled polygons\n");
logged = true;
}
}
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.vtx_window_offset_enable) {
static bool logged = false;
if (!logged) {
@@ -565,7 +461,6 @@ static void McCheck(const HW::ModeControl& c) {
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(c.provoking_vtx_last != false);
EXIT_NOT_IMPLEMENTED(c.persp_corr_dis != false);
}
@@ -592,16 +487,7 @@ static void BcPrint(const char* func, const HW::BlendControl& c, const HW::Blend
color.red, color.green, color.blue, color.alpha, cc.mode, cc.op);
}
static void BcCheck(const HW::BlendControl& /*c*/, const HW::BlendColor& color,
const HW::ColorControl& cc) {
// EXIT_NOT_IMPLEMENTED(c.color_srcblend != 0);
// EXIT_NOT_IMPLEMENTED(c.color_comb_fcn != 0);
// EXIT_NOT_IMPLEMENTED(c.color_destblend != 0);
// EXIT_NOT_IMPLEMENTED(c.alpha_srcblend != 0);
// EXIT_NOT_IMPLEMENTED(c.alpha_comb_fcn != 0);
// EXIT_NOT_IMPLEMENTED(c.alpha_destblend != 0);
// EXIT_NOT_IMPLEMENTED(c.separate_alpha_blend != false);
// EXIT_NOT_IMPLEMENTED(c.enable != false);
static void BcCheck(const HW::BlendColor& color, const HW::ColorControl& cc) {
if (color.red != 0.0f || color.green != 0.0f || color.blue != 0.0f || color.alpha != 0.0f) {
static bool logged = false;
if (!logged) {
@@ -664,29 +550,6 @@ static void DPrint(const char* func, const HW::DepthControl& c, const HW::Stenci
sm.stencil_opval_bf);
}
static void DCheck(const HW::DepthControl& c, const HW::StencilControl& s,
const HW::StencilMask& /*sm*/) {
// EXIT_NOT_IMPLEMENTED(c.stencil_enable != false);
// EXIT_NOT_IMPLEMENTED(c.z_enable != false);
// EXIT_NOT_IMPLEMENTED(c.z_write_enable != false);
// EXIT_NOT_IMPLEMENTED(c.zfunc != 0);
// Back-face stencil state is handled separately when enabled.
// EXIT_NOT_IMPLEMENTED(c.stencilfunc != 0);
// EXIT_NOT_IMPLEMENTED(c.stencilfunc_bf != 0);
// EXIT_NOT_IMPLEMENTED(s.stencil_fail != 0);
// EXIT_NOT_IMPLEMENTED(s.stencil_zpass != 0);
// EXIT_NOT_IMPLEMENTED(s.stencil_zfail != 0);
// Back-face stencil ops may legitimately differ from the front-face ops.
// EXIT_NOT_IMPLEMENTED(sm.stencil_testval != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_mask != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_writemask != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_opval != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_testval_bf != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_mask_bf != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_writemask_bf != 0);
// EXIT_NOT_IMPLEMENTED(sm.stencil_opval_bf != 0);
}
static void EqaaPrint(const char* func, const HW::EqaaControl& c) {
LOGF("%s\n", func);
@@ -845,15 +708,6 @@ static void VpCheck(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc
// EXIT_NOT_IMPLEMENTED(smc.vport_scissor_enable);
EXIT_NOT_IMPLEMENTED(smc.line_stipple_enable);
if (vp.viewports[0].zmin > 0.000000 || vp.viewports[0].zmax != 1.000000) {
static bool logged = false;
if (!logged) {
LOGF("\t warning: non-default viewport depth clamp zmin = %f, zmax = %f; using "
"viewport scale/offset for Vulkan depth range\n",
vp.viewports[0].zmin, vp.viewports[0].zmax);
logged = true;
}
}
// EXIT_NOT_IMPLEMENTED(vp.viewports[0].xscale != 960.000000);
// EXIT_NOT_IMPLEMENTED(vp.viewports[0].xoffset != 960.000000);
// EXIT_NOT_IMPLEMENTED(vp.viewports[0].yscale != -540.000000);
@@ -873,7 +727,7 @@ static void VpCheck(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc
// EXIT_NOT_IMPLEMENTED(fabsf(vp.guard_band_horz_clip - 33.133327f) > 0.001f);
// EXIT_NOT_IMPLEMENTED(fabsf(vp.guard_band_vert_clip - 59.629623f) > 0.001f);
if (vp.guard_band_horz_discard != 0.0f || vp.guard_band_vert_discard != 0.0f) {
if (vp.guard_band_horz_discard != 1.0f || vp.guard_band_vert_discard != 1.0f) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 16) {
LOGF("\t warning: unsupported PS5 guard band discard = %f, %f, continuing\n",
@@ -893,10 +747,6 @@ static bool ScissorRectValid(const ScissorRect& r) {
return r.right > r.left && r.bottom > r.top;
}
static bool ScissorRectSet(const ScissorRect& r) {
return r.left != 0 || r.top != 0 || r.right != 0 || r.bottom != 0;
}
static ScissorRect ScissorRectOffset(ScissorRect r, int x, int y) {
r.left += x;
r.right += x;
@@ -906,8 +756,8 @@ static ScissorRect ScissorRectOffset(ScissorRect r, int x, int y) {
}
static ScissorRect ScissorRectIntersect(const ScissorRect& a, const ScissorRect& b) {
return {a.left > b.left ? a.left : b.left, a.top > b.top ? a.top : b.top,
a.right < b.right ? a.right : b.right, a.bottom < b.bottom ? a.bottom : b.bottom};
return {std::max(a.left, b.left), std::max(a.top, b.top),
std::min(a.right, b.right), std::min(a.bottom, b.bottom)};
}
static ScissorRect ScissorRectClamp(ScissorRect r, uint32_t width, uint32_t height) {
@@ -954,49 +804,26 @@ static bool ScissorClipRuleToIntersectionMask(uint16_t rule, uint8_t* mask) {
}
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
vk::Extent2D extent) {
ScissorRect screen {vp.screen_scissor_left, vp.screen_scissor_top, vp.screen_scissor_right,
vp.screen_scissor_bottom};
ScissorRect final = screen;
if (!ScissorRectSet(screen)) {
final = {0, 0, static_cast<int>(extent.width), static_cast<int>(extent.height)};
auto log_id = g_scissor_default_log_count.fetch_add(1);
if (log_id < 32) {
LOGF("temporary: default unset screen scissor to framebuffer extent %ux%u\n",
extent.width, extent.height);
vk::Extent2D extent, uint32_t viewport_index) {
EXIT_IF(viewport_index >= std::size(vp.viewports));
ScissorRect final {vp.screen_scissor_left, vp.screen_scissor_top, vp.screen_scissor_right,
vp.screen_scissor_bottom};
const auto intersect = [&](ScissorRect rect, bool window_offset) {
if (window_offset) {
rect = ScissorRectOffset(rect, vp.window_offset_x, vp.window_offset_y);
}
}
final = ScissorRectIntersect(final, rect);
};
intersect({vp.window_scissor_left, vp.window_scissor_top, vp.window_scissor_right,
vp.window_scissor_bottom}, vp.window_scissor_window_offset_enable);
intersect({vp.generic_scissor_left, vp.generic_scissor_top, vp.generic_scissor_right,
vp.generic_scissor_bottom}, vp.generic_scissor_window_offset_enable);
ScissorRect window {vp.window_scissor_left, vp.window_scissor_top, vp.window_scissor_right,
vp.window_scissor_bottom};
if (ScissorRectSet(window)) {
if (vp.window_scissor_window_offset_enable) {
window = ScissorRectOffset(window, vp.window_offset_x, vp.window_offset_y);
}
final = ScissorRectIntersect(final, window);
}
ScissorRect generic {vp.generic_scissor_left, vp.generic_scissor_top, vp.generic_scissor_right,
vp.generic_scissor_bottom};
if (ScissorRectSet(generic)) {
if (vp.generic_scissor_window_offset_enable) {
generic = ScissorRectOffset(generic, vp.window_offset_x, vp.window_offset_y);
}
final = ScissorRectIntersect(final, generic);
}
const auto& viewport = vp.viewports[0];
ScissorRect viewport_scissor {viewport.viewport_scissor_left, viewport.viewport_scissor_top,
viewport.viewport_scissor_right,
viewport.viewport_scissor_bottom};
if (smc.vport_scissor_enable && ScissorRectSet(viewport_scissor)) {
if (viewport.viewport_scissor_window_offset_enable) {
viewport_scissor =
ScissorRectOffset(viewport_scissor, vp.window_offset_x, vp.window_offset_y);
}
final = ScissorRectIntersect(final, viewport_scissor);
const auto& viewport = vp.viewports[viewport_index];
if (smc.vport_scissor_enable) {
intersect({viewport.viewport_scissor_left, viewport.viewport_scissor_top,
viewport.viewport_scissor_right, viewport.viewport_scissor_bottom},
viewport.viewport_scissor_window_offset_enable);
}
if (vp.clip_rect_rule == 0) {
@@ -1009,12 +836,8 @@ ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeC
continue;
}
ScissorRect clip {vp.clip_rect_left[i], vp.clip_rect_top[i], vp.clip_rect_right[i],
vp.clip_rect_bottom[i]};
if (vp.clip_rect_window_offset_enable[i]) {
clip = ScissorRectOffset(clip, vp.window_offset_x, vp.window_offset_y);
}
final = ScissorRectIntersect(final, clip);
intersect({vp.clip_rect_left[i], vp.clip_rect_top[i], vp.clip_rect_right[i],
vp.clip_rect_bottom[i]}, vp.clip_rect_window_offset_enable[i]);
}
} else {
static std::atomic<uint32_t> log_count {0};
@@ -1032,15 +855,10 @@ void hw_check(const CommandBuffer& buffer) {
const auto& hw = buffer.GetRegisters();
const auto rt_slot = render_target_first_bound_slot(buffer);
const auto& rt = hw.GetRenderTarget(rt_slot);
const auto& bc = hw.GetBlendControl(rt_slot);
const auto& bclr = hw.GetBlendColor();
const auto& vp = hw.GetScreenViewport();
const auto& z = hw.GetDepthRenderTarget();
const auto& c = hw.GetClipControl();
const auto& rc = hw.GetRenderControl();
const auto& d = hw.GetDepthControl();
const auto& s = hw.GetStencilControl();
const auto& sm = hw.GetStencilMask();
const auto& mc = hw.GetModeControl();
const auto& eqaa = hw.GetEqaaControl();
const auto& cc = hw.GetColorControl();
@@ -1061,25 +879,22 @@ void hw_check(const CommandBuffer& buffer) {
RtCheck(rt);
log_phase("vp");
VpCheck(vp, smc);
log_phase("z");
ZCheck(z, d, rc);
log_phase("clip");
ClipCheck(c);
log_phase("rc");
RcCheck(rc);
log_phase("depth");
DCheck(d, s, sm);
log_phase("mode");
McCheck(mc);
log_phase("blend");
BcCheck(bc, bclr, cc);
BcCheck(bclr, cc);
log_phase("eqaa");
EqaaCheck(eqaa, ac);
log_phase("aa");
AaCheck(aa, ac);
log_phase("done");
if (RenderTargetMaskHasBoundMrt(buffer)) {
if (graphics_debug_dump_enabled() && RenderTargetMaskHasBoundMrt(buffer)) {
LOGF("MRT render target mask: 0x%08" PRIx32 "\n", hw.GetRenderTargetMask());
for (uint32_t i = 0; i < 8; i++) {
const auto& mrt = hw.GetRenderTarget(i);
@@ -1131,7 +946,7 @@ void hw_print(const CommandBuffer& buffer) {
hw.GetRenderTargetMask(), hw.GetDepthClearValue(), hw.GetStencilClearValue(),
hw.GetLineWidth(), hw.GetPrimitiveResetIndex());
LOGF("%s", Common::Concat(rt_print("RenderTraget:", rt), "").c_str());
LOGF("%s", rt_print("RenderTraget:", rt).c_str());
ZPrint("DepthRenderTraget:", z);
VpPrint("ScreenViewport:", vp, smc);
+2 -5
View File
@@ -5,7 +5,6 @@
#include <cstdint>
#include <string>
#include <vector>
namespace Libs::Graphics {
@@ -13,7 +12,6 @@ class CommandBuffer;
namespace HW {
class Context;
class Shader;
class UserConfig;
struct RenderTarget;
struct ScanModeControl;
@@ -32,14 +30,13 @@ uint32_t render_target_first_bound_slot(const CommandBuffer& buf
bool graphics_debug_dump_enabled();
void uc_print(const char* func, const HW::UserConfig& uc);
void uc_check(const HW::UserConfig& uc);
void sh_print(const char* func, const HW::Shader& uc);
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt);
std::string rt_print(const char* func, const HW::RenderTarget& rt);
bool RenderIsColorTileModeLinear(Prospero::TileMode tile_mode);
void hw_print(const CommandBuffer& buffer);
void hw_check(const CommandBuffer& buffer);
void LogDrawPhase(const char* draw_name, const char* phase);
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
vk::Extent2D extent);
vk::Extent2D extent, uint32_t viewport_index);
} // namespace Libs::Graphics
@@ -10,6 +10,7 @@
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
@@ -96,54 +97,20 @@ static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
return vk::Format::eUndefined;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderDepthInfo& r) {
KYTY_PROFILER_FUNCTION();
(void)submit_id;
const auto& hw = buffer.GetRegisters();
const auto& z = hw.GetDepthRenderTarget();
const auto& rc = hw.GetRenderControl();
const auto& dc = hw.GetDepthControl();
const auto& sc = hw.GetStencilControl();
const auto& sm = hw.GetStencilMask();
const bool has_stencil = z.stencil_info.format != Prospero::StencilFormat::kInvalid;
const bool depth_active = dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable ||
rc.depth_clear_enable || rc.copy_depth_to_color;
const bool stencil_active =
has_stencil && (dc.stencil_enable || rc.stencil_clear_enable || rc.copy_stencil_to_color);
if (!depth_active && !stencil_active) {
return;
}
static TextureCache::ImageDesc MakeDepthTargetDesc(const CommandBuffer& buffer,
const HW::DepthRenderTarget& z,
bool write_buffer = false) {
const bool has_stencil = z.stencil_info.format != Prospero::StencilFormat::kInvalid;
const auto depth_address = write_buffer ? z.z_write_base_addr : z.z_read_base_addr;
const auto stencil_address =
write_buffer ? z.stencil_write_base_addr : z.stencil_read_base_addr;
if (!z.z_info.HasValidTextureCompatibility() ||
!z.stencil_info.HasValidTextureCompatibility()) {
DepthFatal("invalid PS5 depth texture-compatibility encoding");
}
const bool attachment_unbound =
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 &&
!z.stencil_info.expclear_enabled && !z.stencil_info.partially_resident &&
z.depth_view.slice_start == 0 && z.depth_view.slice_max == 0 &&
z.depth_view.current_mip_level == 0 && !z.depth_view.depth_write_disable &&
!z.depth_view.stencil_write_disable && z.z_read_base_addr == 0 && z.z_write_base_addr == 0 &&
z.stencil_read_base_addr == 0 && z.stencil_write_base_addr == 0 &&
z.htile_data_base_addr == 0 &&
// DB_DEPTH_SIZE_XY is independent state and may remain programmed after the attachment
// formats and addresses are unbound. A zero encoding is the valid 1x1 value, so its
// presence alone must not manufacture a depth attachment.
!z.z_info.htile_acceleration && z.shading_rate_encoding == 0 && z.size.x_max == 0 &&
z.size.y_max == 0;
if (attachment_unbound) {
static std::atomic_bool logged = false;
if (!logged.exchange(true, std::memory_order_relaxed)) {
LOGF("DepthTarget: ignoring enabled depth/stencil state without a bound attachment\n");
}
return;
}
const bool has_htile = z.z_info.htile_acceleration;
const bool unsupported_shading_rate_encoding =
z.shading_rate_encoding > 1 || (z.shading_rate_encoding != 0 && !has_htile);
const auto samples = render_sample_count(z.z_info.num_samples);
if (samples == 0) {
DepthFatal("unsupported depth fragment count: %u", z.z_info.num_samples);
@@ -158,23 +125,16 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
z.depth_view.slice_max);
}
if (rc.copy_depth_to_color || rc.copy_stencil_to_color || rc.copy_centroid ||
rc.copy_sample != 0 || z.z_info.expclear_enabled ||
z.stencil_info.expclear_enabled || z.z_info.partially_resident ||
if (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.shading_rate_encoding != 0 ||
z.z_read_base_addr == 0 ||
(!z.depth_view.depth_write_disable && z.z_write_base_addr != z.z_read_base_addr) ||
(z.z_read_base_addr & 0xffffu) != 0 ||
dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways)) {
z.depth_view.current_mip_level != 0 || unsupported_shading_rate_encoding ||
depth_address == 0 || (depth_address & 0xffffu) != 0) {
DepthFatal("unsupported depth register state");
}
if (has_stencil) {
if (z.stencil_info.format != Prospero::StencilFormat::k8UInt || !htile_stencil_compat ||
z.stencil_read_base_addr == 0 ||
(!z.depth_view.stencil_write_disable &&
z.stencil_write_base_addr != z.stencil_read_base_addr) ||
(z.stencil_read_base_addr & 0xffffu) != 0) {
stencil_address == 0 || (stencil_address & 0xffffu) != 0) {
DepthFatal("unsupported stencil attachment state");
}
} else if (z.stencil_read_base_addr != 0 || z.stencil_write_base_addr != 0) {
@@ -201,10 +161,10 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
DepthFatal("unsupported depth/stencil format pair");
}
const auto ideal_format = DepthAttachmentFormat(*policy, has_stencil);
r.format = ResolveHostDepthAttachmentFormat(buffer, *policy, has_stencil, samples);
if (r.format == vk::Format::eUndefined) {
const auto format = ResolveHostDepthAttachmentFormat(buffer, *policy, has_stencil, samples);
if (format == vk::Format::eUndefined) {
DepthFatal("no host depth/stencil format supports required usage for %s",
VulkanToString(ideal_format).c_str());
vk::to_string(ideal_format).c_str());
}
const auto guest_format = policy->guest_format;
const uint32_t bytes = policy->bytes_per_element;
@@ -227,27 +187,98 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
const auto depth_backing_size = depth_size.size * view.image_layers;
const auto stencil_backing_size = stencil_size.size * view.image_layers;
const auto htile_backing_size = htile_size.size * view.image_layers;
if (!GuestRange {z.z_read_base_addr, depth_backing_size}.Valid() ||
(has_stencil && !GuestRange {z.stencil_read_base_addr, stencil_backing_size}.Valid()) ||
if (!GuestRange {depth_address, depth_backing_size}.Valid() ||
(has_stencil && !GuestRange {stencil_address, stencil_backing_size}.Valid()) ||
(has_htile && !GuestRange {z.htile_data_base_addr, htile_backing_size}.Valid())) {
DepthFatal("layered depth backing range is invalid");
}
r.htile = has_htile;
r.width = width;
r.height = height;
r.samples = samples;
r.depth_buffer_size = depth_backing_size;
r.depth_buffer_vaddr = z.z_read_base_addr;
r.stencil_buffer_size = has_stencil ? stencil_backing_size : 0;
r.stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0;
r.htile_buffer_size = has_htile ? htile_backing_size : 0;
r.htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0;
TextureCache::ImageDesc desc {};
desc.type = TextureCache::BindingType::DepthTarget;
desc.info.data = {depth_address, depth_backing_size};
desc.info.stencil =
has_stencil ? GuestRange {stencil_address, stencil_backing_size} : GuestRange {};
desc.info.pixel_format = format;
desc.info.guest_format = guest_format;
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, 1};
desc.info.resources = {1, view.image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes;
desc.info.samples = samples;
desc.info.tile_mode = Prospero::TileMode::kDepth;
desc.info.mip_layout[0] = {0, depth_backing_size, pitch, height};
desc.info.metadata.range =
has_htile ? GuestRange {z.htile_data_base_addr, htile_backing_size} : GuestRange {};
desc.info.metadata.kind = has_htile ? ImageMetadataKind::Htile : ImageMetadataKind::None;
desc.info.metadata.stencil_compressed =
has_stencil && has_htile && !z.stencil_info.htile_stencil_disabled;
desc.view_info.format = format;
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
desc.view_info.aspect = ImageViewOps::DepthAspectMask(format);
desc.view_info.base_level = 0;
desc.view_info.level_count = 1;
desc.view_info.base_layer = view.base_layer;
desc.view_info.layer_count = view.layer_count;
desc.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
return desc;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepthInfo& r) {
KYTY_PROFILER_FUNCTION();
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.depth_bounds_enable ||
rc.depth_clear_enable || rc.copy_depth_to_color;
const bool stencil_active =
has_stencil && (dc.stencil_enable || rc.stencil_clear_enable || rc.copy_stencil_to_color);
if (!depth_active && !stencil_active) {
return;
}
const bool attachment_unbound =
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 &&
!z.stencil_info.expclear_enabled && !z.stencil_info.partially_resident &&
z.depth_view.slice_start == 0 && z.depth_view.slice_max == 0 &&
z.depth_view.current_mip_level == 0 && !z.depth_view.depth_write_disable &&
!z.depth_view.stencil_write_disable && z.z_read_base_addr == 0 && z.z_write_base_addr == 0 &&
z.stencil_read_base_addr == 0 && z.stencil_write_base_addr == 0 &&
z.htile_data_base_addr == 0 &&
// DB_DEPTH_SIZE_XY is independent state and may remain programmed after the attachment
// formats and addresses are unbound. A zero encoding is the valid 1x1 value, so its
// presence alone must not manufacture a depth attachment.
!z.z_info.htile_acceleration && z.shading_rate_encoding == 0 && z.size.x_max == 0 &&
z.size.y_max == 0;
if (attachment_unbound) {
static std::atomic_bool logged = false;
if (!logged.exchange(true, std::memory_order_relaxed)) {
LOGF("DepthTarget: ignoring enabled depth/stencil state without a bound attachment\n");
}
return;
}
if (rc.copy_depth_to_color || rc.copy_stencil_to_color || rc.copy_centroid ||
rc.copy_sample != 0 || dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
(!z.depth_view.depth_write_disable && z.z_write_base_addr != z.z_read_base_addr) ||
(has_stencil && !z.depth_view.stencil_write_disable &&
z.stencil_write_base_addr != z.stencil_read_base_addr)) {
DepthFatal("unsupported depth register state");
}
r.desc = MakeDepthTargetDesc(buffer, z);
r.depth_clear_enable = rc.depth_clear_enable;
r.depth_meta_clear_enable = false;
r.depth_load_clear_enable = r.depth_clear_enable;
r.depth_clear_value = hw.GetDepthClearValue();
r.depth_test_enable = dc.z_enable;
r.depth_write_enable = dc.z_write_enable && !z.depth_view.depth_write_disable;
r.depth_write_enable = r.depth_test_enable && dc.z_write_enable &&
!z.depth_view.depth_write_disable && !r.depth_clear_enable;
r.depth_compare_op = static_cast<vk::CompareOp>(dc.zfunc);
r.depth_bounds_test_enable = dc.depth_bounds_enable;
@@ -292,48 +323,75 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
r.stencil_dynamic_back = r.stencil_dynamic_front;
}
}
r.vaddr_num = has_stencil ? 2 : 1;
r.vaddr[0] = r.depth_buffer_vaddr;
r.size[0] = r.depth_buffer_size;
if (has_stencil) {
r.vaddr[1] = r.stencil_buffer_vaddr;
r.size[1] = r.stencil_buffer_size;
}
TextureCache::ImageDesc desc {};
desc.type = TextureCache::BindingType::DepthTarget;
desc.info.data = {r.depth_buffer_vaddr, r.depth_buffer_size};
desc.info.stencil = {r.stencil_buffer_vaddr, r.stencil_buffer_size};
desc.info.pixel_format = r.format;
desc.info.guest_format = guest_format;
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, 1};
desc.info.resources = {1, view.image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes;
desc.info.samples = samples;
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;
desc.info.metadata.stencil_compressed =
has_stencil && has_htile && !z.stencil_info.htile_stencil_disabled;
desc.view_info.format = r.format;
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
desc.view_info.aspect = ImageViewOps::DepthAspectMask(r.format);
desc.view_info.base_level = 0;
desc.view_info.level_count = 1;
desc.view_info.base_layer = view.base_layer;
desc.view_info.layer_count = view.layer_count;
desc.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
r.desc = std::move(desc);
auto& cache = m_context.GetTextureCache();
r.image_id = cache.FindImage(r.desc);
r.image_view = nullptr;
auto& cache = m_context.GetTextureCache();
r.image_id = cache.FindImage(r.desc);
BindRenderTarget(r.image_id);
}
bool RenderExecutor::DepthStencilCopy(CommandBuffer& buffer) {
const auto& hw = buffer.GetRegisters();
const auto& z = hw.GetDepthRenderTarget();
const auto& override = hw.GetDepthRenderOverride();
if (hw.GetColorControl().mode != 0) {
return false;
}
const bool depth_copy = override.force_z_dirty && override.force_z_valid &&
z.z_info.format != Prospero::DepthFormat::kInvalid &&
z.z_read_base_addr != 0 && z.z_write_base_addr != 0 &&
z.z_read_base_addr != z.z_write_base_addr;
const bool stencil_copy = override.force_stencil_dirty && override.force_stencil_valid &&
z.stencil_info.format != Prospero::StencilFormat::kInvalid &&
z.stencil_read_base_addr != 0 && z.stencil_write_base_addr != 0 &&
z.stencil_read_base_addr != z.stencil_write_base_addr;
if (!depth_copy && !stencil_copy) {
return false;
}
auto read_desc = MakeDepthTargetDesc(buffer, z);
auto write_desc = MakeDepthTargetDesc(buffer, z, true);
auto& cache = m_context.GetTextureCache();
const auto read_id = cache.FindImage(read_desc);
BindRenderTarget(read_id);
const auto write_id = cache.FindImage(write_desc);
BindRenderTarget(write_id);
cache.UpdateImage(read_id);
cache.UpdateImage(write_id);
cache.MarkGpuWritten(write_id);
auto& source = cache.GetImage(read_id);
auto& destination = cache.GetImage(write_id);
EXIT_IF(read_id == write_id || source.backing.format != destination.backing.format);
auto& scheduler = m_context.GetCommandScheduler();
scheduler.EndRendering();
const auto command = scheduler.Current().Handle();
const ImageSubresourceRange range {read_desc.view_info.base_level, 1,
read_desc.view_info.base_layer,
read_desc.view_info.layer_count};
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
range, command);
destination.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
range, command);
std::array<vk::ImageCopy, 2> regions {};
uint32_t count = 0;
for (const auto aspect: {vk::ImageAspectFlagBits::eDepth, vk::ImageAspectFlagBits::eStencil}) {
if ((aspect == vk::ImageAspectFlagBits::eDepth && !depth_copy) ||
(aspect == vk::ImageAspectFlagBits::eStencil && !stencil_copy)) {
continue;
}
auto& region = regions[count++];
region.srcSubresource = {aspect, range.base_level, range.base_layer,
range.layer_count};
region.dstSubresource = region.srcSubresource;
region.extent = write_desc.info.extent;
}
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
destination.backing.image, vk::ImageLayout::eTransferDstOptimal,
count, regions.data());
return true;
}
vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
const auto format = desc.view_info.format;
if (format == vk::Format::eUndefined) {
return {};
}
@@ -341,7 +399,7 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
const auto available = ImageViewOps::DepthAspectMask(format);
vk::ImageAspectFlags writes {};
if ((available & vk::ImageAspectFlagBits::eDepth) &&
(depth_load_clear_enable || (depth_test_enable && depth_write_enable))) {
(depth_load_clear_enable || depth_write_enable)) {
writes |= vk::ImageAspectFlagBits::eDepth;
}
if (!(available & vk::ImageAspectFlagBits::eStencil)) {
@@ -19,26 +19,13 @@ inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stenc
}
struct RenderDepthInfo {
// Discovery keeps guest image information but can remap the view into a larger cache image.
TextureCache::ImageDesc desc;
vk::Format format = vk::Format::eUndefined;
uint32_t width = 0;
uint32_t height = 0;
uint32_t samples = 1;
bool htile = false;
uint64_t depth_buffer_size = 0;
uint64_t depth_buffer_vaddr = 0;
uint64_t depth_tile_swizzle = 0;
uint64_t stencil_buffer_size = 0;
uint64_t stencil_buffer_vaddr = 0;
uint64_t stencil_tile_swizzle = 0;
uint64_t htile_buffer_size = 0;
uint64_t htile_buffer_vaddr = 0;
uint64_t htile_tile_swizzle = 0;
bool depth_clear_enable = false;
bool depth_load_clear_enable = false;
bool depth_meta_clear_enable = false;
float depth_clear_value = 0.0f;
bool depth_test_enable = false;
// Effective draw writes; discovery applies test, target-write and clear controls.
bool depth_write_enable = false;
vk::CompareOp depth_compare_op = vk::CompareOp::eNever;
bool depth_bounds_test_enable = false;
@@ -52,24 +39,17 @@ struct RenderDepthInfo {
PipelineStencilDynamicState stencil_dynamic_front;
PipelineStencilDynamicState stencil_dynamic_back;
ImageId image_id;
vk::ImageView image_view = nullptr;
uint64_t vaddr[3] = {};
uint64_t size[3] = {};
int vaddr_num = 0;
[[nodiscard]] vk::ImageAspectFlags AttachmentWriteAspects() const;
};
inline bool depth_attachment_read_only(const RenderDepthInfo& depth) {
return !depth.AttachmentWriteAspects();
}
inline vk::ImageLayout depth_attachment_layout(const RenderDepthInfo& depth) {
const auto available = ImageViewOps::DepthAspectMask(depth.format);
const auto available = ImageViewOps::DepthAspectMask(depth.desc.view_info.format);
const auto writes = depth.AttachmentWriteAspects();
const bool has_depth = static_cast<bool>(available & vk::ImageAspectFlagBits::eDepth);
const bool has_stencil = static_cast<bool>(available & vk::ImageAspectFlagBits::eStencil);
const bool depth_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eDepth);
// LoadOp clears are separate from guest depth writes.
const bool depth_write = depth.depth_write_enable;
const bool stencil_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eStencil);
if (!has_stencil) {
return depth_write ? vk::ImageLayout::eDepthAttachmentOptimal
@@ -23,7 +23,6 @@ BlitHelper::BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler)
texture_binding.stageFlags = vk::ShaderStageFlagBits::eFragment;
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
descriptor_info.bindingCount = 1;
descriptor_info.pBindings = &texture_binding;
@@ -32,7 +31,6 @@ BlitHelper::BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler)
"create BlitHelper descriptor layout");
vk::PipelineLayoutCreateInfo layout_info {};
layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
layout_info.setLayoutCount = 1;
layout_info.pSetLayouts = &m_descriptor_layout;
RequireVulkanSuccess(
@@ -65,7 +63,6 @@ BlitHelper::~BlitHelper() {
vk::ShaderModule BlitHelper::CreateShader(const uint32_t* code, size_t words) const {
EXIT_IF(code == nullptr || words == 0);
vk::ShaderModuleCreateInfo create {};
create.sType = vk::StructureType::eShaderModuleCreateInfo;
create.codeSize = words * sizeof(uint32_t);
create.pCode = code;
vk::ShaderModule module = nullptr;
@@ -84,51 +81,37 @@ vk::Pipeline BlitHelper::GetPipeline(PipelineKey key) {
EXIT_IF(samples == vk::SampleCountFlagBits {} || key.format == vk::Format::eUndefined);
std::array<vk::PipelineShaderStageCreateInfo, 2> stages {};
stages[0].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stages[0].stage = vk::ShaderStageFlagBits::eVertex;
stages[0].module = m_vertex_shader;
stages[0].pName = "main";
stages[1].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stages[1].stage = vk::ShaderStageFlagBits::eFragment;
stages[1].module = m_fragment_shader;
stages[1].pName = "main";
vk::PipelineVertexInputStateCreateInfo vertex_input {};
vertex_input.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo;
vk::PipelineInputAssemblyStateCreateInfo input_assembly {};
input_assembly.sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo;
input_assembly.topology = vk::PrimitiveTopology::eTriangleList;
vk::PipelineViewportStateCreateInfo viewport {};
viewport.sType = vk::StructureType::ePipelineViewportStateCreateInfo;
viewport.viewportCount = 1;
viewport.scissorCount = 1;
vk::PipelineRasterizationStateCreateInfo rasterization {};
rasterization.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
rasterization.polygonMode = vk::PolygonMode::eFill;
rasterization.cullMode = vk::CullModeFlagBits::eNone;
rasterization.lineWidth = 1.0f;
rasterization.lineWidth = 1.0f;
vk::PipelineMultisampleStateCreateInfo multisample {};
multisample.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo;
multisample.rasterizationSamples = samples;
vk::PipelineDepthStencilStateCreateInfo depth {};
depth.sType = vk::StructureType::ePipelineDepthStencilStateCreateInfo;
depth.depthTestEnable = VK_TRUE;
depth.depthWriteEnable = VK_TRUE;
depth.depthCompareOp = vk::CompareOp::eAlways;
vk::PipelineColorBlendStateCreateInfo color_blend {};
color_blend.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
const std::array dynamic_states {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
vk::PipelineDynamicStateCreateInfo dynamic {};
dynamic.sType = vk::StructureType::ePipelineDynamicStateCreateInfo;
dynamic.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
dynamic.pDynamicStates = dynamic_states.data();
vk::PipelineRenderingCreateInfo rendering {};
rendering.sType = vk::StructureType::ePipelineRenderingCreateInfo;
rendering.depthAttachmentFormat = key.format;
vk::GraphicsPipelineCreateInfo create {};
create.sType = vk::StructureType::eGraphicsPipelineCreateInfo;
create.pNext = &rendering;
create.stageCount = static_cast<uint32_t>(stages.size());
create.pStages = stages.data();
@@ -186,7 +169,6 @@ void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
command);
vk::RenderingAttachmentInfo depth_attachment {};
depth_attachment.sType = vk::StructureType::eRenderingAttachmentInfo;
depth_attachment.imageView = destination_view;
depth_attachment.imageLayout = ColorToMsDepthLayout;
depth_attachment.loadOp = vk::AttachmentLoadOp::eClear;
@@ -194,7 +176,6 @@ void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
depth_attachment.clearValue.depthStencil = {0.0f, 0};
vk::RenderingInfo rendering {};
rendering.sType = vk::StructureType::eRenderingInfo;
rendering.renderArea.extent = {destination_info.extent.width, destination_info.extent.height};
rendering.layerCount = 1;
rendering.pDepthAttachment = &depth_attachment;
@@ -204,7 +185,6 @@ void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
descriptor_image.imageView = source_view;
descriptor_image.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
vk::WriteDescriptorSet descriptor_write {};
descriptor_write.sType = vk::StructureType::eWriteDescriptorSet;
descriptor_write.dstBinding = 0;
descriptor_write.descriptorCount = 1;
descriptor_write.descriptorType = vk::DescriptorType::eSampledImage;
+48 -67
View File
@@ -26,12 +26,13 @@ namespace {
}
}
[[nodiscard]] vk::ImageCreateFlags ImageCreateFlags(const ImageInfo& info) {
[[nodiscard]] vk::ImageCreateFlags ImageCreateFlags(const GraphicContext& graphics,
const ImageInfo& info) {
vk::ImageCreateFlags flags {};
if (DepthAspectTransferFormat(info.pixel_format) == vk::Format::eUndefined) {
flags |= vk::ImageCreateFlagBits::eMutableFormat;
flags |= vk::ImageCreateFlagBits::eExtendedUsage;
if (Prospero::BlockCompressedBytesPerBlock(info.guest_format) != 0) {
if (info.IsBlock() && graphics.supports_block_texel_view) {
flags |= vk::ImageCreateFlagBits::eBlockTexelViewCompatible;
}
}
@@ -47,6 +48,10 @@ namespace {
}
[[nodiscard]] vk::ImageUsageFlags ImageUsageFlags(GraphicContext& graphics, const ImageInfo& info) {
if (info.IsBlock()) {
return vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eSampled;
}
const auto properties = graphics.GetFormatProperties(info.pixel_format);
auto usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eSampledImage)) {
@@ -54,21 +59,16 @@ namespace {
}
if (DepthAspectTransferFormat(info.pixel_format) != vk::Format::eUndefined) {
usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
if (graphics.attachment_feedback_loop_enabled && (usage & vk::ImageUsageFlagBits::eSampled)) {
usage |= vk::ImageUsageFlagBits::eAttachmentFeedbackLoopEXT;
}
return usage;
}
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eColorAttachment)) {
usage |= vk::ImageUsageFlagBits::eColorAttachment;
}
if (info.samples == 1 &&
HasFormatFeature(properties, vk::FormatFeatureFlagBits::eStorageImage)) {
if (info.samples == 1) {
usage |= vk::ImageUsageFlagBits::eStorage;
} else if (info.samples == 1) {
const auto compatible = SrgbStorageViewFormat(info.pixel_format);
if (compatible != vk::Format::eUndefined &&
HasFormatFeature(graphics.GetFormatProperties(compatible),
vk::FormatFeatureFlagBits::eStorageImage)) {
usage |= vk::ImageUsageFlagBits::eStorage;
}
}
return usage;
}
@@ -210,9 +210,7 @@ void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destina
if (barriers.empty()) {
return;
}
if (m_scheduler != nullptr) {
m_scheduler->EndRendering();
}
m_scheduler.EndRendering();
vk::DependencyInfo dependency {};
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(barriers.size());
dependency.pImageMemoryBarriers = barriers.data();
@@ -221,8 +219,8 @@ void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destina
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();
EXIT_IF(copies.empty() || buffer == nullptr || size == 0);
m_scheduler.EndRendering();
vk::BufferMemoryBarrier2 buffer_barrier {};
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
buffer_barrier.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite;
@@ -242,7 +240,7 @@ void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffe
dependency.pBufferMemoryBarriers = &buffer_barrier;
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
dependency.pImageMemoryBarriers = image_barriers.data();
auto command = m_scheduler->Current().Handle();
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());
@@ -260,8 +258,8 @@ void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffe
void Image::Download(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();
EXIT_IF(copies.empty() || buffer == nullptr || size == 0);
m_scheduler.EndRendering();
vk::BufferMemoryBarrier2 buffer_barrier {};
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
buffer_barrier.srcAccessMask =
@@ -282,7 +280,7 @@ void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buf
dependency.pBufferMemoryBarriers = &buffer_barrier;
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
dependency.pImageMemoryBarriers = image_barriers.data();
auto command = m_scheduler->Current().Handle();
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());
@@ -319,8 +317,8 @@ std::pair<uint32_t, uint32_t> Image::SanitizeCopyLayers(const Image& source,
}
void Image::CopyImage(Image& source) {
EXIT_IF(m_scheduler == nullptr || source.backing.samples != backing.samples);
m_scheduler->EndRendering();
EXIT_IF(source.backing.samples != backing.samples);
m_scheduler.EndRendering();
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
@@ -359,7 +357,7 @@ void Image::CopyImage(Image& source) {
if (copies.empty()) {
return;
}
auto command = m_scheduler->Current().Handle();
auto command = m_scheduler.Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
command);
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
@@ -372,8 +370,7 @@ void Image::CopyImage(Image& source) {
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 ||
EXIT_IF(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 ||
source_range.base_level >= source.backing.mip_levels ||
@@ -401,8 +398,8 @@ void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
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();
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,
@@ -438,9 +435,8 @@ 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);
m_scheduler->EndRendering();
EXIT_IF(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 =
FullAspectMask(source.backing.format) & ~vk::ImageAspectFlagBits::eStencil;
@@ -470,7 +466,7 @@ void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &barrier;
auto command = m_scheduler->Current().Handle();
auto command = m_scheduler.Current().Handle();
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
command);
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
@@ -530,9 +526,9 @@ void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
}
void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
EXIT_IF(m_scheduler == nullptr || source.backing.samples != backing.samples ||
mip >= backing.mip_levels || layer >= backing.layers);
m_scheduler->EndRendering();
EXIT_IF(source.backing.samples != backing.samples || mip >= backing.mip_levels ||
layer >= backing.layers);
m_scheduler.EndRendering();
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);
@@ -552,7 +548,7 @@ void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
copy.dstSubresource = {aspect, mip, layer, destination_layers};
copy.extent = {width, height, depth};
}
auto command = m_scheduler->Current().Handle();
auto command = m_scheduler.Current().Handle();
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
command);
@@ -656,7 +652,7 @@ Prospero::BufferFormat RenderTargetTransferFormat(uint32_t bytes_per_element) {
} // namespace ImageOps
Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageInfo& image_info)
: info(image_info), m_graphics(&graphics), m_scheduler(&scheduler) {
: info(image_info), m_graphics(graphics), m_scheduler(scheduler) {
KYTY_PROFILER_FUNCTION();
ImageOps::Validate(info);
m_cpu_dirty =
@@ -665,29 +661,17 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
return;
}
backing.format = info.pixel_format;
backing.image_type = HostImageType(info.type);
backing.extent = info.extent;
backing.guest_pitch = info.pitch;
backing.layers = info.IsVolume() ? 1u : info.resources.layers;
backing.mip_levels = info.resources.levels;
backing.samples = info.samples;
backing.flags = ImageCreateFlags(info);
backing.usage = ImageUsageFlags(graphics, info);
vk::ImageCreateInfo create {};
create.sType = vk::StructureType::eImageCreateInfo;
create.flags = backing.flags;
create.imageType = backing.image_type;
create.extent = backing.extent;
create.mipLevels = backing.mip_levels;
create.arrayLayers = backing.layers;
create.format = backing.format;
create.flags = ImageCreateFlags(graphics, info);
create.imageType = HostImageType(info.type);
create.extent = info.extent;
create.mipLevels = info.resources.levels;
create.arrayLayers = info.IsVolume() ? 1u : info.resources.layers;
create.format = info.pixel_format;
create.tiling = vk::ImageTiling::eOptimal;
create.initialLayout = backing.state.layout;
create.usage = backing.usage;
create.sharingMode = vk::SharingMode::eExclusive;
create.samples = vulkan_sample_count(backing.samples);
create.initialLayout = vk::ImageLayout::eUndefined;
create.usage = ImageUsageFlags(graphics, info);
create.samples = vulkan_sample_count(info.samples);
vk::ImageFormatProperties properties {};
if (graphics.GetImageFormatProperties(create.format, create.imageType, create.tiling,
@@ -698,10 +682,9 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
"flags=0x%x samples=%u\n",
static_cast<int>(create.format), static_cast<int>(create.imageType),
static_cast<vk::ImageUsageFlags::MaskType>(create.usage),
static_cast<vk::ImageCreateFlags::MaskType>(create.flags), backing.samples);
static_cast<vk::ImageCreateFlags::MaskType>(create.flags), info.samples);
}
backing.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
if (!graphics.CreateImage(create, backing)) {
EXIT("failed to create image: extent=%ux%ux%u format=%d layers=%u levels=%u\n",
create.extent.width, create.extent.height, create.extent.depth,
@@ -710,11 +693,12 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
}
uint64_t Image::HashGuestEdges() const {
constexpr uint64_t page_mask = TRACKER_PAGE_SIZE - 1;
std::array<uint8_t, TRACKER_PAGE_SIZE * 2> bytes {};
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_end =
std::min(range.End(), Common::AlignUp(range.address, TRACKER_PAGE_SIZE));
const uint64_t tail_begin =
std::max(range.address, Common::AlignDown(range.End(), TRACKER_PAGE_SIZE));
const uint64_t head_size = head_end - range.address;
const uint64_t tail_address = tail_begin < head_end ? head_end : tail_begin;
const uint64_t tail_size = range.End() - tail_address;
@@ -729,16 +713,13 @@ uint64_t Image::HashGuestEdges() const {
Image::~Image() {
KYTY_PROFILER_FUNCTION();
if (m_graphics == nullptr) {
return;
}
for (const auto& cached: views) {
if (cached.view != nullptr) {
m_graphics->device.destroyImageView(cached.view, nullptr);
m_graphics.device.destroyImageView(cached.view, nullptr);
}
}
if (backing.image != nullptr) {
m_graphics->DeleteImage(backing);
m_graphics.DeleteImage(backing);
}
}
+11 -12
View File
@@ -1,6 +1,7 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#include "common/alignment.h"
#include "common/assert.h"
#include "common/slotVector.h"
#include "graphics/host_gpu/graphicContext.h"
@@ -35,11 +36,13 @@ struct ImageUsage {
};
struct ImageBinding {
bool is_bound = false;
bool is_target = false;
bool needs_rebind = false;
bool force_general = false;
bool shader_write = false;
vk::ImageLayout attachment_layout = vk::ImageLayout::eUndefined;
vk::AccessFlags2 attachment_access;
bool is_bound = false;
bool is_target = false;
bool needs_rebind = false;
bool force_general = false;
bool shader_write = false;
};
class Image final {
@@ -49,7 +52,6 @@ public:
KYTY_CLASS_NO_COPY(Image);
[[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,
@@ -127,15 +129,12 @@ public:
return pages ? ImagePageRangesOverlap(info.data.address, info.data.size, address, size)
: ImageRangeOverlaps(info.data.address, info.data.size, address, size);
}
[[nodiscard]] bool GpuOverlaps(uint64_t address, uint64_t size) const noexcept {
return IsGpuModified() && Overlaps(address, size);
}
[[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};
return backing.image == nullptr ? 0 : Common::AlignUp(info.data.size, 1024);
}
[[nodiscard]] uint64_t HashGuestEdges() const;
@@ -161,8 +160,8 @@ private:
[[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;
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
uint64_t m_maybe_cpu_hash = 0;
bool m_cpu_dirty = false;
bool m_maybe_cpu_dirty = false;
@@ -23,8 +23,11 @@ struct ImageMetadataInfo {
GuestRange range;
ImageMetadataKind kind = ImageMetadataKind::None;
uint32_t control = 0;
uint32_t dcc_clear_word = 0;
VideoOutCompression compression = VideoOutCompression::Uncompressed;
bool stencil_compressed = false;
bool dcc_clear_register_valid = false;
bool dcc_alpha_msb = true;
};
struct ImageSubresources {
@@ -44,6 +47,7 @@ struct ImageSubresourceRange {
struct ImageMipInfo {
uint64_t offset = 0;
uint64_t size = 0;
// Padded dimensions in storage elements (compressed blocks for BC formats).
uint32_t pitch = 0;
uint32_t height = 0;
auto operator<=>(const ImageMipInfo&) const = default;
@@ -78,9 +82,6 @@ struct ImageInfo {
[[nodiscard]] constexpr bool IsVolume() const noexcept {
return type == Prospero::ImageType::kColor3D;
}
[[nodiscard]] constexpr bool IsLayered() const noexcept {
return !IsVolume() && resources.layers > 1;
}
[[nodiscard]] constexpr uint32_t TransferLayers() const noexcept {
return IsVolume() ? extent.depth : resources.layers;
}
@@ -338,14 +339,6 @@ ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t
}
}
[[nodiscard]] inline constexpr bool
CanUseVideoOutNativeWithoutUpload(VideoOutCompression compression, bool render_target,
bool gpu_modified, bool guest_modified) noexcept {
return compression != VideoOutCompression::Uncompressed &&
compression != VideoOutCompression::Unsupported && !guest_modified &&
(render_target || gpu_modified);
}
struct VideoOutPixelFormatInfo {
vk::Format format = vk::Format::eUndefined;
Prospero::BufferFormat guest_format = Prospero::BufferFormat::kInvalid;
@@ -396,32 +389,6 @@ inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {
return false;
}
[[nodiscard]] inline constexpr bool
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::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 =
(static_cast<uint64_t>(info.extent.width) + 127u) & ~uint64_t {127u};
const auto padded_height =
(static_cast<uint64_t>(info.extent.height) + 127u) & ~uint64_t {127u};
return expected_pitch <= UINT32_MAX && info.pitch == expected_pitch &&
expected_pitch <= UINT64_MAX / padded_height / info.bytes_per_block &&
info.data.size == expected_pitch * padded_height * info.bytes_per_block;
}
[[nodiscard]] inline constexpr bool IsTiledRenderTarget(const ImageInfo& info) noexcept {
return info.tile_mode == Prospero::TileMode::kRenderTarget ||
IsSupportedStandard64RenderTarget(info);
}
[[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint32_t packed,
vk::ClearColorValue& clear) {
vk::ClearColorValue next {};
@@ -21,10 +21,6 @@ namespace {
}
}
[[nodiscard]] bool IsCompatibleViewFormat(vk::Format image_format, vk::Format view_format) {
return ImageViewOps::FormatsCompatible(image_format, view_format);
}
[[nodiscard]] bool IsStencilViewFormat(vk::Format format) {
switch (format) {
case vk::Format::eS8Uint:
@@ -326,8 +322,14 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
normalized.aspect = vk::ImageAspectFlagBits::eStencil;
}
normalized.usage = is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {};
for (const auto& cached: views) {
if (cached.info == normalized) {
return cached.view;
}
}
const bool format_compatible = normalized.format != vk::Format::eUndefined &&
IsCompatibleViewFormat(image.format, normalized.format);
ImageViewOps::FormatsCompatible(image.format, normalized.format);
const bool slice_view =
image.image_type == vk::ImageType::e3D && (normalized.type == vk::ImageViewType::e2D ||
normalized.type == vk::ImageViewType::e2DArray);
@@ -355,20 +357,12 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
image.layers);
}
for (const auto& cached: views) {
if (cached.info == normalized) {
return cached.view;
}
}
vk::ImageViewUsageCreateInfo usage {};
usage.sType = vk::StructureType::eImageViewUsageCreateInfo;
usage.usage = image.usage;
if (!is_storage) {
usage.usage &= ~vk::ImageUsageFlagBits::eStorage;
}
vk::ImageViewCreateInfo create {};
create.sType = vk::StructureType::eImageViewCreateInfo;
create.pNext = &usage;
create.image = image.image;
create.viewType = normalized.type;
@@ -381,7 +375,7 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
create.subresourceRange.layerCount = normalized.layer_count;
vk::ImageView view = nullptr;
const auto result = m_graphics->device.createImageView(&create, nullptr, &view);
const auto result = m_graphics.device.createImageView(&create, nullptr, &view);
if (result != vk::Result::eSuccess || view == nullptr) {
EXIT("failed to create image view: result=%d image_format=%d view_format=%d type=%d "
"aspect=0x%x mip=%u+%u layer=%u+%u usage=0x%x\n",
@@ -92,29 +92,18 @@ SelectSampledColorView(vk::Format image_format, vk::Format view_format, uint32_t
}
}
[[nodiscard]] inline uint32_t
SelectSampledDepthView(vk::Format image_format, vk::Format view_format, uint32_t swizzle) noexcept {
if (IsSupportedSampledDepthView(image_format, view_format, swizzle)) {
return swizzle;
}
EXIT("unsupported sampled depth image view: image_format=%d view_format=%d swizzle=0x%03x\n",
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
}
[[nodiscard]] inline bool
IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
if (resource.kind != ShaderRecompiler::IR::ResourceKind::Image &&
resource.kind != ShaderRecompiler::IR::ResourceKind::ImageUint) {
if (resource.resource_class != ShaderRecompiler::IR::ImageResourceClass::Sampled) {
return false;
}
if (resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint && resource.depth_compare) {
if (resource.numeric_class == Prospero::TextureNumericClass::Unsupported) {
return false;
}
return (resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaaArray) &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
if (resource.numeric_class != Prospero::TextureNumericClass::Float && resource.depth_compare) {
return false;
}
return resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
!resource.written && !resource.atomic;
}
@@ -128,8 +117,9 @@ inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_fo
[[nodiscard]] inline bool
IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
return (resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
return resource.resource_class == ShaderRecompiler::IR::ImageResourceClass::Storage &&
(resource.numeric_class == Prospero::TextureNumericClass::Float ||
resource.numeric_class == Prospero::TextureNumericClass::Uint) &&
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
@@ -141,19 +131,19 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
resource.mip_count != 0u)) &&
resource.written &&
(!resource.atomic ||
(resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint &&
resource.read)) &&
(resource.numeric_class == Prospero::TextureNumericClass::Uint && resource.read)) &&
!resource.depth_compare;
}
inline void
ValidateStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
if (!IsSupportedStorageImageResource(resource)) {
EXIT("unsupported storage color image resource: kind=%u dimension=%u mip=%u "
EXIT("unsupported storage color image resource: class=%u numeric=%u dimension=%u mip=%u "
"read=%d written=%d atomic=%d depth_compare=%d\n",
static_cast<uint32_t>(resource.kind), static_cast<uint32_t>(resource.dimension),
static_cast<uint32_t>(resource.mip_mode), resource.read, resource.written,
resource.atomic, resource.depth_compare);
static_cast<uint32_t>(resource.resource_class),
static_cast<uint32_t>(resource.numeric_class),
static_cast<uint32_t>(resource.dimension), static_cast<uint32_t>(resource.mip_mode),
resource.read, resource.written, resource.atomic, resource.depth_compare);
}
}
@@ -1,15 +1,16 @@
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/host_gpu/renderer/image/tiler.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/shader.h"
#include <algorithm>
#include <bit>
#include <array>
#include <cinttypes>
#include <cstring>
namespace Libs::Graphics {
namespace {
@@ -27,13 +28,13 @@ constexpr Prospero::ColorComponentMapping kRenderTargetColorMappings[4][4] = {
Prospero::ColorMappingArgb},
};
struct RenderTargetHostFormatInfo {
struct HostFormatInfo {
vk::Format format = vk::Format::eUndefined;
Prospero::ColorComponentMapping host_to_storage;
};
RenderTargetHostFormatInfo ResolveRenderTargetHostFormat(Prospero::BufferFormat guest_format,
Prospero::ChannelOrder order) {
HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format,
Prospero::ChannelOrder order) {
if (order == Prospero::ChannelOrder::kAlt) {
switch (guest_format) {
case Prospero::BufferFormat::k8_8_8_8UNorm:
@@ -47,17 +48,77 @@ RenderTargetHostFormatInfo ResolveRenderTargetHostFormat(Prospero::BufferFormat
default: break;
}
}
const auto format = VulkanFormat(guest_format);
switch (guest_format) {
case Prospero::BufferFormat::k5_5_5_1UNorm:
return {vk::Format::eA1R5G5B5UnormPack16, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k5_5_5_1UNorm: return {format, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k1_5_5_5UNorm:
return {vk::Format::eR5G5B5A1UnormPack16, Prospero::ColorMappingAbgr};
case Prospero::BufferFormat::k4_4_4_4UNorm:
return {vk::Format::eR4G4B4A4UnormPack16, Prospero::ColorMappingAbgr};
default: return {VulkanFormat(guest_format), {}};
case Prospero::BufferFormat::k4_4_4_4UNorm: return {format, Prospero::ColorMappingAbgr};
default: return {format, {}};
}
}
uint32_t GetTextureLevelDepth(uint32_t depth, uint32_t level, bool volume_texture) {
return volume_texture ? std::max(depth >> level, 1u) : depth;
}
size_t GetTextureRegionCount(uint32_t depth, uint32_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 CalcTextureSliceStride(const TextureUploadMipLayout* mips, uint32_t levels,
uint64_t total_size, uint32_t depth) {
uint64_t stride = 0;
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
stride = std::max(stride, mips[mip_level].offset + mips[mip_level].size);
}
if (depth > 1 && total_size != 0 && total_size % depth == 0) {
stride = std::max(stride, total_size / depth);
}
return stride;
}
uint64_t CalcLinearUploadLevelSize(const TileTextureElementLayout& element, uint32_t pitch,
uint32_t height) {
const uint32_t element_columns =
std::max((pitch + element.texel_width - 1u) / element.texel_width, 1u);
const uint32_t element_rows =
std::max((height + element.texel_height - 1u) / element.texel_height, 1u);
return static_cast<uint64_t>(element_columns) * element_rows * element.bytes;
}
uint64_t SetLinearUploadLevels(TextureUploadMipLayout* mips,
const TileTextureElementLayout& element, uint32_t height,
uint32_t levels, uint32_t base_pitch) {
uint64_t offset = 0;
auto mip_pitch = base_pitch;
auto mip_height = height;
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
const auto size = CalcLinearUploadLevelSize(element, mip_pitch, mip_height);
mips[mip_level] = {offset, size};
offset += size;
if (mip_pitch > 1) {
mip_pitch /= 2;
}
if (mip_height > 1) {
mip_height /= 2;
}
}
return offset;
}
bool FitsBufferRange(uint64_t offset, uint64_t size, uint64_t capacity) {
return offset <= capacity && size <= capacity - offset;
}
} // namespace
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
@@ -65,135 +126,61 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layo
Prospero::ChannelOrder order) {
const auto encoding = Prospero::ResolveRenderTargetFormat(layout, type);
if (encoding.IsValid() && encoding.SupportsOrder(order)) {
const auto host = ResolveRenderTargetHostFormat(encoding.buffer_format, order);
const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format);
if (host.format != vk::Format::eUndefined && bytes != 0) {
const auto host_format = ResolveHostFormat(encoding.buffer_format, order);
const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format);
if (host_format.format != vk::Format::eUndefined && bytes != 0) {
const auto order_mapping =
kRenderTargetColorMappings[static_cast<size_t>(order)][encoding.components - 1u];
return {host.format, bytes, host.host_to_storage.Then(order_mapping)};
return {host_format.format, bytes, host_format.host_to_storage.Then(order_mapping)};
}
}
EXIT("unsupported render-target format combination: layout=%u type=%u order=%u\n",
static_cast<uint32_t>(layout), static_cast<uint32_t>(type), static_cast<uint32_t>(order));
}
namespace {
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);
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle,
Prospero::ColorComponentMapping host_to_storage) {
constexpr std::array host_components {vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG,
vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA};
std::array<vk::ComponentSwizzle, 4> storage_components {};
for (uint32_t host_component = 0; host_component < host_components.size(); ++host_component) {
storage_components[host_to_storage.Map(host_component)] = host_components[host_component];
}
return count;
}
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;
const auto resolve = [&](uint32_t component) {
const auto selector = static_cast<Prospero::CompSwizzle>(GetDstSel(swizzle, component));
switch (selector) {
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
case Prospero::CompSwizzle::kOne: return vk::ComponentSwizzle::eOne;
case Prospero::CompSwizzle::kRed: return storage_components[0];
case Prospero::CompSwizzle::kGreen: return storage_components[1];
case Prospero::CompSwizzle::kBlue: return storage_components[2];
case Prospero::CompSwizzle::kAlpha: return storage_components[3];
default: EXIT("unknown swizzle: %u\n", static_cast<uint32_t>(selector));
}
};
return {resolve(0), resolve(1), resolve(2), resolve(3)};
}
SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format) {
const auto backing_format = Prospero::RemapTextureFormat(format);
const auto vk_format = VulkanFormat(backing_format);
const auto backing_format = Prospero::RemapTextureFormat(format);
const auto host_format = ResolveHostFormat(backing_format, Prospero::ChannelOrder::kStandard);
const auto conversion_format =
backing_format != format ? format : Prospero::BufferFormat::kInvalid;
if (vk_format != vk::Format::eUndefined) {
return SurfaceFormatInfo(vk_format, conversion_format);
if (host_format.format != vk::Format::eUndefined) {
return {host_format.format, conversion_format, host_format.host_to_storage};
}
EXIT("unknown format: fmt = %u\n", static_cast<uint32_t>(format));
return SurfaceFormatInfo(vk::Format::eUndefined, Prospero::BufferFormat::kInvalid);
}
namespace {
uint64_t CalcTextureSliceStride(const TextureUploadMipLayout* mips, 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, mips[i].offset + mips[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(const TileTextureElementLayout& element, uint32_t pitch,
uint32_t height) {
const uint32_t elements_w =
std::max((pitch + element.texel_width - 1u) / element.texel_width, 1u);
const uint32_t elements_h =
std::max((height + element.texel_height - 1u) / element.texel_height, 1u);
return static_cast<uint64_t>(elements_w) * elements_h * element.bytes;
}
uint64_t SetLinearUploadLevels(TextureUploadMipLayout* mips,
const TileTextureElementLayout& element, 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(element, pitch, h);
mips[i].size = size;
mips[i].offset = offset;
offset += size;
if (pitch > 1) {
pitch /= 2;
}
if (h > 1) {
h /= 2;
}
}
return offset;
}
} // namespace
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
uint32_t height, uint32_t levels, uint32_t depth,
Prospero::TileMode tile, uint64_t upload_size,
Prospero::TileMode tile_mode, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner) {
TextureUploadLayout layout {};
layout.surface.description = {
format,
tile,
tile_mode,
volume_texture ? TileSurfaceDimension::Dim3D : TileSurfaceDimension::Dim2D,
width,
height,
@@ -202,7 +189,6 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
volume_texture ? 1u : depth,
};
const auto& description = layout.surface.description;
const auto tile_mode = description.tile_mode;
TileTextureElementLayout element {};
if (format == Prospero::BufferFormat::kInvalid) {
@@ -212,34 +198,19 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
levels);
}
switch (tile_mode) {
case Prospero::TileMode::kLinear:
if (!TileGetTextureElementLayout(format, element)) {
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner,
static_cast<uint32_t>(format));
}
break;
case Prospero::TileMode::kDepth:
if (!allow_depth_tile || !TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(format),
static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
levels);
}
break;
default:
if (!TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(format),
static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
levels);
}
break;
}
if (tile_mode != Prospero::TileMode::kLinear) {
if (tile_mode == Prospero::TileMode::kLinear) {
if (!TileGetTextureElementLayout(format, element)) {
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner,
static_cast<uint32_t>(format));
}
} else {
if ((tile_mode == Prospero::TileMode::kDepth && !allow_depth_tile) ||
!TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(format), static_cast<uint32_t>(tile_mode),
upload_size, width, height, levels);
}
element = {layout.surface.texture.block.bytes_per_element,
layout.surface.texture.texel_width, layout.surface.texture.texel_height};
}
@@ -272,40 +243,33 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout) {
const auto& description = layout.surface.description;
const bool volume_texture = description.dimension == TileSurfaceDimension::Dim3D;
const bool linear_texture = description.tile_mode == Prospero::TileMode::kLinear;
const auto depth = volume_texture ? description.depth : description.layers;
uint32_t mip_width = description.width;
uint32_t mip_height = description.height;
uint32_t mip_pitch = volume_texture && description.tile_mode != Prospero::TileMode::kLinear
? description.width
: layout.pitch;
uint32_t mip_pitch = volume_texture && !linear_texture ? description.width : layout.pitch;
std::vector<vk::BufferImageCopy> regions;
regions.reserve(GetTextureRegionCount(depth, description.levels, volume_texture));
for (uint32_t i = 0; i < description.levels; i++) {
EXIT_NOT_IMPLEMENTED(layout.mips[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.mips[i].offset + slice_offset;
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i, volume_texture ? 0u : z,
1};
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
region.imageExtent = {mip_width, mip_height, 1};
const bool linear = description.tile_mode == Prospero::TileMode::kLinear;
if (linear) {
region.bufferRowLength = layout.mips[i].row_length;
region.bufferImageHeight = layout.mips[i].image_height;
} else {
const auto texel_width = layout.surface.texture.texel_width;
const auto align = [](uint32_t value, uint32_t block) {
return ((value + block - 1u) / block) * block;
};
const auto pitch = align(mip_pitch, texel_width);
region.bufferRowLength = pitch > align(mip_width, texel_width) ? pitch : 0;
}
for (uint32_t mip_level = 0; mip_level < description.levels; ++mip_level) {
EXIT_NOT_IMPLEMENTED(layout.mips[mip_level].size == 0);
const auto mip_depth = GetTextureLevelDepth(depth, mip_level, volume_texture);
vk::BufferImageCopy region {};
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, mip_level, 0, 1};
region.imageExtent = {mip_width, mip_height, 1};
if (linear_texture) {
region.bufferRowLength = layout.mips[mip_level].row_length;
region.bufferImageHeight = layout.mips[mip_level].image_height;
} else {
const auto texel_width = layout.surface.texture.texel_width;
const auto aligned_pitch = Common::AlignUp(mip_pitch, texel_width);
region.bufferRowLength =
aligned_pitch > Common::AlignUp(mip_width, texel_width) ? aligned_pitch : 0;
}
for (uint32_t slice_index = 0; slice_index < mip_depth; ++slice_index) {
region.bufferOffset = layout.mips[mip_level].offset + slice_index * layout.slice_stride;
region.imageSubresource.baseArrayLayer = volume_texture ? 0u : slice_index;
region.imageOffset.z = volume_texture ? static_cast<int>(slice_index) : 0;
regions.push_back(region);
}
@@ -323,17 +287,9 @@ std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayo
return regions;
}
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 tiled_size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t levels,
std::vector<GpuTileInfo>& out_infos) {
std::vector<GpuTileInfo>& out_tile_infos) {
const auto& description = layout.surface.description;
const bool volume_texture = description.dimension == TileSurfaceDimension::Dim3D;
const auto depth = volume_texture ? description.depth : description.layers;
@@ -351,111 +307,69 @@ bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferI
return false;
}
std::vector<GpuTileInfo> infos;
infos.reserve(regions.size());
if (volume_texture) {
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 >= surface.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) * surface.block_slice_size +
surface.mips[level].offset;
const uint64_t linear_span =
static_cast<uint64_t>(copy_depth - 1u) * linear_stride +
layout.mips[level].size;
if (!SetGpuTileSize(info.linear_offset, linear_span, UINT64_MAX,
info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, surface.mips[level].size, tiled_size,
info.tiled_size)) {
return false;
}
info.linear_slice_stride = linear_stride;
info.width = std::max((region.imageExtent.width + texture.texel_width - 1u) /
texture.texel_width,
1u);
info.height = std::max(
(logical_height + texture.texel_height - 1u) / texture.texel_height, 1u);
info.depth = copy_depth;
info.surface_z =
std::vector<GpuTileInfo> tile_infos;
tile_infos.reserve(regions.size());
const auto slices_per_copy = volume_texture ? block.block_depth : 1u;
size_t region_base = 0;
for (uint32_t mip_level = 0; mip_level < levels; ++mip_level) {
const auto& mip = surface.mips[mip_level];
const auto mip_depth = GetTextureLevelDepth(depth, mip_level, volume_texture);
for (uint32_t slice_index = 0; slice_index < mip_depth; slice_index += slices_per_copy) {
const auto& region = regions[region_base + slice_index];
GpuTileInfo tile_info {};
tile_info.family = block.family;
tile_info.bytes_per_element = block.bytes_per_element;
tile_info.linear_offset = region.bufferOffset;
tile_info.linear_size = layout.mips[mip_level].size;
tile_info.tiled_size = mip.size;
if (volume_texture) {
tile_info.depth = std::min(slices_per_copy, mip_depth - slice_index);
tile_info.linear_slice_stride = layout.slice_stride;
tile_info.linear_size += (tile_info.depth - 1u) * layout.slice_stride;
tile_info.tiled_offset =
mip.offset +
static_cast<uint64_t>(slice_index / slices_per_copy) * surface.block_slice_size;
tile_info.surface_z =
block.block_depth == 1 ? static_cast<uint32_t>(region.imageOffset.z) : 0;
info.pitch = std::max((pitch + texture.texel_width - 1u) / texture.texel_width, 1u);
info.tail_x = tail ? surface.mips[level].tail_x : 0;
info.tail_y = tail ? surface.mips[level].tail_y : 0;
info.tail = tail;
info.tiled_width = surface.mips[level].padded_width;
info.tiled_height = surface.mips[level].padded_height;
infos.push_back(info);
}
region_base += mip_depth;
}
} else {
size_t region_index = 0;
for (uint32_t level = 0; level < levels; level++) {
const auto& level_size = layout.mips[level];
const auto& mip = surface.mips[level];
const bool tail = level >= surface.first_tail_level;
const auto level_depth = GetTextureLevelDepth(depth, level, false);
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;
} else {
const auto source_stride = layout.source_slice_stride != 0
? layout.source_slice_stride
: surface.block_slice_size;
if (z > (UINT64_MAX - mip.offset) / source_stride) {
if (slice_index > (UINT64_MAX - mip.offset) / source_stride) {
return false;
}
info.tiled_offset = mip.offset + static_cast<uint64_t>(z) * source_stride;
if (!SetGpuTileSize(info.linear_offset, level_size.size, UINT64_MAX,
info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, mip.size, tiled_size, info.tiled_size)) {
return false;
}
info.width = std::max((region.imageExtent.width + texture.texel_width - 1u) /
texture.texel_width,
1u);
info.height = std::max(
(logical_height + texture.texel_height - 1u) / texture.texel_height, 1u);
info.surface_z = block.family == TileBlockFamily::RenderTarget64KB ||
block.family == TileBlockFamily::Depth64KB
? region.imageSubresource.baseArrayLayer
: 0;
info.pitch = std::max((pitch + texture.texel_width - 1u) / texture.texel_width, 1u);
info.tail = tail;
info.tail_x = tail ? mip.tail_x : 0;
info.tail_y = tail ? mip.tail_y : 0;
info.tiled_width = mip.padded_width;
info.tiled_height = mip.padded_height;
infos.push_back(info);
tile_info.tiled_offset =
mip.offset + static_cast<uint64_t>(slice_index) * source_stride;
tile_info.surface_z = block.family == TileBlockFamily::RenderTarget64KB ||
block.family == TileBlockFamily::Depth64KB
? region.imageSubresource.baseArrayLayer
: 0;
}
if (!FitsBufferRange(tile_info.linear_offset, tile_info.linear_size, UINT64_MAX) ||
!FitsBufferRange(tile_info.tiled_offset, tile_info.tiled_size, tiled_size)) {
return false;
}
const auto row_length =
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
const auto image_height = region.bufferImageHeight != 0 ? region.bufferImageHeight
: region.imageExtent.height;
tile_info.width = std::max(
(region.imageExtent.width + texture.texel_width - 1u) / texture.texel_width, 1u);
tile_info.height =
std::max((image_height + texture.texel_height - 1u) / texture.texel_height, 1u);
tile_info.pitch =
std::max((row_length + texture.texel_width - 1u) / texture.texel_width, 1u);
tile_info.tail = mip_level >= surface.first_tail_level;
tile_info.tail_x = tile_info.tail ? mip.tail_x : 0;
tile_info.tail_y = tile_info.tail ? mip.tail_y : 0;
tile_info.tiled_width = mip.padded_width;
tile_info.tiled_height = mip.padded_height;
tile_infos.push_back(tile_info);
}
region_base += mip_depth;
}
if (infos.empty()) {
return false;
}
out_infos = std::move(infos);
out_tile_infos = std::move(tile_infos);
return true;
}
@@ -1,9 +1,7 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_HOST_GPU_RENDERER_IMAGE_TEXTURECOMMON_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/vulkanCommon.h"
@@ -20,12 +18,9 @@ struct RenderTargetFormatInfo {
};
struct SurfaceFormatInfo {
explicit constexpr SurfaceFormatInfo(vk::Format format,
Prospero::BufferFormat conversion_format)
: vk_format(format), conversion_format(conversion_format) {}
vk::Format vk_format;
Prospero::BufferFormat conversion_format;
vk::Format vk_format;
Prospero::BufferFormat conversion_format;
Prospero::ColorComponentMapping host_to_storage;
};
struct TextureUploadMipLayout {
@@ -43,20 +38,21 @@ struct TextureUploadLayout {
TextureUploadMipLayout mips[16] = {};
};
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle,
Prospero::ColorComponentMapping host_to_storage);
SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format);
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
Prospero::ChannelType type,
Prospero::ChannelOrder order);
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
uint32_t height, uint32_t levels, uint32_t depth,
Prospero::TileMode tile, uint64_t upload_size,
Prospero::TileMode tile_mode, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner);
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout);
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t levels,
std::vector<GpuTileInfo>& infos);
std::vector<GpuTileInfo>& out_tile_infos);
} // namespace Libs::Graphics
+17 -37
View File
@@ -1,5 +1,6 @@
#include "graphics/host_gpu/renderer/image/tiler.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "gpu_tiler_shaders/gpu_tiler_demote_d16_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_depth_spv.h"
@@ -40,7 +41,6 @@ TileManager::TileManager(GraphicContext& graphics, CommandScheduler& scheduler,
nullptr};
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
descriptor_info.bindingCount = static_cast<uint32_t>(bindings.size());
descriptor_info.pBindings = bindings.data();
@@ -50,7 +50,6 @@ TileManager::TileManager(GraphicContext& graphics, CommandScheduler& scheduler,
const vk::PushConstantRange push_range {vk::ShaderStageFlagBits::eCompute, 0, sizeof(Push)};
vk::PipelineLayoutCreateInfo layout_info {};
layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
layout_info.setLayoutCount = 1;
layout_info.pSetLayouts = &m_descriptor_layout;
layout_info.pushConstantRangeCount = 1;
@@ -92,11 +91,9 @@ TileManager::~TileManager() {
TileManager::Scratch TileManager::AllocateScratch(uint64_t size) {
EXIT_IF(size == 0);
vk::BufferCreateInfo create {};
create.sType = vk::StructureType::eBufferCreateInfo;
create.size = size;
create.usage = vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eTransferSrc |
vk::BufferUsageFlagBits::eTransferDst;
create.sharingMode = vk::SharingMode::eExclusive;
VmaAllocationCreateInfo allocate {};
allocate.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
@@ -225,7 +222,7 @@ void TileManager::Prepare(bool tile, uint64_t tiled_capacity, uint64_t linear_ca
const uint64_t uniform_alignment =
std::max<uint64_t>(limits.minUniformBufferOffsetAlignment, 1);
const uint64_t stride = (sizeof(Push) + uniform_alignment - 1) & ~(uniform_alignment - 1);
const uint64_t stride = Common::AlignUp<uint64_t>(sizeof(Push), uniform_alignment);
EXIT_NOT_IMPLEMENTED(dispatches.size() > UINT64_MAX / stride);
const uint64_t bytes = dispatches.size() * stride;
auto [mapped, offset] = m_stream_buffer.Map(bytes, uniform_alignment);
@@ -264,20 +261,17 @@ vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
const vk::SpecializationInfo specialization {2, entries, sizeof(values), values};
vk::ShaderModuleCreateInfo module_info {};
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
module_info.codeSize = shaders[family_index].words * sizeof(uint32_t);
module_info.pCode = shaders[family_index].code;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create TileManager shader module");
vk::PipelineShaderStageCreateInfo stage {};
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
stage.pName = "main";
stage.pSpecializationInfo = &specialization;
vk::ComputePipelineCreateInfo create {};
create.sType = vk::StructureType::eComputePipelineCreateInfo;
create.stage = stage;
create.layout = m_pipeline_layout;
const auto result =
@@ -287,19 +281,19 @@ vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
return m_pipelines[slot];
}
void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
void TileManager::Record(vk::Buffer source, uint64_t source_offset,
uint64_t source_capacity, vk::Buffer target, uint64_t target_offset,
uint64_t target_capacity, std::span<Dispatch> dispatches,
bool clear_target) {
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
const uint64_t descriptor_alignment =
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
const uint64_t source_descriptor_offset = source_offset & ~(descriptor_alignment - 1);
const uint64_t target_descriptor_offset = target_offset & ~(descriptor_alignment - 1);
const uint64_t source_descriptor_offset = Common::AlignDown(source_offset, descriptor_alignment);
const uint64_t target_descriptor_offset = Common::AlignDown(target_offset, descriptor_alignment);
const uint64_t source_base = source_offset - source_descriptor_offset;
const uint64_t target_base = target_offset - target_descriptor_offset;
const uint64_t source_range = (source_base + source_capacity + 3u) & ~uint64_t {3};
const uint64_t target_range = (target_base + target_capacity + 3u) & ~uint64_t {3};
const uint64_t source_range = Common::AlignUp(source_base + source_capacity, 4);
const uint64_t target_range = Common::AlignUp(target_base + target_capacity, 4);
EXIT_NOT_IMPLEMENTED(source_range > limits.maxStorageBufferRange ||
target_range > limits.maxStorageBufferRange || target_offset % 4 != 0 ||
target_capacity % 4 != 0);
@@ -307,7 +301,6 @@ void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
m_scheduler.EndRendering();
auto command = m_scheduler.Current().Handle();
vk::BufferMemoryBarrier barriers[3] {};
barriers[0].sType = vk::StructureType::eBufferMemoryBarrier;
barriers[0].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite;
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
@@ -316,7 +309,6 @@ void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
barriers[0].offset = source_offset;
barriers[0].size = source_capacity;
barriers[1] = barriers[0];
barriers[1].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite;
barriers[1].dstAccessMask =
clear_target ? vk::AccessFlagBits::eTransferWrite
: vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
@@ -352,7 +344,6 @@ void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
const vk::DescriptorBufferInfo infos[] {source_info, target_info, params_info};
std::array<vk::WriteDescriptorSet, 3> writes {};
for (uint32_t index = 0; index < writes.size(); index++) {
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
writes[index].dstBinding = index;
writes[index].descriptorCount = 1;
writes[index].descriptorType = index == 2 ? vk::DescriptorType::eUniformBuffer
@@ -382,9 +373,9 @@ TileManager::Result TileManager::Detile(vk::Buffer tiled, uint64_t tiled_offset,
const uint64_t source_base = tiled_offset & (descriptor_alignment - 1);
std::vector<Dispatch> dispatches;
Prepare(false, tiled_capacity, linear_capacity, infos, source_base, 0, dispatches);
auto scratch = AllocateScratch((linear_capacity + 3u) & ~uint64_t {3});
auto scratch = AllocateScratch(Common::AlignUp(linear_capacity, 4));
DeferDestroy(scratch);
Record(false, tiled, tiled_offset, tiled_capacity, scratch.buffer, 0, scratch.size, dispatches,
Record(tiled, tiled_offset, tiled_capacity, scratch.buffer, 0, scratch.size, dispatches,
true);
return {scratch.buffer, 0, linear_capacity};
}
@@ -399,7 +390,7 @@ void TileManager::Tile(vk::Buffer linear, uint64_t linear_offset, uint64_t linea
const uint64_t target_base = tiled_offset & (descriptor_alignment - 1);
std::vector<Dispatch> dispatches;
Prepare(true, tiled_capacity, linear_capacity, infos, source_base, target_base, dispatches);
Record(true, linear, linear_offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
Record(linear, linear_offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
dispatches, false);
}
@@ -416,19 +407,19 @@ void TileManager::TileImage(Image& image, std::span<const vk::BufferImageCopy> r
// Reserve all stream parameters before creating a scheduler-lived scratch dependency:
// StreamBuffer::Map is allowed to submit the current tick when it wraps.
Prepare(true, tiled_capacity, linear_capacity, infos, 0, target_base, dispatches);
auto linear = AllocateScratch((linear_capacity + 3u) & ~uint64_t {3});
auto linear = AllocateScratch(Common::AlignUp(linear_capacity, 4));
DeferDestroy(linear);
image.Download(regions, linear.buffer, 0, linear.size);
Result source {linear.buffer, 0, linear.size};
if (transform == ColorTransform::SwapBgra16) {
source = SwapBgra16(source);
}
Record(true, source.buffer, source.offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
Record(source.buffer, source.offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
dispatches, false);
}
TileManager::Result TileManager::GetScratchBuffer(uint64_t size) {
auto scratch = AllocateScratch((size + 3u) & ~uint64_t {3});
auto scratch = AllocateScratch(Common::AlignUp(size, 4));
DeferDestroy(scratch);
return {scratch.buffer, 0, scratch.size};
}
@@ -436,11 +427,11 @@ TileManager::Result TileManager::GetScratchBuffer(uint64_t size) {
TileManager::StorageBinding TileManager::BindStorage(Result buffer, uint64_t size) const {
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
const auto alignment = std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
const auto descriptor_offset = buffer.offset - buffer.offset % alignment;
const auto descriptor_offset = Common::AlignDown(buffer.offset, alignment);
const auto base = buffer.offset - descriptor_offset;
EXIT_IF(buffer.buffer == nullptr || size == 0 || buffer.size < size || base > UINT32_MAX ||
size > UINT64_MAX - base || base + size > UINT64_MAX - 3);
const auto range = (base + size + 3) & ~uint64_t {3};
const auto range = Common::AlignUp(base + size, 4);
EXIT_IF(range > limits.maxStorageBufferRange || range > UINT32_MAX);
return {{buffer.buffer, descriptor_offset, range}, static_cast<uint32_t>(base)};
}
@@ -482,20 +473,17 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
words = std::size(GPU_TILER_DEMOTE_D16_SPV);
}
vk::ShaderModuleCreateInfo module_info {};
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
module_info.codeSize = words * sizeof(uint32_t);
module_info.pCode = code;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create D16 conversion shader module");
vk::PipelineShaderStageCreateInfo stage {};
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
stage.pName = "main";
stage.pSpecializationInfo = &specialization;
vk::ComputePipelineCreateInfo create {};
create.sType = vk::StructureType::eComputePipelineCreateInfo;
create.stage = stage;
create.layout = m_pipeline_layout;
const auto result =
@@ -525,14 +513,13 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
const auto target_required = required(layout.height, layout.layers, layout.target_row_stride,
layout.target_slice_stride, target_active);
EXIT_IF(source_required > UINT64_MAX - 3 || target_required > UINT64_MAX - 3);
const auto source_barrier_size = (source_required + 3) & ~uint64_t {3};
const auto target_barrier_size = (target_required + 3) & ~uint64_t {3};
const auto source_barrier_size = Common::AlignUp(source_required, 4);
const auto target_barrier_size = Common::AlignUp(target_required, 4);
EXIT_IF(source.size < source_barrier_size || target.size < target_barrier_size);
m_scheduler.EndRendering();
auto command = m_scheduler.Current().Handle();
vk::BufferMemoryBarrier barriers[2] {};
barriers[0].sType = vk::StructureType::eBufferMemoryBarrier;
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[0].buffer = source.buffer;
@@ -542,7 +529,6 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
vk::AccessFlagBits::eTransferWrite |
vk::AccessFlagBits::eShaderWrite;
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
barriers[1].sType = vk::StructureType::eBufferMemoryBarrier;
barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[1].buffer = target.buffer;
@@ -596,7 +582,6 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
};
std::array<vk::WriteDescriptorSet, 2> writes {};
for (uint32_t index = 0; index < writes.size(); index++) {
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
writes[index].dstBinding = index;
writes[index].descriptorCount = 1;
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
@@ -627,19 +612,16 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
if (m_swap_bgra16 == nullptr) {
vk::ShaderModuleCreateInfo module_info {};
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
module_info.codeSize = std::size(GPU_TILER_SWAP_BGRA16_SPV) * sizeof(uint32_t);
module_info.pCode = GPU_TILER_SWAP_BGRA16_SPV;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create BGRA16 swap shader module");
vk::PipelineShaderStageCreateInfo stage {};
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
stage.pName = "main";
vk::ComputePipelineCreateInfo create {};
create.sType = vk::StructureType::eComputePipelineCreateInfo;
create.stage = stage;
create.layout = m_pipeline_layout;
const auto result =
@@ -658,7 +640,6 @@ void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
};
std::array<vk::WriteDescriptorSet, 2> writes {};
for (uint32_t index = 0; index < writes.size(); index++) {
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
writes[index].dstBinding = index;
writes[index].descriptorCount = 1;
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
@@ -666,7 +647,6 @@ void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
}
vk::BufferMemoryBarrier barriers[2] {};
for (uint32_t index = 0; index < 2; index++) {
barriers[index].sType = vk::StructureType::eBufferMemoryBarrier;
barriers[index].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[index].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[index].buffer = infos[index].buffer;
+1 -1
View File
@@ -125,7 +125,7 @@ private:
void Prepare(bool tile, uint64_t tiled_capacity, uint64_t linear_capacity,
std::span<const GpuTileInfo> infos, uint64_t source_base, uint64_t target_base,
std::vector<Dispatch>& dispatches);
void Record(bool tile, vk::Buffer source, uint64_t source_offset, uint64_t source_capacity,
void Record(vk::Buffer source, uint64_t source_offset, uint64_t source_capacity,
vk::Buffer target, uint64_t target_offset, uint64_t target_capacity,
std::span<Dispatch> dispatches, bool clear_target);
[[nodiscard]] vk::Pipeline GetPipeline(uint32_t slot);
@@ -7,12 +7,10 @@ namespace Libs::Graphics {
MasterSemaphore::MasterSemaphore(GraphicContext& graphics): m_graphics(graphics) {
vk::SemaphoreTypeCreateInfo type_info {};
type_info.sType = vk::StructureType::eSemaphoreTypeCreateInfo;
type_info.semaphoreType = vk::SemaphoreType::eTimeline;
type_info.initialValue = 0;
vk::SemaphoreCreateInfo create_info {};
create_info.sType = vk::StructureType::eSemaphoreCreateInfo;
create_info.pNext = &type_info;
const auto result = m_graphics.device.createSemaphore(&create_info, nullptr, &m_semaphore);
@@ -47,7 +45,6 @@ void MasterSemaphore::Wait(uint64_t tick) {
}
vk::SemaphoreWaitInfo wait_info {};
wait_info.sType = vk::StructureType::eSemaphoreWaitInfo;
wait_info.semaphoreCount = 1;
wait_info.pSemaphores = &m_semaphore;
wait_info.pValues = &tick;
@@ -20,7 +20,7 @@ constexpr std::array DescriptorPoolSizes = {
DescriptorHeap::DescriptorHeap(GraphicContext& graphics, MasterSemaphore& master_semaphore)
: m_graphics(graphics), m_master_semaphore(master_semaphore) {
CreatePool();
CreateDescriptorPool();
}
DescriptorHeap::~DescriptorHeap() {
@@ -49,7 +49,7 @@ vk::DescriptorSet DescriptorHeap::Commit(vk::DescriptorSetLayout layout) {
m_pending_pools.pop_front();
EXIT_IF(m_graphics.device.resetDescriptorPool(m_current_pool, {}) != vk::Result::eSuccess);
} else {
CreatePool();
CreateDescriptorPool();
}
m_sets.clear();
@@ -63,7 +63,6 @@ bool DescriptorHeap::Allocate(vk::DescriptorSetLayout layout, Batch& batch) {
layouts.fill(layout);
vk::DescriptorSetAllocateInfo allocate {};
allocate.sType = vk::StructureType::eDescriptorSetAllocateInfo;
allocate.descriptorPool = m_current_pool;
allocate.pSetLayouts = layouts.data();
@@ -83,10 +82,8 @@ bool DescriptorHeap::Allocate(vk::DescriptorSetLayout layout, Batch& batch) {
}
}
void DescriptorHeap::CreatePool() {
void DescriptorHeap::CreateDescriptorPool() {
vk::DescriptorPoolCreateInfo create {};
create.sType = vk::StructureType::eDescriptorPoolCreateInfo;
create.flags = {};
create.maxSets = DescriptorHeapCount;
create.poolSizeCount = static_cast<uint32_t>(DescriptorPoolSizes.size());
create.pPoolSizes = DescriptorPoolSizes.data();
@@ -31,7 +31,7 @@ private:
};
[[nodiscard]] bool Allocate(vk::DescriptorSetLayout layout, Batch& batch);
void CreatePool();
void CreateDescriptorPool();
GraphicContext& m_graphics;
MasterSemaphore& m_master_semaphore;
File diff suppressed because it is too large. Load diff
@@ -16,15 +16,7 @@
namespace Libs::Graphics {
namespace ShaderRecompiler::IR {
struct ResourceSnapshot;
}
struct BufferView {
vk::Buffer buffer = nullptr;
vk::DeviceSize offset = 0;
vk::DeviceSize range = VK_WHOLE_SIZE;
};
struct ShaderStageRuntime;
struct TextureBinding {
ImageId image_id;
@@ -34,23 +26,25 @@ struct TextureBinding {
std::vector<vk::ImageView> mip_views;
};
struct NativeDescriptors {
std::vector<BufferView> buffers;
std::vector<TextureBinding> images;
std::vector<vk::Sampler> samplers;
BufferView gds;
BufferView flattened_srt;
BufferView user_data;
};
struct PreparedBindings {
const ShaderRecompiler::IR::Program* program = nullptr;
const ShaderRecompiler::IR::ResourceSnapshot* snapshot = nullptr;
NativeDescriptors resources;
std::vector<BufferId> buffer_ids;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
bool committed = false;
struct BufferSource {
uint64_t address = 0;
uint64_t size = 0;
BufferId id;
};
// The draw owns the immutable compiled-program/runtime-snapshot association through commit.
const ShaderStageRuntime* runtime = nullptr;
// Keep the resolved guest range through cache preparation; only the host buffer ID may
// become stale and need resolving again when bindings are rebound.
std::vector<BufferSource> buffer_sources;
std::vector<vk::DescriptorBufferInfo> buffers;
std::vector<TextureBinding> images;
std::vector<vk::Sampler> samplers;
vk::DescriptorBufferInfo gds {nullptr, 0, VK_WHOLE_SIZE};
vk::DescriptorBufferInfo flattened_srt;
vk::DescriptorBufferInfo shader_data_buffer;
std::vector<uint32_t> shader_data;
};
[[nodiscard]] vk::DescriptorType
@@ -70,23 +64,8 @@ template <typename T>
return result;
}
struct TargetTextureViewInfo {
vk::ImageViewType type = static_cast<vk::ImageViewType>(VK_IMAGE_VIEW_TYPE_MAX_ENUM);
uint32_t base_layer = 0;
uint32_t layer_count = 0;
};
[[nodiscard]] TargetTextureViewInfo
ResolveTargetTextureView(const ShaderRecompiler::IR::ImageResource& resource,
Prospero::ImageType type, uint32_t base_layer, uint32_t image_layers);
[[nodiscard]] bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
const Image& image, bool r128 = false);
[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor,
const Image& image, bool r128 = false);
[[nodiscard]] bool
IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, const Image& image);
bool r128 = false);
void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, uint64_t size);
@@ -1,6 +1,7 @@
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "common/assert.h"
#include "common/emulatorConfig.h"
#include "common/file.h"
#include "common/logging/log.h"
#include "common/profiler.h"
@@ -11,28 +12,54 @@
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/recompiler/ShaderRecompiler.h"
#include "graphics/shader/shaderCompiler.h"
#include "kernel/memory.h"
#include "kytyGitVersion.h"
#include "loader/systemContent.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <fmt/format.h>
#include <limits>
#include <span>
#include <spirv-tools/libspirv.hpp>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include <fmt/format.h>
#include <xxhash.h>
namespace Libs::Graphics {
namespace {
vk::PolygonMode ResolvePolygonMode(const HW::ModeControl& mode, bool cull_front, bool cull_back) {
// CxPrimitiveSetup::PolygonMode disables both per-face modes when it is zero.
if (mode.poly_mode == 0) {
return vk::PolygonMode::eFill;
}
EXIT_NOT_IMPLEMENTED(mode.poly_mode != 1);
if (cull_front && cull_back) {
return vk::PolygonMode::eFill;
}
if (!cull_front && !cull_back && mode.polymode_front_ptype != mode.polymode_back_ptype) {
EXIT("Pipeline: different polygon modes for two visible faces are unsupported\n");
}
// Vulkan has one polygon mode. A culled face does not constrain that mode.
const auto polygon_mode = cull_front ? mode.polymode_back_ptype : mode.polymode_front_ptype;
switch (polygon_mode) {
case 0: return vk::PolygonMode::ePoint;
case 1: return vk::PolygonMode::eLine;
case 2: return vk::PolygonMode::eFill;
default: EXIT("Pipeline: invalid polygon mode %u\n", polygon_mode);
}
}
std::string DriverCacheSignature(const vk::PhysicalDeviceProperties& properties) {
constexpr char hex[] = "0123456789abcdef";
std::string uuid(VK_UUID_SIZE * 2, '0');
@@ -70,97 +97,299 @@ void PipelineCacheLog(fmt::format_string<Args...> format, Args&&... args) {
Log::Flush();
}
void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_params) {
static_params.viewport_scale[0] = 0.5f;
static_params.viewport_scale[1] = 0.5f;
static_params.viewport_scale[2] = 1.0f;
static_params.viewport_offset[0] = 0.5f;
static_params.viewport_offset[1] = 0.5f;
static_params.viewport_offset[2] = 0.0f;
bool ReadShaderGuestMemory(void*, uint64_t address, uint32_t* value) {
return value != nullptr &&
Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, value, sizeof(*value));
}
static_params.scissor_ltrb[0] = 0;
static_params.scissor_ltrb[1] = 0;
static_params.scissor_ltrb[2] = 1;
static_params.scissor_ltrb[3] = 1;
void DumpShaderSpirv(const char* stage_name, uint64_t shader_hash,
const std::vector<uint32_t>& spirv) {
if (!Config::GraphicsDebugDumpEnabled()) {
return;
}
static std::atomic_int id = 0;
const auto path = Config::GetShaderLogFolder() / fmt::format("{:04d}_new_shader_{}_{:016x}.spv",
id++, stage_name, shader_hash);
Common::File::CreateDirectories(path.parent_path());
Common::File file(path);
if (file.IsInvalid()) {
const auto path_text = Common::PathToString(path);
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", path_text.c_str());
return;
}
file.Write(spirv.data(), spirv.size() * sizeof(uint32_t));
}
void DumpShaderOriginal(const char* stage_name, uint64_t shader_hash,
std::span<const uint32_t> code, const std::string& decoded_dump) {
if (!Config::GraphicsDebugDumpEnabled()) {
return;
}
EXIT_IF(code.empty());
static std::atomic_int id = 0;
const auto base = Config::GetShaderLogFolder() / "original" /
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, stage_name, shader_hash);
Common::File::CreateDirectories(base.parent_path());
for (const auto& [suffix, data, size]: {
std::tuple {".bin", static_cast<const void*>(code.data()), code.size_bytes()},
std::tuple {".rdna2", static_cast<const void*>(decoded_dump.data()),
decoded_dump.size()},
}) {
if (size == 0) {
continue;
}
auto path = base;
path += suffix;
Common::File file(path);
if (file.IsInvalid()) {
const auto path_text = Common::PathToString(path);
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", path_text.c_str());
} else {
file.Write(data, size);
}
}
}
bool ValidateShaderSpirv(const char* label, uint64_t shader_hash,
const std::vector<uint32_t>& spirv) {
if (!Config::ShaderValidationEnabled()) {
return true;
}
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_3);
std::string messages;
tools.SetMessageConsumer([&messages](spv_message_level_t, const char*,
const spv_position_t& position, const char* message) {
messages += fmt::format("{}: {} ({}) {}\n", static_cast<int>(position.line),
static_cast<int>(position.column), static_cast<int>(position.index),
message);
});
if (tools.Validate(spirv)) {
return true;
}
spvtools::SpirvTools disassembler(SPV_ENV_VULKAN_1_2);
std::string text;
disassembler.Disassemble(spirv, &text,
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER) |
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) |
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COMMENT) |
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_INDENT) |
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COLOR));
LOGF_COLOR(Log::Color::BrightRed, "%s SPIR-V validation failed hash=0x%016" PRIx64 ":\n%s",
label, shader_hash, messages.c_str());
LOGF("%s\n", text.c_str());
return false;
}
} // namespace
struct PipelineCache::ProgramCache {
struct SourceKey {
ShaderType stage = ShaderType::Unknown;
uint64_t hash = 0;
struct ProgramKey {
ShaderType stage = ShaderType::Unknown;
uint64_t hash = 0;
uint32_t user_data_count = 0;
uint32_t code_size = 0;
std::vector<uint32_t> static_state;
bool operator==(const SourceKey&) const = default;
bool operator==(const ProgramKey&) const = default;
};
struct Permutation {
std::vector<uint32_t> static_key;
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
ShaderProgram handle;
ShaderRecompiler::IR::ResourceSpecialization specialization;
ShaderRecompiler::IR::CompiledShaderInfo program;
ShaderProgram handle;
};
struct SourceKeyHash {
std::size_t operator()(const SourceKey& key) const {
struct SourceEntry {
explicit SourceEntry(ShaderRecompiler::IR::ResourcePlan plan)
: resource_plan(std::move(plan)) {
permutations.reserve(8);
}
ShaderRecompiler::IR::ResourcePlan resource_plan;
std::vector<Permutation> permutations;
};
struct ProgramKeyHash {
std::size_t operator()(const ProgramKey& key) const {
std::size_t hash = static_cast<std::size_t>(key.stage);
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash));
if constexpr (sizeof(std::size_t) < sizeof(uint64_t)) {
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash >> 32u));
}
PipelineKeyHash::Mix(hash, key.user_data_count);
PipelineKeyHash::Mix(hash, key.code_size);
PipelineKeyHash::Mix(hash, key.static_state.size());
// Bucket same-shape static variants by source. ProgramKey equality performs the one
// exact state comparison needed on a stable hit without hashing up to 429 words first.
return hash;
}
};
static uint64_t MixId(uint64_t hash, uint64_t value) {
return hash ^ (value + 0x9e3779b97f4a7c15ull + (hash << 6u) + (hash >> 2u));
static constexpr std::size_t MaxStaticKeyWords = 13 + ShaderVertexInputInfo::RES_MAX * 13;
Permutation CompilePermutation(const ShaderParams& params,
const ShaderRecompiler::CompileOptions& options,
ShaderRecompiler::TranslateResult translated,
ShaderRecompiler::IR::ResourceSpecialization specialization,
uint32_t push_data_start_dword) {
const char* stage_name = nullptr;
switch (options.stage) {
case ShaderType::Vertex: stage_name = "vs"; break;
case ShaderType::Mesh: stage_name = "ms"; break;
case ShaderType::Pixel: stage_name = "ps"; break;
case ShaderType::Compute: stage_name = "cs"; break;
default: EXIT("invalid pipeline shader stage\n");
}
auto result = ShaderRecompiler::CompileProgram(std::move(translated), options,
specialization, push_data_start_dword);
DumpShaderOriginal(stage_name, options.shader_hash, params.code, result.decoded_dump);
if (!ValidateShaderSpirv(options.dump_label, options.shader_hash, result.spirv)) {
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
EXIT("%s failed hash=0x%016" PRIx64 ": SPIR-V validation failed\n", options.dump_label,
options.shader_hash);
}
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
vk::ShaderModuleCreateInfo create_info {};
create_info.codeSize = result.spirv.size() * sizeof(uint32_t);
create_info.pCode = result.spirv.data();
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(device.createShaderModule(&create_info, nullptr, &module),
"create recompiled shader module");
EXIT_IF(module == nullptr);
if (options.dump_ir) {
LOGF("%s SPIR-V words=%" PRIu64 " wave_size=%u\n", options.dump_label,
static_cast<uint64_t>(result.spirv.size()), options.wave_size);
}
return {
.specialization = std::move(specialization),
.program = std::move(result.program).TakeCompiledInfo(),
.handle = {.id = ++next_shader_id, .module = module},
};
}
static constexpr std::size_t MaxStaticKeyWords =
5 + ShaderVertexInputInfo::RES_MAX * 17;
template <typename InputInfo>
ShaderProgram Get(const ShaderParams& params, InputInfo& input_info) {
constexpr ShaderType stage = [] {
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
return ShaderType::Vertex;
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
return ShaderType::Pixel;
} else {
static_assert(std::is_same_v<InputInfo, ShaderComputeInputInfo>);
return ShaderType::Compute;
}
}();
ShaderProgram Get(const ShaderParams& params, InputInfo& input_info,
uint32_t& push_data_cursor) {
ShaderType stage;
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
stage = input_info.mesh.threads_num[0] != 0 ? ShaderType::Mesh : ShaderType::Vertex;
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
stage = ShaderType::Pixel;
} else {
static_assert(std::is_same_v<InputInfo, ShaderComputeInputInfo>);
stage = ShaderType::Compute;
}
BuildStageStaticKey(input_info, key_scratch);
auto& permutations = programs[{stage, params.hash}];
for (const auto& permutation: permutations) {
if (permutation.static_key == key_scratch &&
MaterializeProgram(permutation.program, params, input_info)) {
return permutation.handle;
lookup_key.stage = stage;
lookup_key.hash = params.hash;
lookup_key.user_data_count = static_cast<uint32_t>(params.user_data.size());
lookup_key.code_size = static_cast<uint32_t>(params.code.size());
BuildStageStaticKey(input_info, lookup_key.static_state);
auto entry = programs.find(lookup_key);
ShaderRecompiler::IR::ResourceSnapshot resources;
ShaderRecompiler::IR::ResourceSpecialization specialization;
const ShaderRecompiler::IR::SrtRuntime runtime {
.user_data = params.user_data,
.shader_base = params.Base(),
.read_specialization_memory = ReadShaderGuestMemory,
};
if (entry != programs.end()) {
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(
entry->second.resource_plan, runtime, resources, specialization));
if (const auto permutation = std::ranges::find_if(
entry->second.permutations, [&](const Permutation& candidate) {
const auto& layout = candidate.program.bindings;
return layout.push_data_start_dword ==
ShaderRecompiler::IR::PushData::StartFor(
push_data_cursor, layout.ShaderDataDwords()) &&
candidate.specialization == specialization;
});
permutation != entry->second.permutations.end()) {
input_info.stage = {.program = &permutation->program,
.resources = std::move(resources)};
permutation->program.bindings.AdvancePushData(push_data_cursor);
return permutation->handle;
}
}
const auto module = CompileProgram(device, params, input_info);
EXIT_IF(module == nullptr || !input_info.stage);
uint64_t id = MixId(MixId(params.hash, static_cast<uint64_t>(stage)), permutations.size());
if (id == 0) {
id = 1;
ShaderStageInputInfo stage_input {};
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
stage_input.vertex = &input_info;
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
stage_input.pixel = &input_info;
} else {
stage_input.compute = &input_info;
}
const ShaderProgram handle {.id = id, .module = module};
permutations.push_back({key_scratch, input_info.stage.program, handle});
const char* label = nullptr;
switch (stage) {
case ShaderType::Vertex: label = "ShaderRecompiler VS"; break;
case ShaderType::Mesh: label = "ShaderRecompiler MS"; break;
case ShaderType::Pixel: label = "ShaderRecompiler PS"; break;
case ShaderType::Compute: label = "ShaderRecompiler CS"; break;
default: EXIT("invalid pipeline shader stage\n");
}
ShaderRecompiler::CompileOptions options;
options.stage = stage;
options.shader_hash = params.hash;
options.user_data = params.user_data;
options.back_code = params.back_code;
options.dump_ir = Config::GetShaderLogDirection() != Config::LogDirection::Silent;
options.early_dump = options.dump_ir;
options.dump_label = label;
options.input_info = stage_input;
std::printf("Num compiled %u shaders\n", ++num_compiled);
return handle;
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
options.user_data_base = 8;
if (stage == ShaderType::Mesh) {
options.user_data_base = 0;
options.wave_size = input_info.mesh.wave_size;
}
} else if constexpr (std::is_same_v<InputInfo, ShaderComputeInputInfo>) {
options.wave_size = input_info.wave_size;
}
auto translated = ShaderRecompiler::TranslateProgram(params.code, options);
if (entry == programs.end()) {
auto resource_plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(resource_plan, runtime, resources,
specialization));
entry = programs.try_emplace(lookup_key, std::move(resource_plan)).first;
}
entry->second.permutations.push_back(CompilePermutation(
params, options, std::move(translated), std::move(specialization), push_data_cursor));
const auto& permutation = entry->second.permutations.back();
input_info.stage = {.program = &permutation.program, .resources = std::move(resources)};
permutation.program.bindings.AdvancePushData(push_data_cursor);
std::array<size_t, static_cast<size_t>(ShaderType::Mesh) + 1> counts {};
for (const auto& [key, source]: programs) {
counts[static_cast<size_t>(key.stage)] += source.permutations.size();
}
// Guest geometry shaders are compiled through the host mesh stage.
std::printf("Shaders: VS %zu | PS %zu | CS %zu | GS %zu\n",
counts[static_cast<size_t>(ShaderType::Vertex)],
counts[static_cast<size_t>(ShaderType::Pixel)],
counts[static_cast<size_t>(ShaderType::Compute)],
counts[static_cast<size_t>(ShaderType::Mesh)]);
return permutation.handle;
}
explicit ProgramCache(vk::Device device): device(device) {
key_scratch.reserve(MaxStaticKeyWords);
lookup_key.static_state.reserve(MaxStaticKeyWords);
}
~ProgramCache() {
for (const auto& [key, entry]: programs) {
(void)key;
for (const auto& permutation: entry.permutations) {
device.destroyShaderModule(permutation.handle.module, nullptr);
}
}
}
std::unordered_map<SourceKey, std::vector<Permutation>, SourceKeyHash> programs;
std::vector<uint32_t> key_scratch;
vk::Device device;
uint32_t num_compiled = 0;
std::unordered_map<ProgramKey, SourceEntry, ProgramKeyHash> programs;
ProgramKey lookup_key;
vk::Device device;
uint64_t next_shader_id = 0;
};
PipelineCache::PipelineCache(GraphicContext& graphics)
@@ -181,12 +410,6 @@ PipelineCache::~PipelineCache() {
};
destroy(m_graphics_pipelines);
destroy(m_compute_pipelines);
for (const auto& [key, permutations]: m_program_cache->programs) {
(void)key;
for (const auto& permutation: permutations) {
m_graphics.device.destroyShaderModule(permutation.handle.module, nullptr);
}
}
if (m_driver_cache != nullptr) {
m_graphics.device.destroyPipelineCache(m_driver_cache, nullptr);
}
@@ -212,7 +435,7 @@ void PipelineCache::InitializeDriverCache() {
return;
}
m_driver_cache_path = std::filesystem::path("_PipelineCache") / (title_id + ".bin");
m_driver_cache_path = std::filesystem::path("_PipelineCache") / (title_id + ".bin");
const auto path = Common::PathToString(m_driver_cache_path);
const bool cache_exists = Common::File::IsFileExisting(m_driver_cache_path);
if (cache_exists) {
@@ -224,7 +447,7 @@ void PipelineCache::InitializeDriverCache() {
if (cache_exists) {
Common::File file(m_driver_cache_path, Common::File::Mode::Read);
const auto file_size = file.IsInvalid() ? 0 : file.Size();
const auto signature = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
const auto signature = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
if (file_size >= signature.size() + sizeof(uint64_t) &&
file_size <= std::numeric_limits<uint32_t>::max()) {
std::string cached_signature(signature.size(), '\0');
@@ -236,7 +459,8 @@ void PipelineCache::InitializeDriverCache() {
file.Read(cached_signature.data(), static_cast<uint32_t>(cached_signature.size()),
&signature_read);
file.Read(&payload_hash, sizeof(payload_hash), &hash_read);
file.Read(initial_data.data(), static_cast<uint32_t>(initial_data.size()), &payload_read);
file.Read(initial_data.data(), static_cast<uint32_t>(initial_data.size()),
&payload_read);
file.Close();
if (signature_read != cached_signature.size() || hash_read != sizeof(payload_hash) ||
payload_read != initial_data.size() || cached_signature != signature ||
@@ -248,26 +472,24 @@ void PipelineCache::InitializeDriverCache() {
}
} else {
file.Close();
PipelineCacheLog("Vulkan pipeline cache: invalidating {} (invalid file size)",
path);
PipelineCacheLog("Vulkan pipeline cache: invalidating {} (invalid file size)", path);
}
}
vk::PipelineCacheCreateInfo create {};
create.sType = vk::StructureType::ePipelineCacheCreateInfo;
create.initialDataSize = initial_data.size();
create.pInitialData = initial_data.empty() ? nullptr : initial_data.data();
auto result = m_graphics.device.createPipelineCache(&create, nullptr, &m_driver_cache);
if (result != vk::Result::eSuccess && !initial_data.empty()) {
PipelineCacheLog("Vulkan pipeline cache: driver rejected {} ({}); starting empty", path,
VulkanToString(result));
vk::to_string(result));
initial_data.clear();
create.initialDataSize = 0;
create.pInitialData = nullptr;
result = m_graphics.device.createPipelineCache(&create, nullptr, &m_driver_cache);
}
if (result != vk::Result::eSuccess) {
PipelineCacheLog("Vulkan pipeline cache: disabled ({})", VulkanToString(result));
PipelineCacheLog("Vulkan pipeline cache: disabled ({})", vk::to_string(result));
m_driver_cache = nullptr;
return;
}
@@ -304,11 +526,11 @@ void PipelineCache::Save() {
if (result != vk::Result::eSuccess || size == 0 ||
size > std::numeric_limits<uint32_t>::max()) {
PipelineCacheLog("Vulkan pipeline cache: save failed ({}, {} bytes)",
VulkanToString(result), size);
vk::to_string(result), size);
return;
}
payload.resize(size);
auto prefix = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
auto prefix = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
const auto payload_hash = XXH3_64bits(payload.data(), payload.size());
prefix.append(reinterpret_cast<const char*>(&payload_hash), sizeof(payload_hash));
if (!Common::File::CreateDirectories(m_driver_cache_path.parent_path())) {
@@ -338,38 +560,75 @@ void PipelineCache::Save() {
m_driver_cache = nullptr;
}
ShaderProgram PipelineCache::GetVertexProgram(const HW::VertexShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderVertexInputInfo& input_info) {
const auto params = PrepareProgram(regs, sh, input_info);
PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
const HW::VertexShaderInfo& vertex_regs, const HW::PixelShaderInfo& pixel_regs,
const HW::ShaderRegisters& sh, const HW::Context& context, const HW::UserConfig& user_config,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping, bool pixel_active,
ShaderVertexInputInfo& vertex_info, ShaderPixelInputInfo& pixel_info) {
const auto vertex_params = PrepareProgram(vertex_regs, context, user_config, vertex_info);
const bool mesh_active = vertex_info.mesh.threads_num[0] != 0;
if (mesh_active) {
EXIT_NOT_IMPLEMENTED(!m_graphics.mesh_shader_enabled);
auto& mesh = vertex_info.mesh;
mesh.host_subgroup_size = m_graphics.subgroup_size;
const auto& limits = m_graphics.mesh_shader_properties;
const auto logical_threads =
mesh.threads_num[0] * mesh.threads_num[1] * mesh.threads_num[2];
const auto host_threads = ((logical_threads + mesh.wave_size - 1u) / mesh.wave_size) *
std::min(mesh.host_subgroup_size, mesh.wave_size);
if (host_threads > limits.maxMeshWorkGroupInvocations ||
host_threads > limits.maxMeshWorkGroupSize[0] ||
mesh.max_vertices > limits.maxMeshOutputVertices ||
mesh.max_primitives > limits.maxMeshOutputPrimitives ||
mesh.lds_size_dwords * sizeof(uint32_t) > limits.maxMeshSharedMemorySize) {
EXIT("mesh shader exceeds host limits: threads=%u vertices=%u primitives=%u LDS=%u\n",
host_threads, mesh.max_vertices, mesh.max_primitives, mesh.lds_size_dwords);
}
}
ShaderParams pixel_params;
if (pixel_active) {
pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
}
if (context.GetClipControl().clip_disable) {
const auto& viewport = context.GetScreenViewport().viewports[0];
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
auto& clip = vertex_info.clip_space;
clip.scale[0] = viewport.xscale;
clip.scale[1] = viewport.yscale;
clip.offset[0] = viewport.xoffset;
clip.offset[1] = viewport.yoffset;
clip.half_extent[0] =
static_cast<float>(std::min(limits.maxViewportDimensions[0], 16384u)) * 0.5f;
clip.half_extent[1] =
static_cast<float>(std::min(limits.maxViewportDimensions[1], 16384u)) * 0.5f;
clip.enabled = true;
}
Common::LockGuard lock(m_mutex);
return m_program_cache->Get(params, input_info);
}
ShaderProgram PipelineCache::GetPixelProgram(
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
const ShaderVertexInputInfo& vertex_info,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
ShaderPixelInputInfo& input_info) {
const auto params = PrepareProgram(regs, sh, vertex_info, target_export_mapping, input_info);
Common::LockGuard lock(m_mutex);
return m_program_cache->Get(params, input_info);
uint32_t push_data_cursor =
mesh_active ? ShaderRecompiler::IR::PushData::MeshDrawDwordCount : 0;
GraphicsPrograms result;
if (pixel_active) {
result.pixel = m_program_cache->Get(pixel_params, pixel_info, push_data_cursor);
}
result.vertex = m_program_cache->Get(vertex_params, vertex_info, push_data_cursor);
return result;
}
ShaderProgram PipelineCache::GetComputeProgram(const HW::ComputeShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info) {
input_info.needs_lds_barriers = !m_graphics.compute_wave64_supported;
const auto params = PrepareProgram(regs, sh, input_info);
input_info.host_subgroup_size = m_graphics.SupportsComputeWave64() ? 64u : 32u;
const auto params = PrepareProgram(regs, sh, input_info);
Common::LockGuard lock(m_mutex);
return m_program_cache->Get(params, input_info);
uint32_t push_data_cursor = 0;
return m_program_cache->Get(params, input_info, push_data_cursor);
}
bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other) const noexcept {
return std::memcmp(this, &other, sizeof(*this)) == 0;
}
PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
@@ -386,51 +645,55 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
Common::LockGuard lock(m_mutex);
auto& ctx = command.GetRegisters();
const HW::BlendColor& bclr = ctx.GetBlendColor();
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < color_count; i++) {
color_mask[i] =
(colors[i].image_id ? colors[i].export_mapping.ApplyMask(render_target_mask_slot(
ctx.GetRenderTargetMask(), colors[i].target_slot))
: 0);
}
const HW::ModeControl& mc = ctx.GetModeControl();
const auto vs_id = vertex_program.id;
const auto ps_id = ps_active ? pixel_program.id : 0;
PipelineStaticParameters static_params {};
GraphicsPipeline p {};
p.ps_shader_id = ps_id;
p.vs_shader_id = vs_id;
static_params.color_count = color_count;
PipelineRenderingState rendering {};
GraphicsPipelineKey key {};
key.vs_shader_id = vs_id;
key.ps_shader_id = ps_id;
auto& static_params = key.static_params;
auto& rendering = key.rendering;
rendering.color_count = color_count;
uint32_t attachment_samples = 0;
for (uint32_t i = 0; i < color_count; i++) {
EXIT_IF(!colors[i].image_id || colors[i].format == vk::Format::eUndefined);
rendering.color_formats[i] = colors[i].format;
EXIT_IF(!colors[i].image_id || colors[i].desc.view_info.format == vk::Format::eUndefined);
static_params.color_mask[i] = colors[i].export_mapping.ApplyMask(
render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot));
rendering.color_formats[i] = colors[i].desc.view_info.format;
if (attachment_samples == 0) {
attachment_samples = colors[i].samples;
} else if (attachment_samples != colors[i].samples) {
attachment_samples = colors[i].desc.info.samples;
} else if (attachment_samples != colors[i].desc.info.samples) {
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
attachment_samples, colors[i].samples);
attachment_samples, colors[i].desc.info.samples);
}
const auto& rt = ctx.GetRenderTarget(colors[i].target_slot);
const auto& bc = ctx.GetBlendControl(colors[i].target_slot);
static_params.color_srcblend[i] = bc.color_srcblend;
static_params.color_comb_fcn[i] = bc.color_comb_fcn;
static_params.color_destblend[i] = bc.color_destblend;
static_params.alpha_srcblend[i] = bc.alpha_srcblend;
static_params.alpha_comb_fcn[i] = bc.alpha_comb_fcn;
static_params.alpha_destblend[i] = bc.alpha_destblend;
static_params.separate_alpha_blend[i] = bc.separate_alpha_blend;
static_params.blend_enable[i] = bc.enable && !rt.info.blend_bypass;
}
const bool with_depth =
depth.format != vk::Format::eUndefined && static_cast<bool>(depth.image_id);
depth.desc.view_info.format != vk::Format::eUndefined && static_cast<bool>(depth.image_id);
if (with_depth) {
const auto aspects = ImageViewOps::DepthAspectMask(depth.format);
rendering.depth_format =
aspects & vk::ImageAspectFlagBits::eDepth ? depth.format : vk::Format::eUndefined;
rendering.stencil_format =
aspects & vk::ImageAspectFlagBits::eStencil ? depth.format : vk::Format::eUndefined;
const auto aspects = ImageViewOps::DepthAspectMask(depth.desc.view_info.format);
rendering.depth_format = aspects & vk::ImageAspectFlagBits::eDepth
? depth.desc.view_info.format
: vk::Format::eUndefined;
rendering.stencil_format = aspects & vk::ImageAspectFlagBits::eStencil
? depth.desc.view_info.format
: vk::Format::eUndefined;
if (attachment_samples == 0) {
attachment_samples = depth.samples;
} else if (attachment_samples != depth.samples) {
attachment_samples = depth.desc.info.samples;
} else if (attachment_samples != depth.desc.info.samples) {
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n", attachment_samples,
depth.samples);
depth.desc.info.samples);
}
}
if (color_count == 0 && !with_depth) {
@@ -460,49 +723,46 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
if (static_params.sample_shading_enable && !m_graphics.sample_rate_shading_enabled) {
EXIT("Pipeline: sample-rate shading is required but unsupported by the host\n");
}
static_params.with_depth = with_depth;
static_params.depth_test_enable = depth.depth_test_enable;
static_params.depth_write_enable = (depth.depth_write_enable && !depth.depth_clear_enable);
static_params.depth_compare_op = depth.depth_compare_op;
static_params.depth_bounds_test_enable = depth.depth_bounds_test_enable;
static_params.depth_min_bounds = depth.depth_min_bounds;
static_params.depth_max_bounds = depth.depth_max_bounds;
static_params.stencil_test_enable = depth.stencil_test_enable;
static_params.stencil_front = depth.stencil_static_front;
static_params.stencil_back = depth.stencil_static_back;
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
static_params.color_mask[i] = color_mask[i];
}
const bool rect_list = topology == vk::PrimitiveTopology::ePatchList;
static_params.cull_back = !rect_list && mc.cull_back;
static_params.cull_front = !rect_list && mc.cull_front;
static_params.face = mc.face;
static_params.provoking_vtx_last = mc.provoking_vtx_last;
static_params.polygon_mode =
ResolvePolygonMode(mc, static_params.cull_front, static_params.cull_back);
for (uint32_t i = 0; i < color_count; i++) {
const auto& rt = ctx.GetRenderTarget(colors[i].target_slot);
const auto& bc = ctx.GetBlendControl(colors[i].target_slot);
static_params.color_srcblend[i] = bc.color_srcblend;
static_params.color_comb_fcn[i] = bc.color_comb_fcn;
static_params.color_destblend[i] = bc.color_destblend;
static_params.alpha_srcblend[i] = bc.alpha_srcblend;
static_params.alpha_comb_fcn[i] = bc.alpha_comb_fcn;
static_params.alpha_destblend[i] = bc.alpha_destblend;
static_params.separate_alpha_blend[i] = bc.separate_alpha_blend;
static_params.blend_enable[i] = bc.enable;
static_params.blend_bypass[i] = rt.info.blend_bypass;
if (vs_input_info.stage.program->stage != ShaderType::Mesh) {
EXIT_IF(vs_input_info.buffers_num < 0 ||
vs_input_info.buffers_num > ShaderVertexInputInfo::RES_MAX ||
vs_input_info.resources_num < 0 ||
vs_input_info.resources_num > ShaderVertexInputInfo::RES_MAX);
key.vertex_input.binding_count = static_cast<uint8_t>(vs_input_info.buffers_num);
key.vertex_input.attribute_count = static_cast<uint8_t>(vs_input_info.resources_num);
uint32_t attributes_num = 0;
for (int binding = 0; binding < vs_input_info.buffers_num; binding++) {
const auto& buffer = vs_input_info.buffers[binding];
EXIT_IF(buffer.attr_num < 0 || buffer.attr_num > ShaderVertexInputBuffer::ATTR_MAX);
attributes_num += static_cast<uint32_t>(buffer.attr_num);
EXIT_IF(attributes_num > static_cast<uint32_t>(vs_input_info.resources_num));
key.vertex_input.bindings[binding] = {.stride = buffer.stride,
.instance = buffer.fetch_index != 0};
for (int attribute = 0; attribute < buffer.attr_num; attribute++) {
const auto index = buffer.attr_indices[attribute];
EXIT_IF(index < 0 || index >= vs_input_info.resources_num);
key.vertex_input.attributes[index] = {
.offset = buffer.attr_offsets[attribute],
.binding = static_cast<uint8_t>(binding),
};
}
}
EXIT_IF(attributes_num != static_cast<uint32_t>(vs_input_info.resources_num));
}
static_params.blend_color_red = bclr.red;
static_params.blend_color_green = bclr.green;
static_params.blend_color_blue = bclr.blue;
static_params.blend_color_alpha = bclr.alpha;
NormalizeStaticParamsForDynamicState(static_params);
GraphicsPipelineKey key {};
key.rendering = rendering;
key.vs_shader_id = p.vs_shader_id;
key.ps_shader_id = p.ps_shader_id;
key.static_params = static_params;
if (auto iter = m_graphics_pipelines.find(key); iter != m_graphics_pipelines.end()) {
return *iter->second;
@@ -513,16 +773,16 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
if (ps_active) {
ShaderDbgDumpInputInfo(*ps_input_info);
}
LOGF("PipelineTrace: shader modules VS=%" PRIu64 " module=%p PS=%" PRIu64
" module=%p\n",
LOGF("PipelineTrace: shader modules VS=%" PRIu64 " module=%p PS=%" PRIu64 " module=%p\n",
vs_id, static_cast<void*>(vertex_program.module), ps_id,
static_cast<void*>(pixel_program.module));
}
auto cached = std::make_unique<GraphicsPipeline>(p);
auto cached = std::make_unique<Pipeline>();
LogPipelineTrace("CreatePipelineInternal begin", vs_id, ps_id);
CreatePipelineInternal(m_graphics, *cached, rendering, vs_input_info, vertex_program.module,
ps_input_info, pixel_program.module, static_params, m_driver_cache);
CreatePipelineInternal(m_graphics, *cached, rendering, key.vertex_input, vs_input_info,
vertex_program, ps_input_info, pixel_program, static_params,
m_driver_cache);
LogPipelineTrace("CreatePipelineInternal done", vs_id, ps_id);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
@@ -534,22 +794,17 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
return *iter->second;
}
PipelineCache::ComputePipeline&
PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
const ShaderProgram& compute_program) {
PipelineCache::Pipeline&
PipelineCache::GetComputePipeline(const ShaderComputeInputInfo& input_info,
const ShaderProgram& compute_program) {
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Compute)", profiler::colors::RedA100);
EXIT_IF(!compute_program);
Common::LockGuard lock(m_mutex);
ComputePipeline p {};
p.cs_shader_id = compute_program.id;
ComputePipelineKey key {};
key.cs_shader_id = p.cs_shader_id;
if (auto iter = m_compute_pipelines.find(key); iter != m_compute_pipelines.end()) {
if (auto iter = m_compute_pipelines.find(compute_program.id);
iter != m_compute_pipelines.end()) {
return *iter->second;
}
@@ -557,13 +812,13 @@ PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
ShaderDbgDumpInputInfo(input_info);
}
auto cached = std::make_unique<ComputePipeline>(p);
auto cached = std::make_unique<Pipeline>();
CreatePipelineInternal(m_graphics, *cached, input_info, compute_program.module, m_driver_cache);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
auto [iter, inserted] = m_compute_pipelines.emplace(std::move(key), std::move(cached));
auto [iter, inserted] = m_compute_pipelines.emplace(compute_program.id, std::move(cached));
EXIT_IF(!inserted);
return *iter->second;
@@ -26,36 +26,31 @@ class CommandBuffer;
namespace HW {
class Context;
class Shader;
class UserConfig;
struct ComputeShaderInfo;
} // namespace HW
#pragma pack(push, 1)
struct PipelineStaticParameters {
float viewport_scale[3] = {};
float viewport_offset[3] = {};
bool negative_one_to_one = false;
bool depth_clip_enable = true;
int scissor_ltrb[4] = {0};
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
bool primitive_restart_enable = false;
uint32_t samples = 1;
bool sample_shading_enable = false;
bool with_depth = false;
bool depth_test_enable = false;
bool depth_write_enable = false;
vk::CompareOp depth_compare_op = vk::CompareOp::eNever;
bool depth_bounds_test_enable = false;
float depth_min_bounds = 0.0f;
float depth_max_bounds = 0.0f;
bool stencil_test_enable = false;
PipelineStencilStaticState stencil_front;
PipelineStencilStaticState stencil_back;
uint32_t color_count = 1;
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool cull_front = false;
bool cull_back = false;
bool face = false;
bool provoking_vtx_last = false;
vk::PolygonMode polygon_mode = vk::PolygonMode::eFill;
uint8_t color_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint8_t color_comb_fcn[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint8_t color_destblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
@@ -64,11 +59,6 @@ struct PipelineStaticParameters {
uint8_t alpha_destblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool separate_alpha_blend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool blend_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool blend_bypass[RENDER_COLOR_ATTACHMENTS_MAX] = {};
float blend_color_red = 0.0f;
float blend_color_green = 0.0f;
float blend_color_blue = 0.0f;
float blend_color_alpha = 0.0f;
bool operator==(const PipelineStaticParameters& other) const noexcept;
};
@@ -78,14 +68,7 @@ struct PipelineStaticParameters {
static_assert(std::is_trivially_copyable_v<PipelineStaticParameters>);
static_assert(std::is_standard_layout_v<PipelineStaticParameters>);
static_assert(alignof(PipelineStaticParameters) == 1);
static_assert(sizeof(PipelineStaticParameters) ==
sizeof(float[3]) + sizeof(float[3]) + sizeof(bool) * 2 + sizeof(int[4]) +
sizeof(vk::PrimitiveTopology) + sizeof(bool) + sizeof(uint32_t) +
sizeof(bool) * 4 + sizeof(vk::CompareOp) + sizeof(bool) + sizeof(float) * 2 +
sizeof(bool) + sizeof(PipelineStencilStaticState) * 2 + sizeof(uint32_t) +
sizeof(uint32_t[RENDER_COLOR_ATTACHMENTS_MAX]) + sizeof(bool) * 3 +
sizeof(uint8_t[RENDER_COLOR_ATTACHMENTS_MAX]) * 6 +
sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) * 3 + sizeof(float) * 4);
static_assert(sizeof(PipelineStaticParameters) == 158);
struct PipelineRenderingState {
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
@@ -96,13 +79,31 @@ struct PipelineRenderingState {
bool operator==(const PipelineRenderingState&) const = default;
};
struct PipelineVertexInputState {
struct Binding {
uint32_t stride = 0;
bool instance = false;
bool operator==(const Binding&) const = default;
};
struct Attribute {
uint32_t offset = 0;
uint8_t binding = 0;
bool operator==(const Attribute&) const = default;
};
std::array<Binding, ShaderVertexInputInfo::RES_MAX> bindings {};
std::array<Attribute, ShaderVertexInputInfo::RES_MAX> attributes {};
uint8_t binding_count = 0;
uint8_t attribute_count = 0;
bool operator==(const PipelineVertexInputState&) const = default;
};
struct ShaderProgram {
uint64_t id = 0;
vk::ShaderModule module = nullptr;
explicit operator bool() const {
return id != 0 && module != nullptr;
}
explicit operator bool() const { return id != 0 && module != nullptr; }
};
class PipelineCache {
@@ -119,35 +120,30 @@ public:
bool uses_push_descriptors = false;
};
struct GraphicsPipeline: Pipeline {
uint64_t vs_shader_id = 0;
uint64_t ps_shader_id = 0;
struct GraphicsPrograms {
ShaderProgram vertex;
ShaderProgram pixel;
};
struct ComputePipeline: Pipeline {
uint64_t cs_shader_id = 0;
};
ShaderProgram GetVertexProgram(const HW::VertexShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderVertexInputInfo& input_info);
ShaderProgram
GetPixelProgram(const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
const ShaderVertexInputInfo& vertex_info,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
ShaderPixelInputInfo& input_info);
GraphicsPrograms
GetGraphicsPrograms(const HW::VertexShaderInfo& vertex_regs,
const HW::PixelShaderInfo& pixel_regs, const HW::ShaderRegisters& sh,
const HW::Context& context, const HW::UserConfig& user_config,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
bool pixel_active, ShaderVertexInputInfo& vertex_info,
ShaderPixelInputInfo& pixel_info);
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info);
const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info);
GraphicsPipeline& CreateGraphicsPipeline(
std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
bool primitive_restart_enable, const ShaderProgram& vertex_program,
const ShaderProgram& pixel_program);
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
const ShaderProgram& compute_program);
Pipeline&
GetGraphicsPipeline(std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
const ShaderPixelInputInfo* ps_input_info,
vk::PrimitiveTopology topology, bool primitive_restart_enable,
const ShaderProgram& vertex_program, const ShaderProgram& pixel_program);
Pipeline& GetComputePipeline(const ShaderComputeInputInfo& input_info,
const ShaderProgram& compute_program);
private:
struct ProgramCache;
@@ -156,19 +152,13 @@ private:
PipelineRenderingState rendering;
uint64_t vs_shader_id = 0;
uint64_t ps_shader_id = 0;
PipelineVertexInputState vertex_input;
PipelineStaticParameters static_params;
bool operator==(const GraphicsPipelineKey& other) const {
return rendering == other.rendering && vs_shader_id == other.vs_shader_id &&
ps_shader_id == other.ps_shader_id && static_params == other.static_params;
}
};
struct ComputePipelineKey {
uint64_t cs_shader_id = 0;
bool operator==(const ComputePipelineKey& other) const {
return cs_shader_id == other.cs_shader_id;
ps_shader_id == other.ps_shader_id && vertex_input == other.vertex_input &&
static_params == other.static_params;
}
};
@@ -201,28 +191,28 @@ private:
PipelineKeyHash::MixRendering(hash, key.rendering);
PipelineKeyHash::Mix(hash, key.vs_shader_id);
PipelineKeyHash::Mix(hash, key.ps_shader_id);
PipelineKeyHash::Mix(hash, key.vertex_input.binding_count);
for (uint32_t i = 0; i < key.vertex_input.binding_count; i++) {
PipelineKeyHash::Mix(hash, key.vertex_input.bindings[i].stride);
PipelineKeyHash::Mix(hash, key.vertex_input.bindings[i].instance);
}
PipelineKeyHash::Mix(hash, key.vertex_input.attribute_count);
for (uint32_t i = 0; i < key.vertex_input.attribute_count; i++) {
PipelineKeyHash::Mix(hash, key.vertex_input.attributes[i].offset);
PipelineKeyHash::Mix(hash, key.vertex_input.attributes[i].binding);
}
PipelineKeyHash::MixStaticParams(hash, key.static_params);
return hash;
}
};
struct ComputePipelineKeyHash {
std::size_t operator()(const ComputePipelineKey& key) const {
std::size_t hash = 0;
PipelineKeyHash::Mix(hash, key.cs_shader_id);
return hash;
}
};
GraphicContext& m_graphics;
GraphicContext& m_graphics;
std::unique_ptr<ProgramCache> m_program_cache;
vk::PipelineCache m_driver_cache = nullptr;
std::filesystem::path m_driver_cache_path;
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>,
GraphicsPipelineKeyHash>
m_graphics_pipelines;
std::unordered_map<ComputePipelineKey, std::unique_ptr<ComputePipeline>, ComputePipelineKeyHash>
m_compute_pipelines;
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<Pipeline>, GraphicsPipelineKeyHash>
m_graphics_pipelines;
std::unordered_map<uint64_t, std::unique_ptr<Pipeline>> m_compute_pipelines;
Common::Mutex m_mutex;
void InitializeDriverCache();
@@ -230,14 +220,14 @@ private:
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
void CreatePipelineInternal(
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params,
vk::PipelineCache driver_cache);
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
const ShaderVertexInputInfo& vs_input_info, const ShaderProgram& vertex_program,
const ShaderPixelInputInfo* ps_input_info, const ShaderProgram& pixel_program,
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache);
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const ShaderComputeInputInfo& input_info,
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
} // namespace Libs::Graphics
@@ -13,6 +13,9 @@ vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) {
if (stages & vk::ShaderStageFlagBits::eVertex) {
result |= vk::PipelineStageFlagBits::eVertexShader;
}
if (stages & vk::ShaderStageFlagBits::eMeshEXT) {
result |= vk::PipelineStageFlagBits::eMeshShaderEXT;
}
if (stages & vk::ShaderStageFlagBits::eFragment) {
result |= vk::PipelineStageFlagBits::eFragmentShader;
}
@@ -25,7 +28,6 @@ vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) {
VulkanMemoryBarrier MakeShaderWriteDependency() {
VulkanMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eMemoryBarrier;
barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite |
vk::AccessFlagBits::eVertexAttributeRead |
@@ -38,7 +40,6 @@ VulkanMemoryBarrier MakeShaderWriteDependency() {
VulkanMemoryBarrier MakeShaderAccessDependency() {
VulkanMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eMemoryBarrier;
barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
return barrier;
@@ -46,7 +47,6 @@ VulkanMemoryBarrier MakeShaderAccessDependency() {
VulkanMemoryBarrier MakeShaderWriteHazardDependency() {
VulkanMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eMemoryBarrier;
barrier.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
return barrier;
@@ -56,7 +56,6 @@ vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
EXIT_IF(buffer == nullptr);
vk::BufferMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
barrier.srcAccessMask = vk::AccessFlagBits::eHostWrite | vk::AccessFlagBits::eTransferWrite |
vk::AccessFlagBits::eShaderWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
@@ -68,11 +67,12 @@ vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
return barrier;
}
std::vector<ShaderBufferWriteRange>
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
const ShaderRecompiler::IR::ResourceSnapshot& resources) {
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
EXIT_IF(!runtime);
const auto& program = *runtime.program;
const auto& resources = runtime.resources;
EXIT_IF(resources.buffers.size() != program.info.buffers.size());
std::vector<ShaderBufferWriteRange> writes;
bool has_writes = false;
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
if (!program.info.buffers[i].written) {
continue;
@@ -81,23 +81,11 @@ CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
EXIT_IF(value.dword_count < 4);
ShaderBufferResource descriptor;
std::memcpy(descriptor.fields, value.dwords.data(), sizeof(descriptor.fields));
const auto address = descriptor.Base48();
const auto records = static_cast<uint64_t>(descriptor.NumRecords());
const auto stride = static_cast<uint64_t>(descriptor.Stride());
if (stride != 0 && records > UINT64_MAX / stride) {
EXIT("shader resource barrier buffer footprint overflow\n");
}
const auto size = stride == 0 ? records : stride * records;
if (address != 0 && size != 0) {
writes.push_back({address, size});
}
// A zero stride means byte addressing. For either addressing mode a nonzero record
// count is exactly the condition for a nonempty descriptor range.
has_writes |= descriptor.Base48() != 0 && descriptor.NumRecords() != 0;
}
return writes;
}
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
EXIT_IF(!runtime);
return !CollectShaderBufferWrites(*runtime.program, *runtime.resources).empty();
return has_writes;
}
void ShaderAccessBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages) {
@@ -118,13 +106,11 @@ void ShaderWriteHazardBarrier(vk::CommandBuffer vk_buffer,
void ShaderWriteBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages) {
EXIT_IF(vk_buffer == nullptr || !source_stages);
const auto barrier = MakeShaderWriteDependency();
vk_buffer.pipelineBarrier(
source_stages,
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eVertexInput |
vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eTransfer |
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::DependencyFlags {}, 1, &barrier, 0, nullptr, 0, nullptr);
vk_buffer.pipelineBarrier(source_stages,
vk::PipelineStageFlagBits::eComputeShader |
vk::PipelineStageFlagBits::eAllGraphics |
vk::PipelineStageFlagBits::eTransfer,
vk::DependencyFlags {}, 1, &barrier, 0, nullptr, 0, nullptr);
}
} // namespace Libs::Graphics
@@ -4,27 +4,15 @@
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include <vector>
namespace Libs::Graphics {
struct ShaderStageRuntime;
struct ShaderBufferWriteRange {
uint64_t address = 0;
uint64_t size = 0;
bool operator==(const ShaderBufferWriteRange& other) const = default;
};
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages);
VulkanMemoryBarrier MakeShaderAccessDependency();
VulkanMemoryBarrier MakeShaderWriteHazardDependency();
VulkanMemoryBarrier MakeShaderWriteDependency();
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer);
std::vector<ShaderBufferWriteRange>
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
const ShaderRecompiler::IR::ResourceSnapshot& resources);
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime);
void ShaderAccessBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages);
void ShaderWriteHazardBarrier(vk::CommandBuffer vk_buffer,
@@ -12,6 +12,7 @@
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/BufferFormat.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/rectListShader.h"
#include "graphics/shader/shader.h"
@@ -103,7 +104,7 @@ static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format,
size = 4;
if (NarrowInputFormat(format, size, used_components)) {
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", raw_format,
VulkanToString(format).c_str(), used_components);
vk::to_string(format).c_str(), used_components);
}
return;
}
@@ -115,227 +116,13 @@ static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format,
}
format = vk::Format::eR16G16Sfloat;
size = 2;
if (NarrowInputFormat(format, size, used_components)) {
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", raw_format,
VulkanToString(format).c_str(), used_components);
}
return;
}
switch (fmt) {
case Prospero::BufferFormat::k32_32_32_32Float:
format = vk::Format::eR32G32B32A32Sfloat;
size = 4;
break;
case Prospero::BufferFormat::k32_32_32_32SInt:
format = vk::Format::eR32G32B32A32Sint;
size = 4;
break;
case Prospero::BufferFormat::k32_32_32_32UInt:
format = vk::Format::eR32G32B32A32Uint;
size = 4;
break;
case Prospero::BufferFormat::k32_32_32Float:
format = vk::Format::eR32G32B32Sfloat;
size = 3;
break;
case Prospero::BufferFormat::k32_32_32SInt:
format = vk::Format::eR32G32B32Sint;
size = 3;
break;
case Prospero::BufferFormat::k32_32_32UInt:
format = vk::Format::eR32G32B32Uint;
size = 3;
break;
case Prospero::BufferFormat::k16_16_16_16Float:
format = vk::Format::eR16G16B16A16Sfloat;
size = 4;
break;
case Prospero::BufferFormat::k16_16_16_16SInt:
format = vk::Format::eR16G16B16A16Sint;
size = 4;
break;
case Prospero::BufferFormat::k16_16_16_16UInt:
format = vk::Format::eR16G16B16A16Uint;
size = 4;
break;
case Prospero::BufferFormat::k16_16_16_16SScaled:
format = vk::Format::eR16G16B16A16Sscaled;
size = 4;
break;
case Prospero::BufferFormat::k16_16_16_16UScaled:
format = vk::Format::eR16G16B16A16Uscaled;
size = 4;
break;
case Prospero::BufferFormat::k16_16_16_16SNorm:
format = vk::Format::eR16G16B16A16Snorm;
size = 4;
break;
case Prospero::BufferFormat::k16_16_16_16UNorm:
format = vk::Format::eR16G16B16A16Unorm;
size = 4;
break;
case Prospero::BufferFormat::k32_32Float:
format = vk::Format::eR32G32Sfloat;
size = 2;
break;
case Prospero::BufferFormat::k32_32SInt:
format = vk::Format::eR32G32Sint;
size = 2;
break;
case Prospero::BufferFormat::k32_32UInt:
format = vk::Format::eR32G32Uint;
size = 2;
break;
case Prospero::BufferFormat::k8_8_8_8UInt:
format = vk::Format::eR8G8B8A8Uint;
size = 4;
break;
case Prospero::BufferFormat::k8_8_8_8SScaled:
format = vk::Format::eR8G8B8A8Sscaled;
size = 4;
break;
case Prospero::BufferFormat::k8_8_8_8UScaled:
format = vk::Format::eR8G8B8A8Uscaled;
size = 4;
break;
case Prospero::BufferFormat::k8_8_8_8SNorm:
format = vk::Format::eR8G8B8A8Snorm;
size = 4;
break;
case Prospero::BufferFormat::k8_8_8_8UNorm:
format = vk::Format::eR8G8B8A8Unorm;
size = 4;
break;
case Prospero::BufferFormat::k10_10_10_2UNorm:
format = vk::Format::eA2B10G10R10UnormPack32;
size = 4;
break;
case Prospero::BufferFormat::k10_10_10_2SNorm:
format = vk::Format::eA2B10G10R10SnormPack32;
size = 4;
break;
case Prospero::BufferFormat::k16_16Float:
format = vk::Format::eR16G16Sfloat;
size = 2;
break;
case Prospero::BufferFormat::k16_16SInt:
format = vk::Format::eR16G16Sint;
size = 2;
break;
case Prospero::BufferFormat::k16_16UInt:
format = vk::Format::eR16G16Uint;
size = 2;
break;
case Prospero::BufferFormat::k16_16SScaled:
format = vk::Format::eR16G16Sscaled;
size = 2;
break;
case Prospero::BufferFormat::k16_16UScaled:
format = vk::Format::eR16G16Uscaled;
size = 2;
break;
case Prospero::BufferFormat::k16_16SNorm:
format = vk::Format::eR16G16Snorm;
size = 2;
break;
case Prospero::BufferFormat::k16_16UNorm:
format = vk::Format::eR16G16Unorm;
size = 2;
break;
case Prospero::BufferFormat::k32Float:
format = vk::Format::eR32Sfloat;
size = 1;
break;
case Prospero::BufferFormat::k32SInt:
format = vk::Format::eR32Sint;
size = 1;
break;
case Prospero::BufferFormat::k32UInt:
format = vk::Format::eR32Uint;
size = 1;
break;
case Prospero::BufferFormat::k8_8SInt:
format = vk::Format::eR8G8Sint;
size = 2;
break;
case Prospero::BufferFormat::k8_8UInt:
format = vk::Format::eR8G8Uint;
size = 2;
break;
case Prospero::BufferFormat::k8_8SScaled:
format = vk::Format::eR8G8Sscaled;
size = 2;
break;
case Prospero::BufferFormat::k8_8UScaled:
format = vk::Format::eR8G8Uscaled;
size = 2;
break;
case Prospero::BufferFormat::k8_8SNorm:
format = vk::Format::eR8G8Snorm;
size = 2;
break;
case Prospero::BufferFormat::k8_8UNorm:
format = vk::Format::eR8G8Unorm;
size = 2;
break;
case Prospero::BufferFormat::k16Float:
format = vk::Format::eR16Sfloat;
size = 1;
break;
case Prospero::BufferFormat::k16SInt:
format = vk::Format::eR16Sint;
size = 1;
break;
case Prospero::BufferFormat::k16UInt:
format = vk::Format::eR16Uint;
size = 1;
break;
case Prospero::BufferFormat::k16SScaled:
format = vk::Format::eR16Sscaled;
size = 1;
break;
case Prospero::BufferFormat::k16UScaled:
format = vk::Format::eR16Uscaled;
size = 1;
break;
case Prospero::BufferFormat::k16SNorm:
format = vk::Format::eR16Snorm;
size = 1;
break;
case Prospero::BufferFormat::k16UNorm:
format = vk::Format::eR16Unorm;
size = 1;
break;
case Prospero::BufferFormat::k8SInt:
format = vk::Format::eR8Sint;
size = 1;
break;
case Prospero::BufferFormat::k8UInt:
format = vk::Format::eR8Uint;
size = 1;
break;
case Prospero::BufferFormat::k8SScaled:
format = vk::Format::eR8Sscaled;
size = 1;
break;
case Prospero::BufferFormat::k8UScaled:
format = vk::Format::eR8Uscaled;
size = 1;
break;
case Prospero::BufferFormat::k8SNorm:
format = vk::Format::eR8Snorm;
size = 1;
break;
case Prospero::BufferFormat::k8UNorm:
format = vk::Format::eR8Unorm;
size = 1;
break;
default:
EXIT("unknown format: fmt = %u\n", raw_format);
format = vk::Format::eUndefined;
size = 4;
break;
format = VulkanFormat(fmt);
size = ShaderRecompiler::Format::GetFormatInfo(fmt).component_count;
if (format == vk::Format::eUndefined || size == 0) {
EXIT("unknown vertex format: fmt = %u\n", raw_format);
}
if (NarrowInputFormat(format, size, used_components)) {
@@ -390,7 +177,7 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
}
static void AddLayoutBindings(std::vector<vk::DescriptorSetLayoutBinding>& descriptor_bindings,
const ShaderRecompiler::IR::Program& program,
const ShaderRecompiler::IR::CompiledShaderInfo& program,
vk::ShaderStageFlagBits stage) {
for (const auto& binding: program.bindings.descriptors) {
descriptor_bindings.push_back(
@@ -408,7 +195,6 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe
pipeline.uses_push_descriptors = descriptor_count <= graphics.max_push_descriptors;
vk::DescriptorSetLayoutCreateInfo create {};
create.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
create.flags = pipeline.uses_push_descriptors
? vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR
: vk::DescriptorSetLayoutCreateFlags {};
@@ -420,21 +206,27 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params,
vk::PipelineCache driver_cache) {
GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
const ShaderVertexInputInfo& vs_input_info, const ShaderProgram& vertex_program,
const ShaderPixelInputInfo* ps_input_info, const ShaderProgram& pixel_program,
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache) {
const bool ps_active = ps_input_info != nullptr;
EXIT_IF(vertex_module == nullptr || (ps_active && pixel_module == nullptr));
EXIT_IF(!vertex_program || (ps_active && !pixel_program));
const bool with_depth = rendering.depth_format != vk::Format::eUndefined ||
rendering.stencil_format != vk::Format::eUndefined;
EXIT_IF(!vs_input_info.stage);
const bool mesh = vs_input_info.stage.program->stage == ShaderType::Mesh;
EXIT_NOT_IMPLEMENTED(mesh && !graphics.mesh_shader_enabled);
const auto vertex_stage =
mesh ? vk::ShaderStageFlagBits::eMeshEXT : vk::ShaderStageFlagBits::eVertex;
const bool rect_list = static_params.topology == vk::PrimitiveTopology::ePatchList;
const bool rect_list = !mesh && static_params.topology == vk::PrimitiveTopology::ePatchList;
vk::ShaderModule tess_control_shader_module = nullptr;
vk::ShaderModule tess_eval_shader_module = nullptr;
vk::ShaderModuleCreateInfo create_info {};
create_info.sType = vk::StructureType::eShaderModuleCreateInfo;
vk::Result result {};
if (rect_list) {
const auto shaders =
@@ -445,7 +237,7 @@ void CreatePipelineInternal(
graphics.device.createShaderModule(&create_info, nullptr, &tess_control_shader_module);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateShaderModule RectList TCS done result=%s module=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(tess_control_shader_module));
vk::to_string(result).c_str(), static_cast<void*>(tess_control_shader_module));
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
@@ -455,7 +247,7 @@ void CreatePipelineInternal(
graphics.device.createShaderModule(&create_info, nullptr, &tess_eval_shader_module);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateShaderModule RectList TES done result=%s module=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(tess_eval_shader_module));
vk::to_string(result).c_str(), static_cast<void*>(tess_eval_shader_module));
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
}
@@ -463,224 +255,100 @@ void CreatePipelineInternal(
EXIT_NOT_IMPLEMENTED(
rect_list && (tess_control_shader_module == nullptr || tess_eval_shader_module == nullptr));
vk::PipelineShaderStageCreateInfo vert_shader_stage_info {};
vert_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
vert_shader_stage_info.pNext = nullptr;
vert_shader_stage_info.flags = {};
vert_shader_stage_info.stage = vk::ShaderStageFlagBits::eVertex;
vert_shader_stage_info.module = vertex_module;
vert_shader_stage_info.pName = "main";
vert_shader_stage_info.pSpecializationInfo = nullptr;
vk::PipelineShaderStageCreateInfo frag_shader_stage_info {};
frag_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
frag_shader_stage_info.pNext = nullptr;
frag_shader_stage_info.flags = {};
frag_shader_stage_info.stage = vk::ShaderStageFlagBits::eFragment;
frag_shader_stage_info.module = pixel_module;
frag_shader_stage_info.pName = "main";
frag_shader_stage_info.pSpecializationInfo = nullptr;
vk::PipelineShaderStageCreateInfo tess_control_shader_stage_info {};
tess_control_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
tess_control_shader_stage_info.stage = vk::ShaderStageFlagBits::eTessellationControl;
tess_control_shader_stage_info.module = tess_control_shader_module;
tess_control_shader_stage_info.pName = "main";
vk::PipelineShaderStageCreateInfo tess_eval_shader_stage_info {};
tess_eval_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
tess_eval_shader_stage_info.stage = vk::ShaderStageFlagBits::eTessellationEvaluation;
tess_eval_shader_stage_info.module = tess_eval_shader_module;
tess_eval_shader_stage_info.pName = "main";
vk::PipelineShaderStageCreateInfo shader_stages[4] = {};
uint32_t shader_stage_count = 0;
shader_stages[shader_stage_count++] = vert_shader_stage_info;
vk::PipelineShaderStageCreateInfo shader_stages[4] = {
{.stage = vertex_stage, .module = vertex_program.module, .pName = "main"}};
uint32_t shader_stage_count = 1;
if (rect_list) {
shader_stages[shader_stage_count++] = tess_control_shader_stage_info;
shader_stages[shader_stage_count++] = tess_eval_shader_stage_info;
shader_stages[shader_stage_count++] = {.stage =
vk::ShaderStageFlagBits::eTessellationControl,
.module = tess_control_shader_module,
.pName = "main"};
shader_stages[shader_stage_count++] = {.stage =
vk::ShaderStageFlagBits::eTessellationEvaluation,
.module = tess_eval_shader_module,
.pName = "main"};
}
if (ps_active) {
shader_stages[shader_stage_count++] = frag_shader_stage_info;
shader_stages[shader_stage_count++] = {.stage = vk::ShaderStageFlagBits::eFragment,
.module = pixel_program.module,
.pName = "main"};
}
vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX];
vk::VertexInputBindingDescription input_desc[ShaderVertexInputInfo::RES_MAX];
vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX] {};
vk::VertexInputBindingDescription input_desc[ShaderVertexInputInfo::RES_MAX] {};
for (int bi = 0; bi < vs_input_info.buffers_num; bi++) {
const auto& b = vs_input_info.buffers[bi];
input_desc[bi].binding = bi;
input_desc[bi].stride = b.stride;
input_desc[bi].inputRate =
(b.fetch_index == 0 ? vk::VertexInputRate::eVertex : vk::VertexInputRate::eInstance);
for (uint32_t binding = 0; binding < vertex_input.binding_count; binding++) {
input_desc[binding].binding = binding;
input_desc[binding].stride = vertex_input.bindings[binding].stride;
input_desc[binding].inputRate = vertex_input.bindings[binding].instance
? vk::VertexInputRate::eInstance
: vk::VertexInputRate::eVertex;
}
for (uint32_t index = 0; index < vertex_input.attribute_count; index++) {
input_attr[index].binding = vertex_input.attributes[index].binding;
input_attr[index].location = index;
input_attr[index].offset = vertex_input.attributes[index].offset;
for (int ai = 0; ai < b.attr_num; ai++) {
auto index = b.attr_indices[ai];
input_attr[index].binding = bi;
input_attr[index].location = index;
input_attr[index].offset = b.attr_offsets[ai];
uint32_t attr_size = 4;
const auto registers_num = vs_input_info.resources_dst[index].registers_num;
const auto compiled_components =
vs_input_info.stage.program->info.vertex_fetch_components[index];
const auto used_components =
compiled_components > 0 ? static_cast<int>(compiled_components) : registers_num;
GetInputFormat(vs_input_info.resources[index], input_attr[index].format, attr_size,
static_cast<uint32_t>(used_components));
uint32_t attr_size = 4;
auto registers_num = vs_input_info.resources_dst[index].registers_num;
const auto compiled_components =
vs_input_info.stage.program->info.vertex_fetch_components[index];
auto used_components =
compiled_components > 0 ? static_cast<int>(compiled_components) : registers_num;
GetInputFormat(vs_input_info.resources[index], input_attr[index].format, attr_size,
static_cast<uint32_t>(used_components));
if (graphics_debug_dump_enabled()) {
static std::atomic_uint log_count = 0;
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
if (log_id < 128) {
LOGF("VertexInputState[%u]: attr=%u binding=%u offset=%u stride=%u fmt=%d "
"src_fmt=%u dst=v%u regs=%u"
" fetched_components=%u attr_size=%u swizzle=%u,%u,%u,%u\n",
log_id, index, input_attr[index].binding, input_attr[index].offset,
input_desc[bi].stride, static_cast<int>(input_attr[index].format),
static_cast<uint32_t>(vs_input_info.resources[index].Format()),
static_cast<uint32_t>(vs_input_info.resources_dst[index].register_start),
static_cast<uint32_t>(registers_num),
static_cast<uint32_t>(used_components), attr_size,
static_cast<uint32_t>(vs_input_info.resources[index].DstSelX()),
static_cast<uint32_t>(vs_input_info.resources[index].DstSelY()),
static_cast<uint32_t>(vs_input_info.resources[index].DstSelZ()),
static_cast<uint32_t>(vs_input_info.resources[index].DstSelW()));
}
}
if (vs_input_info.resources[index].OutOfBounds() != 0) {
static bool logged = false;
if (!logged) {
LOGF("VertexInput: temporary: accepting PS5 out-of-bounds behavior %" PRIu8
"\n",
vs_input_info.resources[index].OutOfBounds());
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].AddTid());
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].SwizzleEnabled());
auto log_unsupported_vertex_swizzle = [index, attr_size, registers_num](
uint32_t swizzle, uint32_t expected) {
static bool logged = false;
if (!logged) {
LOGF(
"VertexInput: temporary: accepting unsupported dst swizzle at attr %" PRIu32
" (attr_size=%" PRIu32 ", regs=%" PRIu32 ", swizzle=0x%03" PRIx32
", expected=0x%03" PRIx32 ")\n",
static_cast<uint32_t>(index), attr_size, registers_num, swizzle, expected);
logged = true;
}
};
switch (registers_num) {
case 1: {
auto swizzle = vs_input_info.resources[index].DstSelX();
if (swizzle != DstSel(4)) {
log_unsupported_vertex_swizzle(swizzle, DstSel(4));
}
break;
}
case 2: {
auto swizzle = vs_input_info.resources[index].DstSelXY();
auto expected = (attr_size == 1 ? DstSel(4, 0) : DstSel(4, 5));
if (swizzle != expected) {
log_unsupported_vertex_swizzle(swizzle, expected);
}
break;
}
case 3: {
auto swizzle = vs_input_info.resources[index].DstSelXYZ();
auto expected = DstSel(4, 5, 6);
switch (attr_size) {
case 1: expected = DstSel(4, 0, 0); break;
case 2: expected = DstSel(4, 5, 0); break;
default: break;
}
if (swizzle != expected) {
log_unsupported_vertex_swizzle(swizzle, expected);
}
break;
}
case 4: {
auto swizzle = vs_input_info.resources[index].DstSelXYZW();
auto expected = DstSel(4, 5, 6, 7);
bool supported = false;
switch (attr_size) {
case 1:
expected = DstSel(4, 0, 0, 1);
supported = (swizzle == expected);
break;
case 2:
expected = DstSel(4, 5, 0, 1);
supported = (swizzle == expected);
break;
case 3:
expected = DstSel(4, 5, 6, 1);
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 0));
break;
default:
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 1) ||
swizzle == DstSel(4, 5, 6, 0));
break;
}
if (!supported) {
log_unsupported_vertex_swizzle(swizzle, expected);
}
break;
}
default: EXIT("invalid registers_num");
if (graphics_debug_dump_enabled()) {
static std::atomic_uint log_count = 0;
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
if (log_id < 128) {
LOGF("VertexInputState[%u]: attr=%u binding=%u offset=%u stride=%u fmt=%d "
"src_fmt=%u dst=v%u regs=%u"
" fetched_components=%u attr_size=%u swizzle=%u,%u,%u,%u\n",
log_id, index, input_attr[index].binding, input_attr[index].offset,
input_desc[input_attr[index].binding].stride,
static_cast<int>(input_attr[index].format),
static_cast<uint32_t>(vs_input_info.resources[index].Format()),
static_cast<uint32_t>(vs_input_info.resources_dst[index].register_start),
static_cast<uint32_t>(registers_num), static_cast<uint32_t>(used_components),
attr_size, static_cast<uint32_t>(vs_input_info.resources[index].DstSelX()),
static_cast<uint32_t>(vs_input_info.resources[index].DstSelY()),
static_cast<uint32_t>(vs_input_info.resources[index].DstSelZ()),
static_cast<uint32_t>(vs_input_info.resources[index].DstSelW()));
}
}
if (vs_input_info.resources[index].OutOfBounds() != 0) {
static bool logged = false;
if (!logged) {
LOGF("VertexInput: temporary: accepting PS5 out-of-bounds behavior %" PRIu8 "\n",
vs_input_info.resources[index].OutOfBounds());
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].AddTid());
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].SwizzleEnabled());
EXIT_IF(registers_num < 1 || registers_num > 4);
}
vk::PipelineVertexInputStateCreateInfo vertex_input_info {};
vertex_input_info.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo;
vertex_input_info.pNext = nullptr;
vertex_input_info.flags = {};
vertex_input_info.vertexBindingDescriptionCount = vs_input_info.buffers_num;
vertex_input_info.vertexBindingDescriptionCount = vertex_input.binding_count;
vertex_input_info.pVertexBindingDescriptions = input_desc;
vertex_input_info.vertexAttributeDescriptionCount = vs_input_info.resources_num;
vertex_input_info.vertexAttributeDescriptionCount = vertex_input.attribute_count;
vertex_input_info.pVertexAttributeDescriptions = input_attr;
vk::PipelineInputAssemblyStateCreateInfo input_assembly {};
input_assembly.sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo;
input_assembly.pNext = nullptr;
input_assembly.flags = {};
input_assembly.topology = static_params.topology;
input_assembly.primitiveRestartEnable =
static_params.primitive_restart_enable ? VK_TRUE : VK_FALSE;
vk::Viewport viewport {};
viewport.x = static_params.viewport_offset[0] - static_params.viewport_scale[0];
viewport.y = static_params.viewport_offset[1] - static_params.viewport_scale[1];
viewport.width = static_params.viewport_scale[0] * 2.0f;
viewport.height = static_params.viewport_scale[1] * 2.0f;
viewport.minDepth = static_params.viewport_offset[2];
viewport.maxDepth = static_params.viewport_scale[2] + static_params.viewport_offset[2];
vk::Rect2D scissor {};
scissor.offset = {static_params.scissor_ltrb[0], static_params.scissor_ltrb[1]};
scissor.extent = {
static_cast<uint32_t>(static_params.scissor_ltrb[2] - static_params.scissor_ltrb[0]),
static_cast<uint32_t>(static_params.scissor_ltrb[3] - static_params.scissor_ltrb[1])};
vk::PipelineViewportDepthClipControlCreateInfoEXT depth_clip_control {};
depth_clip_control.sType = vk::StructureType::ePipelineViewportDepthClipControlCreateInfoEXT;
depth_clip_control.pNext = nullptr;
depth_clip_control.negativeOneToOne = (static_params.negative_one_to_one ? VK_TRUE : VK_FALSE);
vk::PipelineViewportStateCreateInfo viewport_state {};
viewport_state.sType = vk::StructureType::ePipelineViewportStateCreateInfo;
viewport_state.pNext = &depth_clip_control;
viewport_state.flags = {};
viewport_state.viewportCount = 1;
viewport_state.pViewports = &viewport;
viewport_state.scissorCount = 1;
viewport_state.pScissors = &scissor;
viewport_state.pNext = &depth_clip_control;
vk::CullModeFlags cull_mode = vk::CullModeFlagBits::eNone;
if (static_params.cull_back) {
@@ -694,67 +362,39 @@ void CreatePipelineInternal(
(static_params.face ? vk::FrontFace::eClockwise : vk::FrontFace::eCounterClockwise);
vk::PipelineRasterizationDepthClipStateCreateInfoEXT clip_ext {};
clip_ext.sType = vk::StructureType::ePipelineRasterizationDepthClipStateCreateInfoEXT;
clip_ext.pNext = nullptr;
clip_ext.flags = {};
clip_ext.depthClipEnable = static_params.depth_clip_enable ? VK_TRUE : VK_FALSE;
vk::PipelineRasterizationStateCreateInfo rasterizer {};
rasterizer.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
// MoltenVK lacks VK_EXT_depth_clip_enable; omit the depth-clip struct on macOS and accept
// Vulkan's default depth clipping (enabled) instead of the PS5's clamp behavior.
#if defined(__APPLE__)
rasterizer.pNext = nullptr;
#else
#if !defined(__APPLE__)
rasterizer.pNext = &clip_ext;
#endif
rasterizer.flags = {};
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = vk::PolygonMode::eFill;
rasterizer.cullMode = cull_mode;
rasterizer.frontFace = front_face;
rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f;
rasterizer.depthBiasClamp = 0.0f;
rasterizer.depthBiasSlopeFactor = 0.0f;
rasterizer.lineWidth = 1.0f;
vk::PipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex {};
EXIT_NOT_IMPLEMENTED(static_params.provoking_vtx_last &&
!graphics.provoking_vertex_last_enabled);
if (graphics.provoking_vertex_last_enabled) {
provoking_vertex.provokingVertexMode = static_params.provoking_vtx_last
? vk::ProvokingVertexModeEXT::eLastVertex : vk::ProvokingVertexModeEXT::eFirstVertex;
provoking_vertex.pNext = rasterizer.pNext;
rasterizer.pNext = &provoking_vertex;
}
rasterizer.cullMode = cull_mode;
rasterizer.frontFace = front_face;
rasterizer.polygonMode = static_params.polygon_mode;
rasterizer.lineWidth = 1.0f;
vk::PipelineMultisampleStateCreateInfo multisampling {};
multisampling.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo;
multisampling.pNext = nullptr;
multisampling.flags = {};
multisampling.sampleShadingEnable = static_params.sample_shading_enable ? VK_TRUE : VK_FALSE;
multisampling.rasterizationSamples = vulkan_sample_count(static_params.samples);
multisampling.minSampleShading = 1.0f;
multisampling.pSampleMask = nullptr;
multisampling.alphaToCoverageEnable = VK_FALSE;
multisampling.alphaToOneEnable = VK_FALSE;
multisampling.sampleShadingEnable = static_params.sample_shading_enable ? VK_TRUE : VK_FALSE;
multisampling.rasterizationSamples = vulkan_sample_count(static_params.samples);
multisampling.minSampleShading = 1.0f;
vk::PipelineColorBlendAttachmentState color_blend_attachment[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < static_params.color_count; i++) {
vk::ColorComponentFlags color_write_mask = {};
for (uint32_t i = 0; i < rendering.color_count; i++) {
EXIT_NOT_IMPLEMENTED((static_params.color_mask[i] & ~0x0fu) != 0);
if ((static_params.color_mask[i] & 0x1u) != 0) {
color_write_mask |=
static_cast<vk::ColorComponentFlags>(vk::ColorComponentFlagBits::eR);
}
if ((static_params.color_mask[i] & 0x2u) != 0) {
color_write_mask |=
static_cast<vk::ColorComponentFlags>(vk::ColorComponentFlagBits::eG);
}
if ((static_params.color_mask[i] & 0x4u) != 0) {
color_write_mask |=
static_cast<vk::ColorComponentFlags>(vk::ColorComponentFlagBits::eB);
}
if ((static_params.color_mask[i] & 0x8u) != 0) {
color_write_mask |=
static_cast<vk::ColorComponentFlags>(vk::ColorComponentFlagBits::eA);
}
color_blend_attachment[i].colorWriteMask = color_write_mask;
color_blend_attachment[i].blendEnable =
(static_params.blend_enable[i] && !static_params.blend_bypass[i]) ? VK_TRUE : VK_FALSE;
color_blend_attachment[i].colorWriteMask =
vk::ColorComponentFlags {static_params.color_mask[i]};
color_blend_attachment[i].blendEnable = static_params.blend_enable[i] ? VK_TRUE : VK_FALSE;
color_blend_attachment[i].srcColorBlendFactor =
GetBlendFactor(static_params.color_srcblend[i]);
color_blend_attachment[i].dstColorBlendFactor =
@@ -773,54 +413,37 @@ void CreatePipelineInternal(
}
vk::Bool32 color_write_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < static_params.color_count; i++) {
for (uint32_t i = 0; i < rendering.color_count; i++) {
color_write_enable[i] = VK_TRUE;
}
vk::PipelineColorWriteCreateInfoEXT color_write {};
color_write.sType = vk::StructureType::ePipelineColorWriteCreateInfoEXT;
color_write.pNext = nullptr;
color_write.attachmentCount = static_params.color_count;
color_write.attachmentCount = rendering.color_count;
color_write.pColorWriteEnables = color_write_enable;
vk::PipelineColorBlendStateCreateInfo color_blending {};
color_blending.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
// MoltenVK lacks VK_EXT_color_write_enable; drop the dynamic color-write struct on macOS
// and rely on each attachment's static colorWriteMask (all channels enabled by default).
#if defined(__APPLE__)
color_blending.pNext = nullptr;
#else
#if !defined(__APPLE__)
color_blending.pNext = &color_write;
#endif
color_blending.flags = {};
color_blending.logicOpEnable = VK_FALSE;
color_blending.logicOp = vk::LogicOp::eCopy;
color_blending.attachmentCount = static_params.color_count;
color_blending.pAttachments = color_blend_attachment;
color_blending.blendConstants[0] = static_params.blend_color_red;
color_blending.blendConstants[1] = static_params.blend_color_green;
color_blending.blendConstants[2] = static_params.blend_color_blue;
color_blending.blendConstants[3] = static_params.blend_color_alpha;
color_blending.logicOp = vk::LogicOp::eCopy;
color_blending.attachmentCount = rendering.color_count;
color_blending.pAttachments = color_blend_attachment;
EXIT_IF(!vs_input_info.stage);
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
AddLayoutBindings(descriptor_bindings, *vs_input_info.stage.program,
vk::ShaderStageFlagBits::eVertex);
AddLayoutBindings(descriptor_bindings, *vs_input_info.stage.program, vertex_stage);
if (ps_active) {
EXIT_IF(!ps_input_info->stage);
AddLayoutBindings(descriptor_bindings, *ps_input_info->stage.program,
vk::ShaderStageFlagBits::eFragment);
}
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
constexpr auto GraphicsStages =
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
const vk::PushConstantRange push_constants {
GraphicsStages, 0, ShaderRecompiler::IR::NativePushConstantSize};
const auto GraphicsStages = vertex_stage | vk::ShaderStageFlagBits::eFragment;
const vk::PushConstantRange push_constants {GraphicsStages, 0,
ShaderRecompiler::IR::NativePushConstantSize};
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
pipeline_layout_info.pNext = nullptr;
pipeline_layout_info.flags = {};
pipeline_layout_info.setLayoutCount = 1;
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
pipeline_layout_info.pushConstantRangeCount = 1;
@@ -831,25 +454,19 @@ void CreatePipelineInternal(
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreatePipelineLayout begin VS=%" PRIu64 " PS=%" PRIu64
" set_layouts=1 push_constants=%" PRIu32 "\n",
pipeline.vs_shader_id, pipeline.ps_shader_id, 1u);
vertex_program.id, ps_active ? pixel_program.id : 0, 1u);
}
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreatePipelineLayout done result=%s layout=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
vk::to_string(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
EXIT_NOT_IMPLEMENTED(pipeline.pipeline_layout == nullptr);
vk::PipelineDepthStencilStateCreateInfo depth_stencil_info {};
depth_stencil_info.sType = vk::StructureType::ePipelineDepthStencilStateCreateInfo;
depth_stencil_info.pNext = nullptr;
depth_stencil_info.flags = {};
depth_stencil_info.depthTestEnable = (static_params.depth_test_enable ? VK_TRUE : VK_FALSE);
depth_stencil_info.depthWriteEnable = (static_params.depth_write_enable ? VK_TRUE : VK_FALSE);
depth_stencil_info.depthCompareOp = static_params.depth_compare_op;
depth_stencil_info.depthBoundsTestEnable =
#if defined(__APPLE__)
VK_FALSE; // MoltenVK lacks the depthBounds feature; depth-bounds testing is disabled
@@ -868,84 +485,72 @@ void CreatePipelineInternal(
depth_stencil_info.minDepthBounds = static_params.depth_min_bounds;
depth_stencil_info.maxDepthBounds = static_params.depth_max_bounds;
const vk::DynamicState dynamic_states[] = {
vk::DynamicState::eViewport,
vk::DynamicState::eScissor,
std::vector<vk::DynamicState> dynamic_states {
vk::DynamicState::eViewportWithCount,
vk::DynamicState::eScissorWithCount,
vk::DynamicState::eLineWidth,
vk::DynamicState::eDepthTestEnable,
vk::DynamicState::eDepthWriteEnable,
vk::DynamicState::eDepthCompareOp,
vk::DynamicState::eDepthBiasEnable,
vk::DynamicState::eDepthBias,
vk::DynamicState::eStencilCompareMask,
vk::DynamicState::eStencilReference,
vk::DynamicState::eStencilWriteMask,
#if !defined(__APPLE__)
// Keep last so depth-only pipelines can omit this dynamic state.
vk::DynamicState::eColorWriteEnableEXT, // unsupported by MoltenVK; static mask instead
#endif
vk::DynamicState::eBlendConstants,
};
auto dynamic_states_count =
static_cast<uint32_t>(sizeof(dynamic_states) / sizeof(dynamic_states[0]));
#if !defined(__APPLE__)
if (static_params.color_count == 0) {
dynamic_states_count--;
if (rendering.color_count != 0) {
dynamic_states.push_back(vk::DynamicState::eColorWriteEnableEXT);
}
#endif
if (graphics.attachment_feedback_loop_enabled) {
dynamic_states.push_back(vk::DynamicState::eAttachmentFeedbackLoopEnableEXT);
}
vk::PipelineDynamicStateCreateInfo dynamic_state {};
dynamic_state.sType = vk::StructureType::ePipelineDynamicStateCreateInfo;
dynamic_state.pNext = nullptr;
dynamic_state.flags = {};
dynamic_state.dynamicStateCount = dynamic_states_count;
dynamic_state.pDynamicStates = dynamic_states;
dynamic_state.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
dynamic_state.pDynamicStates = dynamic_states.data();
vk::GraphicsPipelineCreateInfo pipeline_info {};
vk::PipelineRenderingCreateInfo rendering_info {};
rendering_info.sType = vk::StructureType::ePipelineRenderingCreateInfo;
rendering_info.colorAttachmentCount = rendering.color_count;
rendering_info.pColorAttachmentFormats = rendering.color_formats.data();
rendering_info.depthAttachmentFormat = rendering.depth_format;
rendering_info.stencilAttachmentFormat = rendering.stencil_format;
pipeline_info.sType = vk::StructureType::eGraphicsPipelineCreateInfo;
pipeline_info.pNext = &rendering_info;
pipeline_info.flags = {};
pipeline_info.stageCount = shader_stage_count;
pipeline_info.pStages = shader_stages;
pipeline_info.pVertexInputState = &vertex_input_info;
pipeline_info.pInputAssemblyState = &input_assembly;
pipeline_info.pVertexInputState = mesh ? nullptr : &vertex_input_info;
pipeline_info.pInputAssemblyState = mesh ? nullptr : &input_assembly;
vk::PipelineTessellationStateCreateInfo tessellation_state {};
tessellation_state.sType = vk::StructureType::ePipelineTessellationStateCreateInfo;
tessellation_state.patchControlPoints = 3;
pipeline_info.pTessellationState = (rect_list ? &tessellation_state : nullptr);
pipeline_info.pViewportState = &viewport_state;
pipeline_info.pRasterizationState = &rasterizer;
pipeline_info.pMultisampleState = &multisampling;
pipeline_info.pDepthStencilState = (static_params.with_depth ? &depth_stencil_info : nullptr);
pipeline_info.pColorBlendState = &color_blending;
pipeline_info.pDynamicState = &dynamic_state;
pipeline_info.layout = pipeline.pipeline_layout;
pipeline_info.renderPass = nullptr;
pipeline_info.subpass = 0;
pipeline_info.basePipelineHandle = nullptr;
pipeline_info.basePipelineIndex = -1;
pipeline_info.pDepthStencilState = (with_depth ? &depth_stencil_info : nullptr);
pipeline_info.pColorBlendState = &color_blending;
pipeline_info.pDynamicState = &dynamic_state;
pipeline_info.layout = pipeline.pipeline_layout;
pipeline_info.basePipelineIndex = -1;
EXIT_IF(pipeline.pipeline != nullptr);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateGraphicsPipelines begin VS=%" PRIu64 " PS=%" PRIu64
" topology=%" PRIu32 " color_mask=0x%08" PRIx32
" depth=%s blend=%s dyn_states=%" PRIu32 " viewport=%f,%f %fx%f"
" scissor=%d,%d %ux%u\n",
pipeline.vs_shader_id, pipeline.ps_shader_id,
static_cast<uint32_t>(static_params.topology),
static_params.color_mask[0], (static_params.with_depth ? "true" : "false"),
(static_params.blend_enable[0] ? "true" : "false"), dynamic_states_count, viewport.x,
viewport.y, viewport.width, viewport.height, scissor.offset.x, scissor.offset.y,
scissor.extent.width, scissor.extent.height);
" depth=%s blend=%s dyn_states=%" PRIu32 "\n",
vertex_program.id, ps_active ? pixel_program.id : 0,
static_cast<uint32_t>(static_params.topology), static_params.color_mask[0],
(with_depth ? "true" : "false"), (static_params.blend_enable[0] ? "true" : "false"),
dynamic_state.dynamicStateCount);
}
result = graphics.device.createGraphicsPipelines(driver_cache, 1, &pipeline_info, nullptr,
&pipeline.pipeline);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateGraphicsPipelines done result=%s pipeline=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline));
vk::to_string(result).c_str(), static_cast<void*>(pipeline.pipeline));
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
@@ -960,26 +565,20 @@ void CreatePipelineInternal(
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
const ShaderComputeInputInfo& input_info,
vk::ShaderModule compute_module, vk::PipelineCache driver_cache) {
vk::ShaderModule compute_module, vk::PipelineCache driver_cache) {
EXIT_IF(compute_module == nullptr);
vk::PipelineShaderStageCreateInfo comp_shader_stage_info {};
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo comp_subgroup_size {};
comp_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
comp_shader_stage_info.pNext = nullptr;
comp_shader_stage_info.flags = {};
comp_shader_stage_info.stage = vk::ShaderStageFlagBits::eCompute;
comp_shader_stage_info.module = compute_module;
comp_shader_stage_info.pName = "main";
comp_shader_stage_info.pSpecializationInfo = nullptr;
comp_shader_stage_info.stage = vk::ShaderStageFlagBits::eCompute;
comp_shader_stage_info.module = compute_module;
comp_shader_stage_info.pName = "main";
EXIT_IF(!input_info.stage);
const auto wave_size = input_info.stage.program->wave_size;
if (graphics.compute_subgroup_size_control_enabled &&
wave_size >= graphics.min_subgroup_size && wave_size <= graphics.max_subgroup_size) {
comp_subgroup_size.sType =
vk::StructureType::ePipelineShaderStageRequiredSubgroupSizeCreateInfo;
comp_subgroup_size.requiredSubgroupSize = wave_size;
comp_shader_stage_info.pNext = &comp_subgroup_size;
}
@@ -992,9 +591,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipe
ShaderRecompiler::IR::NativePushConstantSize};
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
pipeline_layout_info.pNext = nullptr;
pipeline_layout_info.flags = {};
pipeline_layout_info.setLayoutCount = 1;
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
pipeline_layout_info.pushConstantRangeCount = 1;
@@ -1007,19 +603,15 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipe
auto result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
LOGF("PipelineTrace: vkCreatePipelineLayout CS done result=%s layout=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
vk::to_string(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
EXIT_NOT_IMPLEMENTED(pipeline.pipeline_layout == nullptr);
vk::ComputePipelineCreateInfo info {};
info.sType = vk::StructureType::eComputePipelineCreateInfo;
info.pNext = nullptr;
info.flags = {};
info.stage = comp_shader_stage_info;
info.layout = pipeline.pipeline_layout;
info.basePipelineHandle = nullptr;
info.basePipelineIndex = -1;
info.stage = comp_shader_stage_info;
info.layout = pipeline.pipeline_layout;
info.basePipelineIndex = -1;
EXIT_IF(pipeline.pipeline != nullptr);
@@ -1028,7 +620,7 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipe
result = graphics.device.createComputePipelines(driver_cache, 1, &info, nullptr,
&pipeline.pipeline);
LOGF("PipelineTrace: vkCreateComputePipelines done result=%s pipeline=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline));
vk::to_string(result).c_str(), static_cast<void*>(pipeline.pipeline));
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);
+43 -22
View File
@@ -48,6 +48,31 @@ enum class CommandBufferDebugOp : uint32_t {
Unknown,
};
enum class DrawOffsetSource : uint8_t {
DrawState,
IndirectArgs,
};
struct DrawIndexArgs {
uint32_t index_count = 0;
const void* index_addr = nullptr;
uint32_t instance_count = 0;
uint32_t index_type_and_size = 0;
int32_t base_vertex = 0;
uint32_t first_instance = 0;
DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
uint32_t render_target_slice_offset = 0;
};
struct DrawAutoArgs {
uint32_t vertex_count = 0;
uint32_t instance_count = 0;
uint32_t first_vertex = 0;
uint32_t first_instance = 0;
DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
uint32_t render_target_slice_offset = 0;
};
struct SubmitInfo {
static constexpr uint32_t MaxSemaphores = 3;
@@ -129,13 +154,6 @@ public:
explicit RenderExecutor(RenderContext& context): m_context(context) {}
KYTY_CLASS_NO_COPY(RenderExecutor);
void DrawIndex(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_type_and_size,
uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
uint32_t instance_count = 1, uint32_t render_target_slice_offset = 0,
int32_t vertex_offset_add = 0, uint32_t first_instance = 0);
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_count, uint32_t flags,
uint32_t render_target_slice_offset = 0, uint32_t instance_count = 1,
uint32_t first_vertex = 0, uint32_t first_instance = 0);
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
@@ -148,6 +166,9 @@ public:
std::span<PreparedBindings* const> bindings);
private:
void DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const DrawIndexArgs& args);
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
struct GraphicsBindings {
PreparedBindings vertex;
std::optional<PreparedBindings> pixel;
@@ -158,31 +179,32 @@ private:
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel,
bool pixel_active);
void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderColorInfo& target, uint32_t render_target_slice_offset = 0,
uint32_t render_target_slot = UINT32_MAX,
void ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& target,
uint32_t render_target_slice_offset, uint32_t render_target_slot,
bool ignore_target_mask = false, bool exact_format = false);
void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderDepthInfo& target);
[[nodiscard]] bool PrepareDrawRenderState(uint64_t submit_id, CommandBuffer& buffer,
void ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepthInfo& target);
[[nodiscard]] bool DepthStencilCopy(CommandBuffer& buffer);
[[nodiscard]] bool PrepareDrawRenderState(CommandBuffer& buffer,
const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
bool log_setup_phases, DrawRenderState& state);
DrawRenderState& state);
void ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buffer, const DrawCallInfo& draw,
DrawRenderState& state, vk::PrimitiveTopology topology,
const DrawEmitInfo& emit, const DrawIndexBufferSource& index_source,
bool primitive_restart_enable, bool log_pipeline_phase,
bool set_bind_debug, bool set_auto_debug);
bool primitive_restart_enable);
[[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth);
[[nodiscard]] bool ResolveColorTargets(uint64_t submit_id, CommandBuffer& buffer,
uint32_t color_count, RenderDepthInfo& depth,
const std::optional<PreparedBindings>& pixel = std::nullopt);
[[nodiscard]] bool ResolveColorTargets(CommandBuffer& buffer,
uint32_t render_target_slice_offset);
void BindImage(ImageId id, bool storage);
void BindRenderTarget(ImageId id);
void TrackImageBinding(ImageId id);
void ResetBindings();
[[nodiscard]] bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const CommandBuffer& buffer);
[[nodiscard]] bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input,
CommandBuffer& command, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode);
RenderContext& m_context;
std::vector<ImageId> m_bound_images;
@@ -190,13 +212,12 @@ private:
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
std::vector<uint32_t> m_image_occurrences;
std::array<uint32_t, ShaderRecompiler::IR::NativePushConstantSize / sizeof(uint32_t)>
m_push_constants {};
friend class CommandProcessor;
friend struct RenderExecutorTestAccess;
};
[[nodiscard]] bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x,
[[nodiscard]] bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode,
ShaderBufferResource& descriptor,
uint32_t& packed_clear, uint64_t& size);
+114 -73
View File
@@ -25,28 +25,33 @@
#include "libs/errno.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cmath>
#include <cstring>
#include <limits>
#include <mutex>
#include <span>
#include <unordered_map>
#include <vector>
namespace Libs::Graphics {
static uint64_t BufferDescriptorSize(const ShaderBufferResource& descriptor) {
const uint64_t records = descriptor.NumRecords();
const uint64_t stride = descriptor.Stride();
if (stride != 0 && records > UINT64_MAX / stride) {
EXIT("compute buffer descriptor footprint overflow\n");
static bool FillSourcesDisjoint(std::span<const ShaderRecompiler::IR::DescriptorValue> sources,
GuestRange destination, uint32_t output_buffer = UINT32_MAX) {
for (uint32_t i = 0; i < sources.size(); ++i) {
if (i == output_buffer) continue;
const auto source = DecodeNativeDescriptor<ShaderBufferResource>(sources[i]);
const auto bytes = source.GetSize();
if (source.Base48() < destination.End() && destination.address < source.Base48() + bytes)
return false;
}
return stride == 0 ? records : records * stride;
return true;
}
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const CommandBuffer& buffer) {
const auto& program = *input.stage.program;
const auto& resources = *input.stage.resources;
const auto& resources = input.stage.resources;
if (resources.buffers.size() != program.info.buffers.size()) {
EXIT("compute runtime buffer count does not match shader metadata\n");
}
@@ -54,7 +59,9 @@ bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& in
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& resource = program.info.buffers[i];
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
if (!resource.written && cache.IsMeta(descriptor.Base48())) {
// A metadata resource that is also read is not proven to be a full overwrite. Execute it
// conservatively instead of replacing the dispatch with a coarse full-surface clear.
if ((!resource.written || resource.read) && cache.IsMeta(descriptor.Base48())) {
return false;
}
}
@@ -74,82 +81,115 @@ bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& in
return false;
}
bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x,
bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode,
ShaderBufferResource& resolved_descriptor, uint32_t& resolved_clear,
uint64_t& resolved_size) {
const auto& program = *input.stage.program;
const auto& resources = *input.stage.resources;
if (program.info.buffers.size() != 1 || resources.buffers.size() != 1 ||
!program.info.images.empty() || !program.info.samplers.empty() || program.info.uses_dma ||
!resources.images.empty() || !resources.samplers.empty()) {
const auto& resources = input.stage.resources;
const auto& fill = resources.uniform_fill;
if (fill.kind != ShaderRecompiler::IR::UniformFillKind::Buffer) {
return false;
}
const auto& resource = program.info.buffers.front();
const auto& raw = resources.buffers.front();
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(raw);
if (!resource.formatted || !resource.written || resource.read || resource.atomic ||
resource.scalar || resource.max_byte_extent != 16 || descriptor.Stride() != 16 ||
descriptor.Format() != Prospero::BufferFormat::k32_32_32_32UInt ||
const auto element_size = fill.words * sizeof(uint32_t);
const auto descriptor =
DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[fill.resource]);
constexpr std::array formats {
Prospero::BufferFormat::k32UInt, Prospero::BufferFormat::k32_32UInt,
Prospero::BufferFormat::k32_32_32UInt, Prospero::BufferFormat::k32_32_32_32UInt};
if (descriptor.Stride() != element_size || descriptor.Format() != formats[fill.words - 1] ||
descriptor.SwizzleEnabled() || descriptor.IndexStride() != 0 || descriptor.AddTid() ||
resource.packed_stride != descriptor.PackedStride() || raw.dword_count != 4 ||
program.user_data_base != 0 || resources.user_data.size() != 8) {
descriptor.Base48() == 0) {
return false;
}
for (uint32_t i = 0; i < raw.dword_count; i++) {
if (raw.dwords[i] != resources.user_data[i]) {
return false;
}
}
const uint32_t clear = resources.user_data[4];
if (resources.user_data[5] != clear || resources.user_data[6] != clear ||
resources.user_data[7] != clear) {
if (input.threads_num[0] == 0 || input.threads_num[0] != fill.group_stride[0] ||
input.threads_num[1] != 1 || input.threads_num[2] != 1 || group_x == 0 || group_y != 1 ||
group_z != 1 || mode != (input.dispatch_thread_dimensions ? 0x61u : 0x41u)) {
return false;
}
const bool full_dispatch =
input.dispatch_thread_dimensions && input.threads_num[0] == 64 &&
input.threads_num[1] == 1 && input.threads_num[2] == 1 && group_x != 0 && group_y == 1 &&
group_z == 1 && input.dispatch_threads_num[0] == group_x &&
input.dispatch_threads_num[1] == 1 && input.dispatch_threads_num[2] == 1 &&
input.group_id[0] && !input.group_id[1] && !input.group_id[2] &&
input.thread_ids_num == 1 && input.wave_size == 64 && !input.tg_size_en && mode == 0x61u &&
group_x % input.threads_num[0] == 0 && descriptor.NumRecords() == group_x;
const auto size = BufferDescriptorSize(descriptor);
if (!full_dispatch || size == 0) {
const uint64_t invocations = input.dispatch_thread_dimensions
? group_x
: static_cast<uint64_t>(group_x) * input.threads_num[0];
const auto size = descriptor.GetSize();
if (invocations != descriptor.NumRecords() || size == 0 || size > UINT32_MAX ||
(input.dispatch_thread_dimensions &&
(group_x % input.threads_num[0] != 0 || input.dispatch_threads_num[0] != group_x ||
input.dispatch_threads_num[1] != 1 || input.dispatch_threads_num[2] != 1))) {
return false;
}
if (!FillSourcesDisjoint(resources.buffers, {descriptor.Base48(), size}, fill.resource))
return false;
resolved_descriptor = descriptor;
resolved_clear = clear;
resolved_clear = fill.value;
resolved_size = size;
return true;
}
static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, CommandBuffer& command,
uint32_t group_x, uint32_t group_y, uint32_t group_z,
uint32_t mode) {
bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& input,
CommandBuffer& command, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode) {
const auto& program = *input.stage.program;
const auto& resources = input.stage.resources;
const auto& fill = resources.uniform_fill;
auto& cache = command.GetContext().GetTextureCache();
if (fill.kind == ShaderRecompiler::IR::UniformFillKind::Image) {
if (mode != 0x41u || input.dispatch_thread_dimensions || fill.value > 255 ||
input.threads_num[2] != 1)
return false;
const auto descriptor = DecodeNativeDescriptor<ShaderTextureResource>(resources.images[0]);
const auto& resource = program.info.images[0];
if (descriptor.IsNull() || descriptor.Format() != Prospero::BufferFormat::k8UInt ||
descriptor.Type() != Prospero::ImageType::kColor2DArray || descriptor.MetaCompress() ||
descriptor.WriteCompress() || descriptor.BaseLevel() > descriptor.LastLevel() ||
descriptor.BaseLevel() > descriptor.MaxMip() ||
descriptor.BaseArray5() > descriptor.Depth() || descriptor.DstSelX() != 4)
return false;
const std::array extents {
std::max(1u, (descriptor.Width5() + 1u) >> descriptor.BaseLevel()),
std::max(1u, (descriptor.Height5() + 1u) >> descriptor.BaseLevel()),
descriptor.Depth() - descriptor.BaseArray5() + 1u};
const std::array groups {group_x, group_y, group_z};
for (uint32_t axis = 0; axis < 3; ++axis) {
const uint64_t threads = input.threads_num[axis];
// Guest image writes outside the descriptor dimensions are discarded. Only the
// final workgroup may extend beyond the selected image view.
if (threads == 0 || threads != fill.group_stride[axis] ||
groups[axis] != (extents[axis] + threads - 1) / threads ||
groups[axis] * threads > UINT32_MAX) return false;
}
const auto binding = ResolveTexture(resource, resources.images[0]);
const auto& destination = binding.desc.info.data;
if (!FillSourcesDisjoint(resources.buffers, destination)) return false;
std::scoped_lock lock {cache.m_lock};
const auto& image = cache.GetImage(binding.image_id);
const auto& view = binding.desc.view_info;
if (image.backing.format != vk::Format::eD32SfloatS8Uint || image.info.samples != 1 ||
image.info.stencil != destination || view.base_level >= image.backing.mip_levels ||
view.base_layer >= image.backing.layers || view.layer_count != extents[2] ||
view.layer_count > image.backing.layers - view.base_layer ||
std::max(1u, image.info.extent.width >> view.base_level) != extents[0] ||
std::max(1u, image.info.extent.height >> view.base_level) != extents[1]) return false;
const vk::ImageSubresourceRange range {vk::ImageAspectFlagBits::eStencil, view.base_level,
1, view.base_layer, view.layer_count};
vk::ClearValue clear {};
clear.depthStencil = vk::ClearDepthStencilValue {0.0f, fill.value};
cache.ClearImage(command, binding.image_id, range, clear);
return true;
}
ShaderBufferResource descriptor;
uint32_t packed_clear = 0;
uint64_t size = 0;
if (!ResolveComputeImageClear(input, group_x, group_y, group_z, mode, descriptor, packed_clear,
if (!ResolveComputeBufferFill(input, group_x, group_y, group_z, mode, descriptor, packed_clear,
size)) {
return false;
}
auto& cache = command.GetContext().GetTextureCache();
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
// Recognized metadata-fill shaders access DCC as an ordinary storage buffer and may run
// before the render target is bound. TryConsumeDccFill either consumes registered state
// or retains a PendingDcc fill while allowing the dispatch to run.
const bool registered_metadata =
cache.TryConsumeDccFill(descriptor.Base48(), size, packed_clear);
// Track deferred DCC state while the original dispatch writes the metadata allocation.
cache.TrackDccFill(descriptor.Base48(), size, packed_clear);
static std::atomic<uint32_t> logged_metadata_clears {0};
if (logged_metadata_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("GraphicsRenderDispatchDirect: %s metadata clear shader=0x%016" PRIx64
LOGF("GraphicsRenderDispatchDirect: metadata fill shader=0x%016" PRIx64
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
registered_metadata ? "tracked" : "deferred", input.stage.program->shader_hash,
descriptor.Base48(), size, packed_clear);
}
if (registered_metadata) {
return true;
input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear);
}
return false;
}
@@ -218,11 +258,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
input_info.dispatch_threads_num[2] = thread_group_z;
}
const uint32_t frame_num = static_cast<uint32_t>(m_context.GetGpu().GetFrameNum());
const bool large_workgroup =
(input_info.threads_num[0] * input_info.threads_num[1] * input_info.threads_num[2] >= 512);
const auto& program = *input_info.stage.program;
const auto& resources = *input_info.stage.resources;
const auto& resources = input_info.stage.resources;
if (TryConsumeComputeMetaClear(input_info, buffer)) {
ResetBindings();
return;
@@ -232,15 +269,17 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
ResetBindings();
return;
}
const auto sampled_images = std::count_if(
program.info.images.begin(), program.info.images.end(), [](const auto& image) {
return image.kind == ShaderRecompiler::IR::ResourceKind::Image ||
image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint;
});
const bool large_workgroup =
(input_info.threads_num[0] * input_info.threads_num[1] * input_info.threads_num[2] >= 512);
const bool has_sampler = !program.info.samplers.empty();
static std::atomic<uint32_t> dispatch_log_count {0};
if ((large_workgroup || has_sampler) &&
dispatch_log_count.fetch_add(1, std::memory_order_relaxed) < 512) {
const auto sampled_images = std::count_if(
program.info.images.begin(), program.info.images.end(), [](const auto& image) {
return image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled;
});
const uint32_t frame_num = static_cast<uint32_t>(m_context.GetGpu().GetFrameNum());
LOGF("GraphicsRenderDispatchDirect: frame=%u shader=0x%016" PRIx64
" groups=%ux%ux%u mode=0x%08" PRIx32 " local=%ux%ux%u "
"buffers=%zu textures=%zu sampled=%zu storage=%zu samplers=%zu push=%u\n",
@@ -248,7 +287,10 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
thread_group_z, mode, input_info.threads_num[0], input_info.threads_num[1],
input_info.threads_num[2], program.info.buffers.size(), program.info.images.size(),
sampled_images, program.info.images.size() - sampled_images,
program.info.samplers.size(), program.bindings.push_constant_size);
program.info.samplers.size(),
program.bindings.UsesPushData()
? static_cast<uint32_t>(sizeof(ShaderRecompiler::IR::PushData))
: 0u);
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& buffer = program.info.buffers[i];
const auto r = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
@@ -263,8 +305,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
LOGF(" CS texture[%u]: source=%u usage=%s sampled=%s addr=0x%010" PRIx64
" type=%u fmt=%u extent=%ux%u depth=%u levels=%u tile=%u\n",
i, image.source, image.written ? "read-write" : "read-only",
(image.kind == ShaderRecompiler::IR::ResourceKind::Image ||
image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint)
image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled
? "true"
: "false",
r.Base40(), static_cast<uint32_t>(r.Type()), static_cast<uint32_t>(r.Format()),
@@ -327,11 +368,11 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
buffer.EndRendering();
auto& pipeline =
m_context.GetPipelineCache().CreateComputePipeline(input_info, compute_program);
m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
auto bindings = PrepareBindings(input_info.stage);
FindBuffers(bindings);
if (program.info.uses_dma) {
m_context.GetGpuResources().PrepareBda();
m_context.PrepareBda();
}
RebindBuffers(bindings);
RebindImages(bindings);
@@ -345,8 +386,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
std::any_of(program.info.images.begin(), program.info.images.end(),
[](const auto& image) {
return image.written &&
(image.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
image.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint);
image.resource_class ==
ShaderRecompiler::IR::ImageResourceClass::Storage;
}) ||
has_storage_writes;
if (has_storage_writes) {
@@ -14,7 +14,8 @@ RenderContext::RenderContext(GraphicContext& graphics)
: m_graphics(graphics), m_render_executor(*this), m_command_scheduler(*this, graphics),
m_descriptor_heap(graphics, m_command_scheduler.GetMasterSemaphore()),
m_pipeline_cache(graphics), m_sampler_cache(graphics),
m_gpu_resources(graphics, m_command_scheduler) {
m_buffer_cache(graphics, m_command_scheduler, m_page_manager, m_texture_cache),
m_texture_cache(graphics, m_command_scheduler, m_page_manager, m_buffer_cache) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
@@ -27,12 +28,10 @@ void RenderContext::InitializeGpu(VideoOut::VideoOutDriver* video_out) {
EXIT_IF(m_gpu != nullptr);
m_video_out = video_out;
m_gpu = std::make_unique<GuestGpu>(*this);
m_gpu_resources.SetGpu(m_gpu.get());
}
void RenderContext::ShutdownGpu() {
if (m_gpu != nullptr) {
m_gpu_resources.SetGpu(nullptr);
m_gpu->Shutdown();
m_gpu.reset();
}
@@ -55,6 +54,88 @@ VideoOut::VideoOutDriver& RenderContext::GetVideoOut() const {
return *m_video_out;
}
bool RenderContext::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
// The host reports the faulting byte, not the instruction's access width. Both caches
// resolve its page; guessing a width can cross the end of a valid guest mapping.
constexpr uint64_t fault_size = 1;
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 RenderContext::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 RenderContext::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
if (!GuestRange {vaddr, size}.Valid()) {
return false;
}
std::shared_lock lock(m_mapped_ranges_mutex);
return m_mapped_ranges.Contains(vaddr, size);
}
void RenderContext::MapMemory(uint64_t vaddr, uint64_t size) {
std::lock_guard lock(m_mapped_ranges_mutex);
m_mapped_ranges.Add(vaddr, size);
}
void RenderContext::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_command_scheduler.Active()) {
const auto tick = m_command_scheduler.CurrentTick();
m_command_scheduler.Finish();
m_command_scheduler.WaitPriorityOperations(tick);
}
m_buffer_cache.InvalidateMemory(vaddr, size);
m_texture_cache.UnmapMemory(vaddr, size);
std::lock_guard lock(m_mapped_ranges_mutex);
m_mapped_ranges.Subtract(vaddr, size);
};
// Shutdown still owns the GPU while queued rendering drains, but its command lane no
// longer accepts external work. Use the guest GPU's state for the teardown route.
if (m_gpu == nullptr || m_gpu->IsStopping()) {
unmap();
return;
}
m_gpu->SendCommandSync(unmap);
}
void RenderContext::PrepareBda() {
std::shared_lock lock(m_mapped_ranges_mutex);
m_mapped_ranges.ForEach([this](uint64_t start, uint64_t end) {
m_buffer_cache.SynchronizeBuffersInRange(start, end - start);
});
m_fault_process_pending = true;
}
void RenderContext::RunGarbageCollector() {
if (m_fault_process_pending) {
m_fault_process_pending = false;
m_buffer_cache.ProcessFaultBuffer();
}
m_texture_cache.ProcessDownloadImages();
m_texture_cache.RunGarbageCollector();
m_buffer_cache.RunGarbageCollector();
}
void RenderContext::AddInterruptEq(LibKernel::EventQueue::KernelEqueue eq, int event_id) {
Common::LockGuard lock(m_interrupt_mutex);
+19 -5
View File
@@ -5,8 +5,9 @@
#include "common/assert.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/cache/gpuResourceManager.h"
#include "graphics/host_gpu/renderer/cache/samplerCache.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
@@ -15,6 +16,7 @@
#include "kernel/eventQueue.h"
#include <memory>
#include <shared_mutex>
#include <vector>
namespace Libs::VideoOut {
@@ -42,11 +44,18 @@ public:
PipelineCache& GetPipelineCache() { return m_pipeline_cache; }
DescriptorHeap& GetDescriptorHeap() { return m_descriptor_heap; }
SamplerCache& GetSamplerCache() { return m_sampler_cache; }
GpuResourceManager& GetGpuResources() { return m_gpu_resources; }
BufferCache& GetBufferCache() { return m_gpu_resources.GetBufferCache(); }
TextureCache& GetTextureCache() { return m_gpu_resources.GetTextureCache(); }
BufferCache& GetBufferCache() { return m_buffer_cache; }
TextureCache& GetTextureCache() { return m_texture_cache; }
RenderExecutor& GetRenderExecutor() { return m_render_executor; }
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
[[nodiscard]] bool InvalidateMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
void MapMemory(uint64_t vaddr, uint64_t size);
void UnmapMemory(uint64_t vaddr, uint64_t size);
void PrepareBda();
void RunGarbageCollector();
void AddInterruptEq(LibKernel::EventQueue::KernelEqueue eq, int event_id);
void DeleteInterruptEq(LibKernel::EventQueue::KernelEqueue eq, int event_id);
void TriggerInterrupt(int event_id, uint32_t context_id);
@@ -64,9 +73,14 @@ private:
DescriptorHeap m_descriptor_heap;
PipelineCache m_pipeline_cache;
SamplerCache m_sampler_cache;
GpuResourceManager m_gpu_resources;
PageManager m_page_manager;
BufferCache m_buffer_cache;
TextureCache m_texture_cache;
mutable std::shared_mutex m_mapped_ranges_mutex;
RangeSet m_mapped_ranges;
std::unique_ptr<GuestGpu> m_gpu;
VideoOut::VideoOutDriver* m_video_out = nullptr;
bool m_fault_process_pending = false;
Common::Mutex m_interrupt_mutex;
std::vector<InterruptEqRegistration> m_interrupt_eqs;
File diff suppressed because it is too large. Load diff
+3 -2
View File
@@ -2,13 +2,14 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERDRAW_H_
#include <cstdint>
#include <utility>
namespace Libs::Graphics {
struct ShaderVertexInputInfo;
[[nodiscard]] int32_t ResolveVertexOffset(uint32_t index_offset,
const ShaderVertexInputInfo& vs_input_info);
[[nodiscard]] std::pair<int32_t, uint32_t>
ResolveDrawOffsets(uint32_t index_offset, const ShaderVertexInputInfo& vs_input_info);
} // namespace Libs::Graphics
@@ -84,14 +84,6 @@ struct PipelineStencilDynamicState {
#pragma pack(pop)
inline constexpr bool stencil_face_accesses_attachment(const PipelineStencilStaticState& state,
const PipelineStencilDynamicState& dynamic) {
return state.compareOp != vk::CompareOp::eAlways ||
(dynamic.writeMask != 0 &&
(state.failOp != vk::StencilOp::eKeep || state.passOp != vk::StencilOp::eKeep ||
state.depthFailOp != vk::StencilOp::eKeep));
}
static_assert(std::is_trivially_copyable_v<PipelineStencilStaticState>);
static_assert(std::is_standard_layout_v<PipelineStencilStaticState>);
static_assert(alignof(PipelineStencilStaticState) == 1);
+49 -122
View File
@@ -13,10 +13,8 @@
#include "kernel/pthread.h"
#include "libs/errno.h"
#include <array>
#include <cstring>
#include <limits>
#include <optional>
namespace Libs::Graphics::Sync {
@@ -55,70 +53,9 @@ uint64_t ReadReferenceClock() {
return value;
}
enum class EndOfPipeCompletion { None, Interrupt, Flip, FlipAndInterrupt };
struct EndOfPipeSignal {
CommandBuffer* buffer = nullptr;
uint64_t submit_id = 0;
CommandBufferDebugOp debug_operation = CommandBufferDebugOp::Unknown;
std::array<uint32_t, 4> debug_args = {};
uint64_t debug_data = 0;
std::optional<uint64_t> destination;
EndOfPipeCompletion completion = EndOfPipeCompletion::None;
uint64_t completion_data = 0;
int interrupt_event_id = 0;
};
enum class EndOfPipeWriteSize : uint32_t { Dword = 4, Qword = 8 };
enum class EndOfPipeWriteAction { Write, WriteBack, Interrupt, InterruptWriteBack };
static void ValidateEndOfPipeSignal(const EndOfPipeSignal& signal) {
if (signal.destination.has_value()) {
EXIT_IF(*signal.destination == 0);
}
EXIT_IF(signal.buffer == nullptr);
(void)signal.buffer->Handle();
}
static void RecordEndOfPipeSignal(const EndOfPipeSignal& signal) {
ValidateEndOfPipeSignal(signal);
signal.buffer->SetDebugInfo(static_cast<uint32_t>(signal.debug_operation), signal.submit_id,
signal.debug_args[0], signal.debug_args[1], signal.debug_args[2],
signal.debug_args[3], signal.debug_data);
auto& renderer = signal.buffer->GetContext();
auto& scheduler = renderer.GetCommandScheduler();
if (signal.completion != EndOfPipeCompletion::None) {
EXIT_IF(!scheduler.Active() || signal.buffer != &scheduler.Current());
}
switch (signal.completion) {
case EndOfPipeCompletion::None: return;
case EndOfPipeCompletion::Interrupt: {
const auto context_id = static_cast<uint32_t>(signal.completion_data);
const auto event_id = signal.interrupt_event_id;
scheduler.DeferPriorityOperation([&renderer, event_id, context_id] {
renderer.TriggerInterrupt(event_id, context_id);
});
return;
}
case EndOfPipeCompletion::Flip: {
const auto request_id = signal.completion_data;
scheduler.DeferPriorityOperation(
[&renderer, request_id] { renderer.GetVideoOut().CompleteFlip(request_id); });
return;
}
case EndOfPipeCompletion::FlipAndInterrupt: {
const auto request_id = signal.completion_data;
const auto event_id = signal.interrupt_event_id;
scheduler.DeferPriorityOperation([&renderer, event_id, request_id] {
renderer.GetVideoOut().CompleteFlip(request_id);
renderer.TriggerInterrupt(event_id, 0);
});
return;
}
}
}
static CommandBufferDebugOp DebugOperation(EndOfPipeWriteAction action) {
switch (action) {
case EndOfPipeWriteAction::Write: return CommandBufferDebugOp::EopWrite;
@@ -139,24 +76,20 @@ static void RecordEndOfPipeWrite(uint64_t submit_id, CommandBuffer& buffer, uint
uint64_t value, EndOfPipeWriteSize size,
EndOfPipeWriteAction action, int interrupt_event_id = 0,
uint32_t context_id = 0) {
EXIT_IF(destination == 0);
(void)buffer.Handle();
const auto width = static_cast<uint32_t>(size);
const auto value_low = static_cast<uint32_t>(value);
const auto value_high = static_cast<uint32_t>(value >> 32u);
const bool interrupt = TriggersInterrupt(action);
EndOfPipeSignal signal {
.buffer = &buffer,
.submit_id = submit_id,
.debug_operation = DebugOperation(action),
.debug_args = interrupt ? std::array {width, context_id, value_low, value_high}
: std::array {width, value_low, value_high, 0u},
.debug_data = destination,
.destination = destination,
.completion = interrupt ? EndOfPipeCompletion::Interrupt : EndOfPipeCompletion::None,
.completion_data = interrupt ? context_id : value,
.interrupt_event_id = interrupt_event_id,
};
RecordEndOfPipeSignal(signal);
const auto operation = static_cast<uint32_t>(DebugOperation(action));
if (TriggersInterrupt(action)) {
buffer.SetDebugInfo(operation, submit_id, width, context_id, value_low, value_high,
destination);
TriggerEopEventAtEndOfPipe(buffer, interrupt_event_id, context_id);
} else {
buffer.SetDebugInfo(operation, submit_id, width, value_low, value_high, 0, destination);
}
}
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
@@ -167,15 +100,10 @@ void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst
void WriteAtEndOfPipeGds32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
uint32_t dw_offset, uint32_t dw_num) {
const auto destination = reinterpret_cast<uint64_t>(dst_gpu_addr);
RecordEndOfPipeSignal({
.buffer = &buffer,
.submit_id = submit_id,
.debug_operation = CommandBufferDebugOp::EopWrite,
.debug_args = {dw_offset, dw_num, 0, 0},
.debug_data = destination,
.destination = destination,
});
EXIT_IF(dst_gpu_addr == nullptr);
(void)buffer.Handle();
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWrite), submit_id,
dw_offset, dw_num, 0, 0, reinterpret_cast<uint64_t>(dst_gpu_addr));
}
void WriteAtEndOfPipe64(uint64_t submit_id, CommandBuffer& buffer, uint64_t* dst_gpu_addr,
@@ -273,54 +201,53 @@ void WriteAtEndOfPipeWithInterruptWriteBackFlip32(uint64_t submit_id, CommandBuf
int handle, int index, int flip_mode,
int64_t flip_arg, uint64_t request_id,
int event_id) {
const auto destination = reinterpret_cast<uint64_t>(dst_gpu_addr);
RecordEndOfPipeSignal({
.buffer = &buffer,
.submit_id = submit_id,
.debug_operation = CommandBufferDebugOp::EopWriteBackFlip,
.debug_args = {static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
static_cast<uint32_t>(flip_mode), value},
.debug_data = static_cast<uint64_t>(flip_arg),
.destination = destination,
.completion = EndOfPipeCompletion::FlipAndInterrupt,
.completion_data = request_id,
.interrupt_event_id = event_id,
EXIT_IF(dst_gpu_addr == nullptr);
(void)buffer.Handle();
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWriteBackFlip), submit_id,
static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
static_cast<uint32_t>(flip_mode), value, static_cast<uint64_t>(flip_arg));
auto& renderer = buffer.GetContext();
auto& scheduler = renderer.GetCommandScheduler();
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
scheduler.DeferPriorityOperation([&renderer, event_id, request_id] {
renderer.GetVideoOut().CompleteFlip(request_id);
renderer.TriggerInterrupt(event_id, 0);
});
}
void WriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
uint32_t value, int handle, int index, int flip_mode,
int64_t flip_arg, uint64_t request_id) {
const auto destination = reinterpret_cast<uint64_t>(dst_gpu_addr);
RecordEndOfPipeSignal({
.buffer = &buffer,
.submit_id = submit_id,
.debug_operation = CommandBufferDebugOp::EopFlip,
.debug_args = {static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
static_cast<uint32_t>(flip_mode), value},
.debug_data = static_cast<uint64_t>(flip_arg),
.destination = destination,
.completion = EndOfPipeCompletion::Flip,
.completion_data = request_id,
});
EXIT_IF(dst_gpu_addr == nullptr);
(void)buffer.Handle();
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopFlip), submit_id,
static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
static_cast<uint32_t>(flip_mode), value, static_cast<uint64_t>(flip_arg));
auto& renderer = buffer.GetContext();
auto& scheduler = renderer.GetCommandScheduler();
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
scheduler.DeferPriorityOperation(
[&renderer, request_id] { renderer.GetVideoOut().CompleteFlip(request_id); });
}
void WriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer& buffer, int handle, int index,
int flip_mode, int64_t flip_arg, uint64_t request_id) {
RecordEndOfPipeSignal({
.buffer = &buffer,
.submit_id = submit_id,
.debug_operation = CommandBufferDebugOp::EopOnlyFlip,
.debug_args = {static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
static_cast<uint32_t>(flip_mode), 0},
.debug_data = static_cast<uint64_t>(flip_arg),
.completion = EndOfPipeCompletion::Flip,
.completion_data = request_id,
});
(void)buffer.Handle();
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopOnlyFlip), submit_id,
static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
static_cast<uint32_t>(flip_mode), 0, static_cast<uint64_t>(flip_arg));
auto& renderer = buffer.GetContext();
auto& scheduler = renderer.GetCommandScheduler();
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
scheduler.DeferPriorityOperation(
[&renderer, request_id] { renderer.GetVideoOut().CompleteFlip(request_id); });
}
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, int event_id, uint32_t context_id) {
ValidateEndOfPipeSignal({.buffer = &buffer});
(void)buffer.Handle();
auto& renderer = buffer.GetContext();
auto& scheduler = renderer.GetCommandScheduler();
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
@@ -1,29 +1,17 @@
#include "gpu_tiler_standard.inc"
uint standard64_offset(uvec2 p) {
uint x = p.x, y = p.y;
uint offset = standard_offset(p);
switch (ELEMENT_BYTES) {
case 1: return (x & 0x0fu) ^ ((x << 5) & 0x200u) ^ ((x << 6) & 0x800u) ^
((x << 7) & 0x2000u) ^ ((x << 8) & 0x8000u) ^
((y << 4) & 0x1f0u) ^ ((y << 5) & 0x400u) ^
((y << 6) & 0x1000u) ^ ((y << 7) & 0x4000u);
case 2: return ((x << 1) & 0x00eu) ^ ((x << 4) & 0x080u) ^
((x << 5) & 0x200u) ^ ((x << 6) & 0x800u) ^
((x << 7) & 0x2000u) ^ ((x << 8) & 0x8000u) ^
((y << 4) & 0x070u) ^ ((y << 5) & 0x100u) ^
((y << 6) & 0x400u) ^ ((y << 7) & 0x1000u) ^ ((y << 8) & 0x4000u);
case 4: return ((x << 2) & 0x00cu) ^ ((x << 5) & 0x080u) ^
((x << 6) & 0x200u) ^ ((x << 7) & 0x800u) ^
((x << 8) & 0x2000u) ^ ((x << 9) & 0x8000u) ^
((y << 4) & 0x070u) ^ ((y << 5) & 0x100u) ^
((y << 6) & 0x400u) ^ ((y << 7) & 0x1000u) ^ ((y << 8) & 0x4000u);
case 8: return ((x << 3) & 0x008u) ^ ((x << 5) & 0x0c0u) ^
((x << 6) & 0x200u) ^ ((x << 7) & 0x800u) ^
((x << 8) & 0x2000u) ^ ((x << 9) & 0x8000u) ^
((y << 4) & 0x030u) ^ ((y << 6) & 0x100u) ^
((y << 7) & 0x400u) ^ ((y << 8) & 0x1000u) ^ ((y << 9) & 0x4000u);
default: return ((x << 6) & 0x0c0u) ^ ((x << 7) & 0x200u) ^
((x << 8) & 0x800u) ^ ((x << 9) & 0x2000u) ^
((x << 10) & 0x8000u) ^ ((y << 4) & 0x030u) ^
((y << 6) & 0x100u) ^ ((y << 7) & 0x400u) ^
((y << 8) & 0x1000u) ^ ((y << 9) & 0x4000u);
case 1: return offset ^ ((x << 7) & 0x2000u) ^ ((x << 8) & 0x8000u) ^
((y << 6) & 0x1000u) ^ ((y << 7) & 0x4000u);
case 2: return offset ^ ((x << 7) & 0x2000u) ^ ((x << 8) & 0x8000u) ^
((y << 7) & 0x1000u) ^ ((y << 8) & 0x4000u);
case 4: return offset ^ ((x << 8) & 0x2000u) ^ ((x << 9) & 0x8000u) ^
((y << 7) & 0x1000u) ^ ((y << 8) & 0x4000u);
case 8: return offset ^ ((x << 8) & 0x2000u) ^ ((x << 9) & 0x8000u) ^
((y << 8) & 0x1000u) ^ ((y << 9) & 0x4000u);
default: return offset ^ ((x << 9) & 0x2000u) ^ ((x << 10) & 0x8000u) ^
((y << 8) & 0x1000u) ^ ((y << 9) & 0x4000u);
}
}
+20 -110
View File
@@ -18,45 +18,12 @@
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/vma.h"
#include <algorithm>
#include <atomic>
#include <cinttypes>
namespace Libs::Graphics {
namespace {
struct MemoryStats {
std::atomic_uint64_t allocated[VK_MAX_MEMORY_TYPES] {};
std::atomic_uint64_t count[VK_MAX_MEMORY_TYPES] {};
};
MemoryStats g_memory_stats;
void TrackAllocationImpl(const VulkanMemory& memory) {
g_memory_stats.allocated[memory.type] += memory.requirements.size;
g_memory_stats.count[memory.type]++;
}
void UntrackAllocationImpl(const VulkanMemory& memory) {
EXIT_IF(g_memory_stats.allocated[memory.type] < memory.requirements.size);
EXIT_IF(g_memory_stats.count[memory.type] == 0);
g_memory_stats.allocated[memory.type] -= memory.requirements.size;
g_memory_stats.count[memory.type]--;
}
} // namespace
void VulkanTrackAllocation(const VulkanMemory& memory) {
TrackAllocationImpl(memory);
}
void VulkanUntrackAllocation(const VulkanMemory& memory) {
UntrackAllocationImpl(memory);
}
bool GraphicContext::CreateAllocator() {
KYTY_PROFILER_FUNCTION();
EXIT_IF(instance == nullptr || physical_device == nullptr || device == nullptr ||
@@ -79,7 +46,7 @@ bool GraphicContext::CreateAllocator() {
const auto result = static_cast<vk::Result>(vmaCreateAllocator(&info, &allocator));
if (result != vk::Result::eSuccess) {
LOGF("vmaCreateAllocator failed: %s\n", VulkanToString(result).c_str());
LOGF("vmaCreateAllocator failed: %s\n", vk::to_string(result).c_str());
return false;
}
return true;
@@ -93,11 +60,6 @@ void GraphicContext::DestroyAllocator() {
allocator = nullptr;
}
uint64_t VulkanNextMemoryUniqueId() {
static std::atomic_uint64_t sequence = 0;
return ++sequence;
}
void GraphicContext::LogMemoryBudget() const {
if (allocator == nullptr || physical_device == nullptr) {
return;
@@ -166,96 +128,44 @@ uint64_t GraphicContext::GetTotalMemoryBudget() const {
return std::max(local, available > system_reserve ? available - system_reserve : uint64_t {0});
}
void GraphicContext::CreateBuffer(uint64_t size, VulkanBuffer& buffer) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(allocator == nullptr || buffer.buffer != nullptr ||
buffer.memory.allocation != nullptr || size == 0);
vk::BufferCreateInfo buffer_info {};
buffer_info.sType = vk::StructureType::eBufferCreateInfo;
buffer_info.size = size;
buffer_info.usage = buffer.usage;
buffer_info.sharingMode = vk::SharingMode::eExclusive;
VmaAllocationCreateInfo alloc_info {};
alloc_info.requiredFlags =
static_cast<vk::MemoryPropertyFlags::MaskType>(buffer.memory.property);
alloc_info.preferredFlags =
static_cast<vk::MemoryPropertyFlags::MaskType>(buffer.memory.preferred_property);
vk::Buffer::CType native_buffer = VK_NULL_HANDLE;
const auto result = static_cast<vk::Result>(vmaCreateBuffer(
allocator, static_cast<const vk::BufferCreateInfo::NativeType*>(buffer_info), &alloc_info,
&native_buffer, &buffer.memory.allocation, &buffer.memory.allocation_info));
buffer.buffer = native_buffer;
if (result != vk::Result::eSuccess) {
LogMemoryBudget();
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
device.getBufferMemoryRequirements(buffer.buffer, &buffer.memory.requirements);
buffer.memory.type = buffer.memory.allocation_info.memoryType;
buffer.memory.memory = buffer.memory.allocation_info.deviceMemory;
buffer.memory.offset = buffer.memory.allocation_info.offset;
buffer.memory.unique_id = VulkanNextMemoryUniqueId();
buffer.buffer_size = size;
VulkanTrackAllocation(buffer.memory);
}
bool GraphicContext::CreateImage(const vk::ImageCreateInfo& image_info, VulkanImage& image) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(allocator == nullptr || image.image != nullptr || image.memory.allocation != nullptr);
EXIT_IF(allocator == nullptr || image.image != nullptr || image.allocation != nullptr);
auto& memory = image.memory;
VmaAllocationCreateInfo alloc_info {};
alloc_info.requiredFlags = static_cast<vk::MemoryPropertyFlags::MaskType>(memory.property);
alloc_info.preferredFlags =
static_cast<vk::MemoryPropertyFlags::MaskType>(memory.preferred_property);
alloc_info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
vk::Image::CType native_image = VK_NULL_HANDLE;
const auto result = static_cast<vk::Result>(
const auto result = static_cast<vk::Result>(
vmaCreateImage(allocator, static_cast<const vk::ImageCreateInfo::NativeType*>(image_info),
&alloc_info, &native_image, &memory.allocation, &memory.allocation_info));
&alloc_info, &native_image, &image.allocation, nullptr));
image.image = native_image;
if (result != vk::Result::eSuccess) {
LogMemoryBudget();
return false;
}
device.getImageMemoryRequirements(image.image, &memory.requirements);
memory.type = memory.allocation_info.memoryType;
memory.memory = memory.allocation_info.deviceMemory;
memory.offset = memory.allocation_info.offset;
memory.unique_id = VulkanNextMemoryUniqueId();
VulkanTrackAllocation(memory);
image.format = image_info.format;
image.image_type = image_info.imageType;
image.extent = image_info.extent;
image.layers = image_info.arrayLayers;
image.mip_levels = image_info.mipLevels;
image.samples = static_cast<uint32_t>(image_info.samples);
image.usage = image_info.usage;
image.flags = image_info.flags;
image.state = {.layout = image_info.initialLayout};
image.subresource_states.clear();
return true;
}
void GraphicContext::DeleteImage(VulkanImage& image) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(allocator == nullptr || image.image == nullptr || image.memory.allocation == nullptr);
EXIT_IF(allocator == nullptr || image.image == nullptr || image.allocation == nullptr);
auto& memory = image.memory;
VulkanUntrackAllocation(memory);
vmaDestroyImage(allocator, image.image, memory.allocation);
image.image = nullptr;
memory.memory = nullptr;
memory.allocation = nullptr;
memory.allocation_info = {};
memory.offset = 0;
}
void GraphicContext::MapMemory(VulkanMemory& memory, void*& data) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(allocator == nullptr || memory.allocation == nullptr);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(vmaMapMemory(allocator, memory.allocation,
&data)) != vk::Result::eSuccess);
}
void GraphicContext::UnmapMemory(VulkanMemory& memory) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(allocator == nullptr || memory.allocation == nullptr);
vmaUnmapMemory(allocator, memory.allocation);
vmaDestroyImage(allocator, image.image, image.allocation);
image.image = nullptr;
image.allocation = nullptr;
}
} // namespace Libs::Graphics
-15
View File
@@ -1,15 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_VMA_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_VMA_H_
#include "common/common.h"
#include "graphics/host_gpu/graphicContext.h"
namespace Libs::Graphics {
uint64_t VulkanNextMemoryUniqueId();
void VulkanTrackAllocation(const VulkanMemory& memory);
void VulkanUntrackAllocation(const VulkanMemory& memory);
} // namespace Libs::Graphics
#endif /* EMULATOR_SRC_GRAPHICS_HOST_GPU_VMA_H_ */
+18 -19
View File
@@ -17,14 +17,22 @@ struct FormatMapping {
constexpr FormatMapping kFormatMappings[] = {
{Prospero::BufferFormat::k8UNorm, vk::Format::eR8Unorm},
{Prospero::BufferFormat::k8SNorm, vk::Format::eR8Snorm},
{Prospero::BufferFormat::k8UScaled, vk::Format::eR8Uscaled},
{Prospero::BufferFormat::k8SScaled, vk::Format::eR8Sscaled},
{Prospero::BufferFormat::k8UInt, vk::Format::eR8Uint},
{Prospero::BufferFormat::k8SInt, vk::Format::eR8Sint},
{Prospero::BufferFormat::k16UNorm, vk::Format::eR16Unorm},
{Prospero::BufferFormat::k16SNorm, vk::Format::eR16Snorm},
{Prospero::BufferFormat::k16UScaled, vk::Format::eR16Uscaled},
{Prospero::BufferFormat::k16SScaled, vk::Format::eR16Sscaled},
{Prospero::BufferFormat::k16UInt, vk::Format::eR16Uint},
{Prospero::BufferFormat::k16SInt, vk::Format::eR16Sint},
{Prospero::BufferFormat::k16Float, vk::Format::eR16Sfloat},
{Prospero::BufferFormat::k8_8UNorm, vk::Format::eR8G8Unorm},
{Prospero::BufferFormat::k8_8SNorm, vk::Format::eR8G8Snorm},
{Prospero::BufferFormat::k8_8UScaled, vk::Format::eR8G8Uscaled},
{Prospero::BufferFormat::k8_8SScaled, vk::Format::eR8G8Sscaled},
{Prospero::BufferFormat::k8_8UInt, vk::Format::eR8G8Uint},
{Prospero::BufferFormat::k8_8SInt, vk::Format::eR8G8Sint},
{Prospero::BufferFormat::k32UInt, vk::Format::eR32Uint},
@@ -32,14 +40,19 @@ constexpr FormatMapping kFormatMappings[] = {
{Prospero::BufferFormat::k32Float, vk::Format::eR32Sfloat},
{Prospero::BufferFormat::k16_16UNorm, vk::Format::eR16G16Unorm},
{Prospero::BufferFormat::k16_16SNorm, vk::Format::eR16G16Snorm},
{Prospero::BufferFormat::k16_16UScaled, vk::Format::eR16G16Uscaled},
{Prospero::BufferFormat::k16_16SScaled, vk::Format::eR16G16Sscaled},
{Prospero::BufferFormat::k16_16UInt, vk::Format::eR16G16Uint},
{Prospero::BufferFormat::k16_16SInt, vk::Format::eR16G16Sint},
{Prospero::BufferFormat::k16_16Float, vk::Format::eR16G16Sfloat},
{Prospero::BufferFormat::k11_11_10Float, vk::Format::eB10G11R11UfloatPack32},
{Prospero::BufferFormat::k10_10_10_2UNorm, vk::Format::eA2B10G10R10UnormPack32},
{Prospero::BufferFormat::k10_10_10_2SNorm, vk::Format::eA2B10G10R10SnormPack32},
{Prospero::BufferFormat::k10_10_10_2UInt, vk::Format::eA2B10G10R10UintPack32},
{Prospero::BufferFormat::k8_8_8_8UNorm, vk::Format::eR8G8B8A8Unorm},
{Prospero::BufferFormat::k8_8_8_8SNorm, vk::Format::eR8G8B8A8Snorm},
{Prospero::BufferFormat::k8_8_8_8UScaled, vk::Format::eR8G8B8A8Uscaled},
{Prospero::BufferFormat::k8_8_8_8SScaled, vk::Format::eR8G8B8A8Sscaled},
{Prospero::BufferFormat::k8_8_8_8UInt, vk::Format::eR8G8B8A8Uint},
{Prospero::BufferFormat::k8_8_8_8SInt, vk::Format::eR8G8B8A8Sint},
{Prospero::BufferFormat::k32_32UInt, vk::Format::eR32G32Uint},
@@ -47,6 +60,8 @@ constexpr FormatMapping kFormatMappings[] = {
{Prospero::BufferFormat::k32_32Float, vk::Format::eR32G32Sfloat},
{Prospero::BufferFormat::k16_16_16_16UNorm, vk::Format::eR16G16B16A16Unorm},
{Prospero::BufferFormat::k16_16_16_16SNorm, vk::Format::eR16G16B16A16Snorm},
{Prospero::BufferFormat::k16_16_16_16UScaled, vk::Format::eR16G16B16A16Uscaled},
{Prospero::BufferFormat::k16_16_16_16SScaled, vk::Format::eR16G16B16A16Sscaled},
{Prospero::BufferFormat::k16_16_16_16UInt, vk::Format::eR16G16B16A16Uint},
{Prospero::BufferFormat::k16_16_16_16SInt, vk::Format::eR16G16B16A16Sint},
{Prospero::BufferFormat::k16_16_16_16Float, vk::Format::eR16G16B16A16Sfloat},
@@ -63,9 +78,9 @@ constexpr FormatMapping kFormatMappings[] = {
{Prospero::BufferFormat::k8_8_8_8Srgb, vk::Format::eR8G8B8A8Srgb},
{Prospero::BufferFormat::k9_9_9_5Float, vk::Format::eE5B9G9R9UfloatPack32},
{Prospero::BufferFormat::k5_6_5UNorm, vk::Format::eB5G6R5UnormPack16},
{Prospero::BufferFormat::k5_5_5_1UNorm, vk::Format::eR5G5B5A1UnormPack16},
{Prospero::BufferFormat::k5_5_5_1UNorm, vk::Format::eA1R5G5B5UnormPack16},
{Prospero::BufferFormat::k1_5_5_5UNorm, vk::Format::eR5G5B5A1UnormPack16},
{Prospero::BufferFormat::k4_4_4_4UNorm, vk::Format::eR4G4B4A4UnormPack16},
{Prospero::BufferFormat::kFmask8_S4_F4, vk::Format::eR32Sfloat},
{Prospero::BufferFormat::kBc1UNorm, vk::Format::eBc1RgbaUnormBlock},
{Prospero::BufferFormat::kBc1Srgb, vk::Format::eBc1RgbaSrgbBlock},
{Prospero::BufferFormat::kBc2UNorm, vk::Format::eBc2UnormBlock},
@@ -96,22 +111,6 @@ constexpr auto kFormatLookup = MakeFormatLookup();
} // namespace
std::string VulkanToString(vk::Result value) {
return vk::to_string(value);
}
std::string VulkanToString(vk::Format value) {
return vk::to_string(value);
}
std::string VulkanToString(vk::ImageLayout value) {
return vk::to_string(value);
}
std::string VulkanToString(vk::QueueFlags value) {
return vk::to_string(value);
}
vk::Format VulkanFormat(Prospero::BufferFormat guest_format) {
const auto index = static_cast<size_t>(guest_format);
return index < kFormatLookup.size() ? kFormatLookup[index] : vk::Format::eUndefined;
@@ -119,7 +118,7 @@ vk::Format VulkanFormat(Prospero::BufferFormat guest_format) {
void RequireVulkanSuccess(vk::Result result, const char* operation) {
if (result != vk::Result::eSuccess) {
EXIT("%s failed: %s (%d)\n", operation, VulkanToString(result).c_str(),
EXIT("%s failed: %s (%d)\n", operation, vk::to_string(result).c_str(),
static_cast<int>(result));
}
}
-5
View File
@@ -25,10 +25,6 @@ namespace Libs::Graphics {
using VulkanMemoryBarrier = vk::MemoryBarrier;
std::string VulkanToString(vk::Result value);
std::string VulkanToString(vk::Format value);
std::string VulkanToString(vk::ImageLayout value);
std::string VulkanToString(vk::QueueFlags value);
vk::Format VulkanFormat(Prospero::BufferFormat guest_format);
void RequireVulkanSuccess(vk::Result result, const char* operation);
@@ -42,7 +38,6 @@ void SetVulkanObjectNameF(vk::Device device, Handle handle, fmt::format_string<A
const auto name = fmt::format(format, std::forward<Args>(args)...);
vk::DebugUtilsObjectNameInfoEXT info {};
info.sType = vk::StructureType::eDebugUtilsObjectNameInfoEXT;
info.objectType = Handle::objectType;
info.objectHandle = static_cast<uint64_t>(
reinterpret_cast<uintptr_t>(static_cast<typename Handle::CType>(handle)));
-42
View File
@@ -1,42 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEOVERLAY_H_
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEOVERLAY_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <memory>
union SDL_Event;
namespace Libs::Graphics {
struct GraphicContext;
struct ImeVisualState {
bool active;
uint64_t revision;
};
void InitializeImeInput();
void ShutdownImeInput();
bool ProcessImeInput(const SDL_Event& event);
ImeVisualState GetImeVisualState() noexcept;
class ImeOverlay final {
public:
explicit ImeOverlay(GraphicContext& graphics);
~ImeOverlay();
KYTY_CLASS_NO_COPY(ImeOverlay);
[[nodiscard]] bool PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count);
void Record(vk::CommandBuffer command, vk::ImageView target);
void ReleaseVulkan();
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEOVERLAY_H_
+1 -1
View File
@@ -25,7 +25,7 @@ public:
CommandBuffer* producer = nullptr);
[[nodiscard]] Frame* PrepareLastFrame();
[[nodiscard]] bool IsGuestPaused() const noexcept;
[[nodiscard]] bool NeedsImeRefresh() const noexcept;
[[nodiscard]] bool NeedsSystemOverlayRefresh() const noexcept;
[[nodiscard]] RenderContext& Renderer() const noexcept;
void Present(Frame& frame, bool reuse = false);
void Discard(Frame& frame);
Loaded 100 of 259 files, more files were not shown because too many files have changed in this diff. Show more