Compare commits

...
Author SHA1 Message Date
83db2efec3 shader: pack v_cvt_pkrtz_f16_f32 with the native half conversion (GTA V) (#1520)
* shader: pack v_cvt_pkrtz_f16_f32 with the native half conversion

The round-toward-zero pack was emulated with integer operations: about 20 to 30 operations
per half. GTA V uses it in 613 of its 656 pixel shaders, about 16 times in each G-buffer
pixel shader.

The pair is now packed with PackHalf2x16, unpacked again, and each half whose magnitude
exceeds its input's takes one unit off. Half signs and magnitudes are separate, so that is
the next value toward zero, whatever rounding the native conversion uses as long as it
returns one of the two neighbours. Same bits as before for every finite input and infinity;
a NaN stays a NaN. Checked against the old code on 2 million values on the CPU, and on an
Apple M5 GPU through Metal (400 000 values, subnormals included, safe and fast math).

GTA V on an Apple M5 (MoltenVK), 1080p, fixed script camera, three places, A B B A between
launches: -1.7 to -2.8 ms per frame (+4 to +6 %).

Scope: generic
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* shader: remove half-pack commentary and unused shift helper

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-16 02:56:35 +02:00
opcod3 13e4c315b6 agc: Cleanup names so they match NIDs (#1508)
* agc: Cleanup names so they match NIDs

Simple commit tidying up libSceAgc function names so they match their
NIDs. This just a tidy-up commit that makes it more obvious what each
function is.

* agc: Preserve unknown fused shader size name
2026-10-16 02:16:19 +02:00
Felipeandnmzik 4242ec5a64 Add add-on (DLC) support (#1519)
Add add-on content support

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-16 02:09:39 +02:00
NeySlim c6b8210d7c test: fix the Linux and Windows CI test builds (#1504) 2026-10-16 01:43:24 +02:00
Mr. Big Britchesandnmzik 6568fe9a84 Implement Missing NGS2 System Functions Lock & Unlock (#1542)
* Implement NGS2 SystemLock

* Implement NGS2 SystemUnlock

* Add guard for system_handle value of 0

* Return correct error code from system_handle guard

* Validate NGS2 system handles before locking

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-16 01:39:40 +02:00
FelipeandCopilot App 57c4a6ee11 Make trophy toast duration configurable (5-10s, default 6s) (#1538)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-16 01:37:51 +02:00
Hector Mödinger S. 54abbf48f2 kernel: implement vectored writes for profile index saves 2026-10-16 01:27:57 +02:00
overlordxrz-source 56d3ce98f0 kernel: implement path-based truncate for replay files 2026-10-16 01:27:53 +02:00
nmzik cbe5b62947 file: keep independent Linux file handles coherent 2026-10-16 01:26:56 +02:00
nmzikandshadowbeat070 f7ae86c982 kernel: optimize virtual range edits with an ordered map
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-10-16 00:58:57 +02:00
shadowbeat070 30a4673c60 libs: port entitlement requests and fixed-refresh VRR status 2026-10-16 00:16:01 +02:00
nmzik a5ceb48184 agc: encode acquire-memory byte ranges in standard packets 2026-10-15 23:34:42 +02:00
nmzik c8caa27936 shader: preserve folded native constants for descriptor provenance 2026-10-15 23:22:30 +02:00
nmzik e4019af522 shader: remove disabled legacy stage register checks 2026-10-15 22:31:45 +02:00
nmzik f7226ca7dc graphics: emit PS5 register object and primitive IDs to RT7 2026-10-15 22:28:01 +02:00
nmzik d172ab75ca shader: reuse selector domains proven by prior resource accesses 2026-10-15 22:28:01 +02:00
nmzik bcf13e101e shader: validate texture mip ranges and channel selectors 2026-10-15 22:28:01 +02:00
nmzik 2caf2f7e4e shader: retain runtime count bounds for finite descriptor choices 2026-10-15 22:28:01 +02:00
nmzik df92a08990 shader: report descriptor materialization rejection context 2026-10-15 22:28:01 +02:00
nmzikandfxpw e291589177 shader: resolve GPU-selected parent pointers and bounded child image tables
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:01 +02:00
nmzikandfxpw 2285a25182 shader: prove narrowing loop masks through shared implication analysis
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:01 +02:00
nmzikandfxpw a50e4de389 shader: recognize reversed unsigned greater-than resource bounds
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:01 +02:00
nmzikandfxpw 959bee4799 graphics: checkpoint live Vulkan pipeline cache after new compilations
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:01 +02:00
nmzikandfxpw 232e2f2593 shader: bound waterfall buffer descriptors with same-lane guards
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:01 +02:00
nmzikandfxpw b4406b0467 graphics: render packed 11-11-10 UNorm targets with exact rounding
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:01 +02:00
nmzik fb213e8ae5 shader: share packed color component encoding 2026-10-15 22:28:01 +02:00
nmzik 87d938139d graphics: log complete per-target state before format resolution 2026-10-15 22:28:01 +02:00
nmzik 2a4ccaa5d5 shader: share proven selector domains for paired images and samplers 2026-10-15 22:28:01 +02:00
nmzikandfxpw 9c04abacc5 shader: resolve shared selector keys once per materialization
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:01 +02:00
nmzikandfxpw 5916a184cc shader: preserve affine image heap candidate domains
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:01 +02:00
nmzik fcab4e5d0d shader: trace unsupported sampled image candidate provenance 2026-10-15 22:28:01 +02:00
nmzik fe23334ff0 shader: remove unreachable candidate diagnostic branches 2026-10-15 22:28:01 +02:00
nmzikandfxpw 81169d77ea shader: materialize compact image and paired sampler tables
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:01 +02:00
nmzik 7d24972b8f shader: report rejected indirect buffer candidate context 2026-10-15 22:28:01 +02:00
nmzik 1d3fd07fe6 shader: share indirect buffer format geometry lookup 2026-10-15 22:28:01 +02:00
nmzikandfxpw 28766a915b audio: decode PS5 ATRAC9 discrete multichannel streams
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:01 +02:00
nmzikandfxpw 279bc95941 graphics: track HTile clears across PS5 depth array slices
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:01 +02:00
nmzik 12c522bba5 graphics: log cached GPU completion beside dispatch submission ticks 2026-10-15 22:28:01 +02:00
nmzik 65a6f30f51 shader: compare real nested descriptor pointer instructions 2026-10-15 22:28:01 +02:00
nmzikandfxpw bb2d6dbeb3 shader: share scalar condition results with direct subgroup votes
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:01 +02:00
nmzik 50844badbf shader: map debug SPIR-V blocks to native RDNA2 PCs 2026-10-15 22:28:01 +02:00
nmzik c1f6838e40 shader: share repeated raw indirect buffer load bodies 2026-10-15 22:28:01 +02:00
nmzikandfxpw 6455546f26 shader: admit bounded multi-component formatted buffer loads
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:00 +02:00
nmzik dcc642551f graphics: classify signed byte textures for native sampling 2026-10-15 22:28:00 +02:00
nmzikandfxpw dd429ef858 shader: decode high-half formatted buffer stores
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:00 +02:00
nmzikandfxpw bc2664cefa shader: decode comparison gathers with explicit LOD
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-15 22:28:00 +02:00
nmzikandfxpw 8b81f665a2 shader: separate indirect buffer candidates from tracked root limits
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 21:19:38 +02:00
nmzikandfxpw 1039c54f5d shader: share indirect buffer array access after an empty root
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 21:17:44 +02:00
nmzikandfxpw b24ce3440d shader: track buffer liveness and offsets by resource count
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 21:11:14 +02:00
nmzikandfxpw 2f6ee0e91d shader: support RDNA2 high-half short buffer stores
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 20:55:50 +02:00
nmzik 6b1c4a7a71 test: model bounded scalar selector batches in native CFG coverage 2026-10-14 20:55:39 +02:00
nmzik 166b9a5eea test: dispatch null texture coverage with the intended wave size 2026-10-14 20:47:25 +02:00
nmzik 5313181ecf shader: bound workgroup selector prefixes from runtime starting indices 2026-10-14 20:39:25 +02:00
nmzikandfxpw ceccd746d8 shader: resolve workgroup-selected descriptor prefixes from dispatch arguments
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 20:29:48 +02:00
nmzik 82470c7181 shader: share workgroup and selector count resolution 2026-10-14 19:45:31 +02:00
nmzikandfxpw 4843d8d942 shader: support packed D16 XYZW formatted buffer stores
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 19:35:08 +02:00
nmzik c914435f20 shader: plan finite GPU-selected RGBA16 buffer stores 2026-10-14 19:13:16 +02:00
nmzik 7468e4f60a shader: share descriptor dispatch for scalar and wide buffer stores 2026-10-14 19:01:14 +02:00
nmzikandfxpw b6da068313 shader: preserve typed candidates for unfiltered indirect image access
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 18:57:26 +02:00
nmzikandfxpw 127cf8d46f shader: route image stores through shared indirect descriptor access
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 18:39:40 +02:00
nmzikandfxpw e0586cc85a shader: decode unsigned byte loads into preserved low halves
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 18:21:58 +02:00
nmzik 302240da70 shader: lower complete wave minima for sparse fragment subgroups 2026-10-14 18:03:48 +02:00
nmzik 892e70c8a1 graphics: bind unbound textures as native null descriptors 2026-10-14 17:56:47 +02:00
nmzikandfxpw 7c9da0e56f shader: share finite texture selection through masked first-lane reads
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 17:29:49 +02:00
nmzik 6d6da57a95 shader: share formatted stores for GPU-selected signed byte records 2026-10-14 17:10:02 +02:00
nmzik 04cb4c6553 shader: separate exact descriptor row bounds from pointer offsets 2026-10-14 16:55:47 +02:00
nmzikandfxpw 487bbf95fd shader: share nested pointer-table image and sampler selection
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 16:40:15 +02:00
nmzik 3a925efddf graphics: preserve sparse spans in rebased mip-tail views 2026-10-14 16:07:49 +02:00
nmzik aeecc9343a test: give native binding fixture valid nonempty buffer descriptors 2026-10-14 16:02:33 +02:00
nmzikandfxpw 8c7fe539b8 graphics: represent rebased contiguous mip-tail texture views
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 15:56:45 +02:00
nmzikandfxpw c88f05f7b5 graphics: share signed storage conversion and support R16 image writes
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 15:36:29 +02:00
nmzikandfxpw f3c017b168 shader: fold reads through empty scalar descriptor tables
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 15:17:03 +02:00
nmzikandfxpw 4b549b9cb3 shader: prove constant post-tested image selector loops
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 15:11:12 +02:00
nmzik 9cc0bba80b shader: bound GPU-selected record descriptors by scalar table geometry 2026-10-14 14:45:56 +02:00
nmzik 150bcb06a9 shader: share runtime buffer format conversion across loads 2026-10-14 14:29:40 +02:00
nmzik 280b87adec shader: allow reverse left shifts to use shared SDWA partial writes 2026-10-14 05:20:23 +02:00
nmzikandfxpw 26d326d1b0 shader: share bounded record descriptors for packed loads and byte stores
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 05:07:24 +02:00
nmzik 4cf5a96b79 shader: reuse prepared binding offsets and shared resource switch 2026-10-14 04:46:46 +02:00
nmzikandfxpw 7bba785963 shader: load bytes and halfwords from GPU-selected buffers
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 04:35:21 +02:00
nmzikandfxpw 8d18be71db shader: decode upper-half byte stores through shared operand selection
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 04:20:02 +02:00
nmzikandfxpw 6abfe3bbe4 shader: share formatted loads and support packed D16 XYZW buffers
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 04:09:57 +02:00
nmzikandfxpw 1905b28b3c shader: decode packed UNorm image loads through shared conversion
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 03:53:32 +02:00
nmzikandfxpw 8e4c3b5e14 shader: prove finite masked selectors for writable buffer tables
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 03:48:26 +02:00
nmzikandfxpw be5915c248 graphics: skip GPU-clean empty indirect dispatches before shader preparation
Co-authored-by: fxpw <fxpw@outlook.com>
2026-10-14 03:36:33 +02:00
nmzik 9e41d8320a shader: support returning 64-bit LDS max and compare-exchange 2026-10-13 21:33:50 +00:00
nmzik e998f21e71 shader: reuse EXEC proof contexts across shared predicate graphs 2026-10-13 21:33:50 +00:00
nmzik 3197ef093c test: align scalar image tables and buffer reads with PS5 semantics 2026-10-13 21:33:50 +00:00
nmzik fa0e81bfba shader: zero out-of-range scalar descriptor reads 2026-10-13 21:33:50 +00:00
nmzik 01ce973882 renderer: preserve guest writes to reused metadata buffers 2026-10-13 21:33:50 +00:00
nmzik f57d2c0965 shader: keep unbounded material selectors on bounded GPU heaps 2026-10-13 21:33:50 +00:00
nmzik 735a229236 shader: preserve native types in indirect image tables 2026-10-13 21:33:50 +00:00
nmzik 9ade7e3582 shader: prove inactive resources across masked traversal merges 2026-10-13 21:33:50 +00:00
nmzik 259a40abfc memory: honor sparse AMM unmaps and restore failed GPU ranges 2026-10-13 21:33:50 +00:00
nmzik f1577a5a94 shader: memoize bounded material record bit analysis 2026-10-13 21:33:50 +00:00
nmzik d08a6cef64 shader: specialize guarded packed material selectors 2026-10-13 21:33:50 +00:00
nmzik 907c65f54c shader: track material offsets scaled after lane selection 2026-10-13 21:33:50 +00:00
nmzik fe6499b86e shader: preserve clamped scalar material selector provenance 2026-10-13 21:33:50 +00:00
nmzik 6309ac838d shader: restrict shared-list descriptor bounds to overlapping producers 2026-10-13 21:33:50 +00:00
nmzik 19799b3079 shader: simplify invariant execution masks in descriptor proofs 2026-10-13 21:33:50 +00:00
nmzik 4e16234d32 shader: bound signed image loops from their initial descriptor index 2026-10-13 21:33:50 +00:00
Ali Almohaya 3c9da9c3f4 graphics: decode both channels of RG32 float metadata clears 2026-10-13 21:33:50 +00:00
nmzikandAli Almohaya ff6dffe298 graphics: decode packed unsigned float CMASK register clears
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
2026-10-13 21:33:50 +00:00
nmzik 5f6dc0a8d0 shader: stream tile bitmaps within their fixed descriptor domain 2026-10-13 21:33:50 +00:00
nmzik 34893dd8c8 shader: recognize unconditional predicates in implication proofs 2026-10-13 21:33:50 +00:00
nmzik 50595d1c13 shader: recover counted image rows through private LDS spills 2026-10-13 21:33:50 +00:00
nmzikandAli Almohaya 717c54e085 gpu: match complete NOP marker tags and ignore opaque payloads
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
2026-10-13 21:33:50 +00:00
nmzikandAli Almohaya 31b7be9ff2 graphics: materialize fast clears before sampling reused targets
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
2026-10-13 21:27:07 +00:00
nmzik 40692dfb64 graphics: copy sparse buffer uploads through resident mappings 2026-10-13 21:27:07 +00:00
nmzik 9a3aca963a shader: combine image execution masks with descriptor branch bounds 2026-10-13 21:27:07 +00:00
nmzik 1fa79d5cac shader: specialize bounded pointer and packed image tables 2026-10-13 21:27:07 +00:00
nmzik c9ec82e32e shader: factor live execution guards before resource planning 2026-10-13 21:27:07 +00:00
nmzik 2f88a0a3b2 shader: elide bitcasts between identical physical types 2026-10-13 21:27:07 +00:00
nmzik 7e884f55e0 shader: reuse dominating descriptor loads across image samples 2026-10-13 21:27:07 +00:00
nmzik d70cbd4184 shader: bound descriptor worklists by their finite bit masks 2026-10-13 21:27:07 +00:00
nmzik 9bcf80bf67 graphics: share temporary staging for oversized image uploads 2026-10-13 21:27:07 +00:00
nmzik ed1195d1e7 shader: prove descriptor bounds from simplified image EXEC predicates 2026-10-13 21:27:07 +00:00
nmzik 25e1cc7ec9 shader: preserve descriptor bounds through guarded branch joins 2026-10-13 21:27:07 +00:00
nmzik a287a54f26 shader: prove guarded descriptor counts for interleaved image records 2026-10-13 21:27:07 +00:00
nmzik 56738aa4e1 shader: sample packed normalized images with one bilinear gather 2026-10-13 21:27:07 +00:00
nmzik c11254a996 shader: share format conversion for packed normalized image stores 2026-10-13 21:27:07 +00:00
nmzik aec8c88755 graphics: support signed 32-bit image atomic descriptors 2026-10-13 21:27:07 +00:00
nmzik 68d7bc94e2 shader: correlate masked first-lane material rows 2026-10-13 21:27:07 +00:00
nmzik 8ee31abd9d shader: bound indirect image selectors by counted scalar rows 2026-10-13 21:27:07 +00:00
nmzik 9a7c54da58 shader: share formatted buffer payload reads across format conversions 2026-10-13 21:27:07 +00:00
nmzik 5dc6914688 shader: lower bounded UInt formatted buffer copies to shared stores 2026-10-13 21:27:07 +00:00
nmzikandAli Almohaya a7a734b5d3 video: implement PS5 Vdecsw using a shared queued AVC decoder
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
2026-10-13 21:27:07 +00:00
nmzik e2c8b61de6 shader: remove unreachable mixed image specialization paths 2026-10-13 21:27:07 +00:00
nmzik 145578ac03 shader: materialize image keys from proven GPU bitmap subsets 2026-10-13 21:27:07 +00:00
nmzik f01a3750d4 shader: validate indirect image operands at each typed access 2026-10-13 21:27:07 +00:00
nmzik 0e9c77b0a2 shader: bound material selectors by proven one-wave LDS append lists 2026-10-13 21:27:07 +00:00
nmzik d692d8ee08 shader: preserve full region addresses for LDS and GDS reads and writes 2026-10-13 21:27:07 +00:00
Ali Almohaya 460a196fca shader: emit dynamic sample offsets with the enabled maintenance8 feature 2026-10-13 21:27:07 +00:00
nmzik 652154aead shader: merge typed image candidates as raw vectors with shared sampler variants 2026-10-13 21:27:07 +00:00
nmzikandAli Almohaya 49ebbfd627 shader: prove descending image ranges and prune impossible register selectors
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
2026-10-13 21:27:07 +00:00
nmzikandAli Almohaya 7688093ca3 shader: resolve GPU scalar image selectors within buffer bounds
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
2026-10-13 21:27:07 +00:00
nmzik df08b83287 shader: propagate late constants through affected IR users 2026-10-13 21:27:07 +00:00
nmzik fa68e104ea shader: fold full wave64 masks before register predication 2026-10-13 21:27:07 +00:00
nmzikandAli Almohaya fd66f580b4 shader: resolve packed buffer selectors with shared indexed stores
Co-authored-by: Ali Almohaya <AliAlmohaya@gmail.com>
2026-10-13 21:27:07 +00:00
nmzik 8f7d745383 graphics: simplify image usage and dirty state 2026-10-13 21:27:07 +00:00
f4692a450e graphics: avoid full-cache scans when deleting stencil associations
Co-authored-by: overlordxrz-source <overlordxrz-source@users.noreply.github.com>
Co-authored-by: TheCruZ <thecruzwtf@gmail.com>
2026-10-13 21:27:07 +00:00
nmzik df5b9b92be minor cleanup 2026-10-12 15:06:38 +02:00
Devran Cosmo Uenal 5b0ef07742 audio: keep NGS2 sampler voices within max_waveform_blocks (#1450)
audio: preserve NGS2 streaming buffer ownership
2026-10-12 14:22:06 +02:00
Ettore Sartoriandnmzik 5ce9a52936 Fix the splash screen (#1378)
Fix swapchain acquire semaphore reuse

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-12 13:40:59 +02:00
129 changed files with 20007 additions and 3389 deletions

No files matched your search

+10
View File
@@ -432,6 +432,14 @@ add_executable(avplayer_file_tests EXCLUDE_FROM_ALL
target_link_libraries(avplayer_file_tests common FFmpeg::ffmpeg ZArchive::zarchive)
target_include_directories(avplayer_file_tests PRIVATE ${inc_headers})
add_executable(vdecsw_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/VdecswTests.cpp"
"${KYTY_SOURCE_DIR}/libs/videoDec2Decoder.cpp"
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
)
target_link_libraries(vdecsw_tests common FFmpeg::ffmpeg)
target_include_directories(vdecsw_tests PRIVATE ${inc_headers})
add_executable(bit_array_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/BitArrayTests.cpp"
)
@@ -622,6 +630,7 @@ if(BUILD_TESTING)
add_test(NAME archive_file COMMAND $<TARGET_FILE:archive_file_tests>)
add_test(NAME trophy_system COMMAND $<TARGET_FILE:trophy_system_tests>)
add_test(NAME avplayer_file COMMAND $<TARGET_FILE:avplayer_file_tests>)
add_test(NAME vdecsw COMMAND $<TARGET_FILE:vdecsw_tests>)
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
add_test(NAME lru_cache COMMAND $<TARGET_FILE:lru_cache_tests>)
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
@@ -698,6 +707,7 @@ if(BUILD_TESTING)
archive_file_tests
trophy_system_tests
avplayer_file_tests
vdecsw_tests
bit_array_tests
lru_cache_tests
shader_vertex_metadata_tests
+112
View File
@@ -0,0 +1,112 @@
#include "common/additionalContent.h"
#include "common/stringUtils.h"
#include <algorithm>
#include <fstream>
#include <map>
#include <nlohmann/json.hpp>
#include <regex>
#include <system_error>
namespace Common {
namespace {
std::string GetString(const nlohmann::json& object, const std::string& key) {
const auto it = object.find(key);
return it != object.end() && it->is_string() ? it->get<std::string>() : std::string {};
}
std::string GetTitle(const nlohmann::json& param) {
const auto localized = param.find("localizedParameters");
if (localized != param.end() && localized->is_object()) {
const auto preferred = localized->find(GetString(*localized, "defaultLanguage"));
if (preferred != localized->end()) {
if (auto title = GetString(*preferred, "titleName"); !title.empty()) {
return title;
}
}
for (const auto& language: *localized) {
if (auto title = GetString(language, "titleName"); !title.empty()) {
return title;
}
}
}
return GetString(param, "titleName");
}
} // namespace
std::vector<AddContentEntry> FindAddContent(const std::filesystem::path& dir,
const std::string& title_id) {
if (dir.empty() || title_id.empty()) {
return {};
}
std::error_code ec;
const auto root = std::filesystem::absolute(dir, ec);
if (ec) {
return {};
}
// Scan directories separately so an unreadable child does not discard its siblings.
std::vector<std::filesystem::path> pending {root};
std::vector<std::filesystem::path> params;
while (!pending.empty()) {
const auto current = std::move(pending.back());
pending.pop_back();
std::filesystem::directory_iterator it(
current, std::filesystem::directory_options::skip_permission_denied, ec);
const std::filesystem::directory_iterator end;
for (; !ec && it != end; it.increment(ec)) {
std::error_code entry_ec;
if (it->is_directory(entry_ec) && !it->is_symlink(entry_ec) && !entry_ec) {
pending.push_back(it->path());
} else if (it->is_regular_file(entry_ec) && !entry_ec &&
EqualNoCase(PathToString(it->path().filename()), "param.json") &&
EqualNoCase(PathToString(it->path().parent_path().filename()), "sce_sys")) {
params.push_back(it->path());
}
}
}
std::sort(params.begin(), params.end());
static const std::regex content_id_pattern(
"^[A-Z]{2}[0-9]{4}-[A-Z]{4}[0-9]{5}_[0-9]{2}-[A-Z0-9]{16}$");
std::map<std::string, AddContentEntry> by_label;
for (const auto& file: params) {
const auto size = std::filesystem::file_size(file, ec);
if (ec || size > 1024 * 1024) {
continue;
}
std::ifstream stream(file, std::ios::binary);
if (!stream) {
continue;
}
const auto param = nlohmann::json::parse(stream, nullptr, false);
// applicationCategoryType is required for applications, not additional content.
if (!param.is_object() || param.contains("applicationCategoryType")) {
continue;
}
const auto content_id = GetString(param, "contentId");
if (!std::regex_match(content_id, content_id_pattern) ||
content_id.compare(7, 9, title_id) != 0) {
continue;
}
const auto mount_dir = file.parent_path().parent_path();
const auto label = content_id.substr(20);
by_label.try_emplace(label,
AddContentEntry {content_id, label, mount_dir, GetTitle(param)});
}
std::vector<AddContentEntry> entries;
entries.reserve(by_label.size());
for (auto& [label, entry]: by_label) {
entries.push_back(std::move(entry));
}
return entries;
}
} // namespace Common
+23
View File
@@ -0,0 +1,23 @@
#ifndef KYTY_COMMON_ADDITIONAL_CONTENT_H_
#define KYTY_COMMON_ADDITIONAL_CONTENT_H_
#include <filesystem>
#include <string>
#include <vector>
namespace Common {
struct AddContentEntry {
std::string content_id;
std::string entitlement_label;
std::filesystem::path mount_dir;
std::string title;
};
// Finds packages for this title, deduplicated by entitlement label.
std::vector<AddContentEntry> FindAddContent(const std::filesystem::path& dir,
const std::string& title_id);
} // namespace Common
#endif /* KYTY_COMMON_ADDITIONAL_CONTENT_H_ */
+4
View File
@@ -172,6 +172,10 @@ bool TrophyEnabled() {
return g_config->trophy_enabled;
}
uint32_t GetTrophyDurationSeconds() {
return std::clamp(g_config->trophy_duration_seconds, 5u, 10u);
}
bool PlayGoHackEnabled() {
return g_config->playgo_hack_enabled;
}
+2
View File
@@ -79,6 +79,7 @@ struct ConfigOptions {
bool sync_raw_image_buffers = false;
bool tessellation_enabled = false;
bool trophy_enabled = true;
uint32_t trophy_duration_seconds = 6;
bool playgo_hack_enabled = false;
bool skip_notice_screen = false;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
@@ -135,6 +136,7 @@ bool ReadbackLinearImagesEnabled();
bool SyncRawImageBuffersEnabled();
bool TessellationEnabled();
bool TrophyEnabled();
uint32_t GetTrophyDurationSeconds();
bool PlayGoHackEnabled();
bool SkipNoticeScreen();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
+18 -65
View File
@@ -152,42 +152,29 @@ void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* byte
}
}
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
static sys_file_t* OpenHostFile(const std::filesystem::path& file_name, const char* mode,
sys_file_cache_type_t cache_type) {
auto* ret = new sys_file_t;
auto real_name = get_internal_name(file_name);
auto real_name_str = real_name.string();
ret->f = fopen(real_name_str.c_str(), "w+");
if (ret->f == nullptr) {
printf("can't create file: %s\n", real_name_str.c_str());
ret->f = fopen(get_internal_name(file_name).c_str(), mode);
ret->type = ret->f == nullptr ? SYS_FILE_ERROR : SYS_FILE_FILE;
if (ret->f != nullptr) {
// Independent handles must observe writes and truncation immediately.
setvbuf(ret->f, nullptr, _IONBF, 0);
apply_cache_hint(ret->f, cache_type);
}
return ret;
}
ret->type = SYS_FILE_FILE;
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
auto* ret = OpenHostFile(file_name, "w+", SYS_FILE_CACHE_AUTO);
if (ret->f == nullptr) {
printf("can't create file: %s\n", file_name.c_str());
}
return ret;
}
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
auto* ret = new sys_file_t;
ret->type = SYS_FILE_FILE;
auto internal_name = get_internal_name(file_name);
auto internal_name_str = internal_name.string();
FILE* f = fopen(internal_name_str.c_str(), "r");
if (f == nullptr) {
ret->type = SYS_FILE_ERROR;
}
apply_cache_hint(f, cache_type);
ret->f = f;
return ret;
return OpenHostFile(file_name, "r", cache_type);
}
sys_file_t* SysFileOpen(uint8_t* buf, uint32_t buf_size) {
@@ -215,46 +202,12 @@ sys_file_t* SysFileCreate() {
}
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
auto* ret = new sys_file_t;
auto real_name = get_internal_name(file_name);
auto real_name_str = real_name.string();
FILE* f = fopen(real_name_str.c_str(), "r+");
if (f == nullptr) {
ret->type = SYS_FILE_ERROR;
} else {
ret->type = SYS_FILE_FILE;
}
apply_cache_hint(f, cache_type);
ret->f = f;
return ret;
return OpenHostFile(file_name, "r+", cache_type);
}
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
sys_file_cache_type_t cache_type) {
auto* ret = new sys_file_t;
auto real_name = get_internal_name(file_name);
auto real_name_str = real_name.string();
FILE* f = fopen(real_name_str.c_str(), "r+");
if (f == nullptr) {
ret->type = SYS_FILE_ERROR;
} else {
ret->type = SYS_FILE_FILE;
}
apply_cache_hint(f, cache_type);
ret->f = f;
return ret;
return OpenHostFile(file_name, "r+", cache_type);
}
void SysFileClose(sys_file_t* f) {
+15 -19
View File
@@ -8,10 +8,6 @@
#include <condition_variable> // IWYU pragma: keep
#include <mutex>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_COMPILER == KYTY_COMPILER_CLANG
#define KYTY_WIN_CS
#endif
// macOS has no clock_nanosleep.
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS && !defined(__APPLE__)
#define KYTY_POSIX_HIGH_RES_SLEEP
@@ -22,7 +18,7 @@
#include <string>
#include <thread>
#ifdef KYTY_WIN_CS
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <windows.h> // IWYU pragma: keep
// IWYU pragma: no_include <winbase.h>
constexpr DWORD KYTY_CS_SPIN_COUNT = 4000;
@@ -133,7 +129,7 @@ namespace Common {
using thread_id_t = std::thread::id;
struct MutexPrivate {
#ifdef KYTY_WIN_CS
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
MutexPrivate() { InitializeCriticalSectionAndSpinCount(&m_cs, KYTY_CS_SPIN_COUNT); }
~MutexPrivate() { DeleteCriticalSection(&m_cs); }
KYTY_CLASS_NO_COPY(MutexPrivate);
@@ -144,7 +140,7 @@ struct MutexPrivate {
};
struct CondVarPrivate {
#ifdef KYTY_WIN_CS
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
CondVarPrivate() {
static auto func = ResolveInitializeConditionVariable();
EXIT_NOT_IMPLEMENTED(func == nullptr);
@@ -216,7 +212,7 @@ void Thread::Detach() {
}
void Thread::SleepMicro(uint32_t micros) {
#ifdef KYTY_WIN_CS
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
SleepHighResolution100ns(static_cast<uint64_t>(micros) * 10);
#elif defined(KYTY_POSIX_HIGH_RES_SLEEP)
SleepHighResolutionNanos(static_cast<uint64_t>(micros) * 1000);
@@ -226,7 +222,7 @@ void Thread::SleepMicro(uint32_t micros) {
}
void Thread::SleepNano(uint64_t nanos) {
#ifdef KYTY_WIN_CS
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
SleepHighResolution100ns((nanos + 99) / 100);
#elif defined(KYTY_POSIX_HIGH_RES_SLEEP)
SleepHighResolutionNanos(nanos);
@@ -262,7 +258,7 @@ Mutex::~Mutex() {
}
void Mutex::Lock() {
#ifdef KYTY_WIN_CS
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
EnterCriticalSection(&m_mutex->m_cs);
#else
m_mutex->m_mutex.lock();
@@ -270,7 +266,7 @@ void Mutex::Lock() {
}
void Mutex::Unlock() {
#ifdef KYTY_WIN_CS
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
LeaveCriticalSection(&m_mutex->m_cs);
#else
m_mutex->m_mutex.unlock();
@@ -278,7 +274,7 @@ void Mutex::Unlock() {
}
bool Mutex::TryLock() {
#ifdef KYTY_WIN_CS
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
return (TryEnterCriticalSection(&m_mutex->m_cs) != 0);
#else
return m_mutex->m_mutex.try_lock();
@@ -292,7 +288,7 @@ CondVar::~CondVar() {
}
void CondVar::Wait(Mutex* mutex) {
#ifndef KYTY_WIN_CS
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
#endif
auto poll_callback = [&] {
@@ -300,7 +296,7 @@ void CondVar::Wait(Mutex* mutex) {
if (callback == nullptr) {
return;
}
#if defined(KYTY_WIN_CS)
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
LeaveCriticalSection(&mutex->m_mutex->m_cs);
callback();
EnterCriticalSection(&mutex->m_mutex->m_cs);
@@ -310,7 +306,7 @@ void CondVar::Wait(Mutex* mutex) {
cpp_lock.lock();
#endif
};
#ifdef KYTY_WIN_CS
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static auto func = ResolveSleepConditionVariableCS();
EXIT_NOT_IMPLEMENTED(func == nullptr);
if (g_cond_wait_poll_callback == nullptr) {
@@ -340,10 +336,10 @@ void CondVar::SetWaitPollCallback(wait_poll_func_t callback) {
bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
bool ok = false;
#ifndef KYTY_WIN_CS
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
#endif
#ifdef KYTY_WIN_CS
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static auto func = ResolveSleepConditionVariableCS();
EXIT_NOT_IMPLEMENTED(func == nullptr);
ok = !(func(&m_cond_var->m_cv, &mutex->m_mutex->m_cs, (micros < 1000 ? 1 : micros / 1000)) ==
@@ -358,7 +354,7 @@ bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
}
void CondVar::Signal() {
#ifdef KYTY_WIN_CS
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static auto func = ResolveWakeConditionVariable();
EXIT_NOT_IMPLEMENTED(func == nullptr);
func(&m_cond_var->m_cv);
@@ -368,7 +364,7 @@ void CondVar::Signal() {
}
void CondVar::SignalAll() {
#ifdef KYTY_WIN_CS
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static auto func = ResolveWakeAllConditionVariable();
EXIT_NOT_IMPLEMENTED(func == nullptr);
func(&m_cond_var->m_cv);
+11 -1
View File
@@ -22,6 +22,7 @@
#include "libs/controller.h"
#include "libs/libs.h"
#include "libs/network.h"
#include "loader/additionalContent.h"
#include "loader/runtimeLinker.h"
#include "loader/systemContent.h"
#include "loader/timer.h"
@@ -201,7 +202,15 @@ void Run(const RunOptions& options) {
PrintSystemInfo();
std::string title_id;
if (Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id) && !title_id.empty()) {
Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id);
if (title_id.empty()) {
std::string content_id;
if (Loader::SystemContentParamSfoGetString("CONTENT_ID", &content_id) &&
content_id.size() == 36 && content_id[6] == '-' && content_id[16] == '_') {
title_id = content_id.substr(7, 9);
}
}
if (!title_id.empty()) {
Log::WriteToConsoleAndLog(fmt::format("Title ID: {}\n", title_id));
}
@@ -217,6 +226,7 @@ void Run(const RunOptions& options) {
MountSandboxDirs();
Loader::AddContentLoad(options.addcont_dir, title_id);
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
Libs::InitAll(rt->Symbols());
+1
View File
@@ -13,6 +13,7 @@ struct RunOptions {
std::filesystem::path app0_dir;
std::filesystem::path elf;
std::filesystem::path game_patch;
std::filesystem::path addcont_dir;
};
void Run(const RunOptions& options);
@@ -231,7 +231,6 @@ constexpr auto MakeCustomOpcodeDispatchTable() {
g_cp_op_custom_func[Pm4::R_WAIT_FLIP_DONE] = CpOpWaitFlipDone;
g_cp_op_custom_func[Pm4::R_PUSH_MARKER] = CpOpPushMarker;
g_cp_op_custom_func[Pm4::R_POP_MARKER] = CpOpPopMarker;
g_cp_op_custom_func[Pm4::R_ACQUIRE_MEM] = CpOpAcquireMem;
g_cp_op_custom_func[Pm4::R_FLIP] = CpOpFlip;
g_cp_op_custom_func[Pm4::R_RELEASE_MEM] = CpOpReleaseMem;
g_cp_op_custom_func[Pm4::R_CONTEXT_STATE] = CpOpContextState;
@@ -295,10 +295,6 @@ static void HwCtxIgnoreDisabledUserClipPlane(CommandProcessor& cp, uint32_t valu
EXIT_NOT_IMPLEMENTED(value != 0 || cp.GetCtx().GetClipControl().user_clip_planes != 0);
}
static void HwCtxIgnoreDrawPayloadControl([[maybe_unused]] uint32_t value) {}
static void HwCtxIgnoreObjprimIdControl([[maybe_unused]] uint32_t value) {}
static void HwCtxIgnorePrimitiveIdReset([[maybe_unused]] uint32_t value) {}
static void HwCtxIgnoreVtxControl([[maybe_unused]] uint32_t value) {}
@@ -454,23 +450,13 @@ KYTY_HW_CTX_PARSER(HwCtxSetAlphaToMask) {
}
KYTY_HW_CTX_PARSER(HwCtxSetDrawPayloadControl) {
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
for (uint32_t i = 0; i < num_values; i++) {
HwCtxIgnoreDrawPayloadControl(buffer[i]);
}
return num_values;
cp.GetCtx().SetDrawPayloadControl(buffer[0]);
return KYTY_PM4_LEN(cmd_id) - 2u;
}
KYTY_HW_CTX_PARSER(HwCtxSetObjprimIdControl) {
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
for (uint32_t i = 0; i < num_values; i++) {
HwCtxIgnoreObjprimIdControl(buffer[i]);
}
return num_values;
cp.GetCtx().SetObjprimIdControl(buffer[0]);
return KYTY_PM4_LEN(cmd_id) - 2u;
}
KYTY_HW_CTX_PARSER(HwCtxSetPrimitiveIdReset) {
@@ -1304,8 +1290,8 @@ KYTY_HW_UC_PARSER(HwUcSetGdsOaRegisters) {
KYTY_CP_OP_PARSER(CpOpAcquireMem) {
KYTY_PROFILER_FUNCTION();
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0055800 && cmd_id != 0xc0061050);
return (cmd_id == 0xc0061050 ? 7 : 6);
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0055800 && cmd_id != 0xc0065800);
return KYTY_PM4_LEN(cmd_id) - 1;
}
KYTY_CP_OP_PARSER(CpOpDispatchDirect) {
@@ -2142,27 +2128,23 @@ KYTY_CP_OP_PARSER(CpOpIndirectUcRegs) {
KYTY_CP_OP_PARSER(CpOpMarker) {
KYTY_PROFILER_FUNCTION();
// EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0001000);
uint32_t id = buffer[0] & 0xfffu;
uint32_t len_dw = ((cmd_id >> 16u) & 0x3fffu);
switch (id) {
case 0x0: break;
case 0x4: cp.SetUserDataMarker(HW::UserSgprType::Vsharp); break;
case 0xd: cp.SetUserDataMarker(HW::UserSgprType::Region); break;
case 0x777: {
switch (buffer[0]) {
case 0x68750004: cp.SetUserDataMarker(HW::UserSgprType::Vsharp); break;
case 0x6875000d: cp.SetUserDataMarker(HW::UserSgprType::Region); break;
case 0x68750777: {
cp.Flip();
break;
}
case 0x778: {
case 0x68750778: {
auto* addr =
reinterpret_cast<void*>(buffer[1] | (static_cast<uint64_t>(buffer[2]) << 32u));
uint32_t value = buffer[3];
cp.Flip(addr, value);
break;
}
case 0x781: {
case 0x68750781: {
auto* addr =
reinterpret_cast<void*>(buffer[1] | (static_cast<uint64_t>(buffer[2]) << 32u));
uint32_t value = buffer[3];
@@ -2171,7 +2153,8 @@ KYTY_CP_OP_PARSER(CpOpMarker) {
cp.FlipWithInterrupt(eop_event_type, cache_action, addr, value);
break;
}
default: EXIT("unknown marker at %05" PRIx32 ": 0x%" PRIx32 "\n", num_dw - dw, id);
// NOP payloads are caller-owned data; only complete known marker tags execute.
default: break;
}
return len_dw + 1;
@@ -3109,7 +3092,7 @@ void GraphicsInitJmpTablesCxIndirect() {
cp.GetCtx().SetTfParam(value);
};
g_hw_ctx_indirect_func[Pm4::VGT_DRAW_PAYLOAD_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDrawPayloadControl(value);
cp.GetCtx().SetDrawPayloadControl(value);
};
g_hw_ctx_indirect_func[Pm4::VGT_PRIMITIVEID_RESET] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnorePrimitiveIdReset(value);
@@ -3118,7 +3101,7 @@ void GraphicsInitJmpTablesCxIndirect() {
cp.GetCtx().SetShaderZFormat(value);
};
g_hw_ctx_indirect_func[Pm4::PA_CL_OBJPRIM_ID_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreObjprimIdControl(value);
cp.GetCtx().SetObjprimIdControl(value);
};
for (auto cmd_offset = Pm4::PA_SC_CLIPRECT_RULE; cmd_offset < Pm4::PA_SC_CLIPRECT_0_TL + 8u;
+3
View File
@@ -23,6 +23,7 @@ constexpr FormatInfo kFormatInfo[] = {
{BufferFormat::k8UScaled, 1, 0, 0, true, false},
{BufferFormat::k8SNorm, 1, 0, 1, true, false},
{BufferFormat::k8UInt, 1, 0, 1, true, true},
{BufferFormat::k8SInt, 1, 0, 1, true, false, true},
{BufferFormat::k16UNorm, 2, 0, 2, true, false},
{BufferFormat::k16SNorm, 2, 0, 2, true, false},
{BufferFormat::k16UInt, 2, 0, 2, true, true},
@@ -41,6 +42,7 @@ constexpr FormatInfo kFormatInfo[] = {
{BufferFormat::k16_16UInt, 4, 0, 4, true, true},
{BufferFormat::k16_16SInt, 4, 0, 4, true, false, true},
{BufferFormat::k16_16Float, 4, 0, 4, true, false},
{BufferFormat::k11_11_10UNorm, 4, 0, 4, true, false},
{BufferFormat::k11_11_10UInt, 4, 0, 4, true, true},
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
{BufferFormat::k10_11_11Float, 4, 0, 4, true, false},
@@ -237,6 +239,7 @@ BufferFormat RemapTextureFormat(BufferFormat format) {
switch (format) {
case BufferFormat::k8UScaled: return BufferFormat::k8UNorm;
case BufferFormat::k8_8UScaled: return BufferFormat::k8_8UNorm;
case BufferFormat::k11_11_10UNorm:
case BufferFormat::k11_11_10UInt: return BufferFormat::k32UInt;
default: return format;
}
+8
View File
@@ -556,6 +556,8 @@ struct ShaderRegisters {
uint32_t m_vgtGsMaxVertOut = 0;
uint32_t m_vgtGsOutPrimType = 0;
uint32_t m_vgtPrimitiveIdEn = 0;
uint32_t m_vgtDrawPayloadCntl = 0;
uint32_t m_paClObjprimIdCntl = 0;
uint32_t m_vgtReuseOff = 0;
uint32_t m_vgtTessDistribution = 0;
uint32_t m_vgtLsHsConfig = 0;
@@ -679,6 +681,10 @@ public:
void SetRenderTargetMask(uint32_t mask) { m_render_target_mask = mask; }
[[nodiscard]] uint32_t GetRenderTargetMask() const { return m_render_target_mask; }
[[nodiscard]] bool HasObjectPrimitiveIdOutput() const {
return (m_sh_regs.m_vgtDrawPayloadCntl & 1u) != 0 &&
(m_render_target_mask & 0xf0000000u) != 0;
}
void SetShaderStages(uint32_t flags) { m_shader_stages = flags; }
[[nodiscard]] uint32_t GetShaderStages() const { return m_shader_stages; }
@@ -877,6 +883,8 @@ public:
void SetGsMaxVertOut(uint32_t value) { m_sh_regs.m_vgtGsMaxVertOut = value; }
void SetGsOutPrimType(uint32_t value) { m_sh_regs.m_vgtGsOutPrimType = value; }
void SetPrimitiveIdEn(uint32_t value) { m_sh_regs.m_vgtPrimitiveIdEn = value; }
void SetDrawPayloadControl(uint32_t value) { m_sh_regs.m_vgtDrawPayloadCntl = value; }
void SetObjprimIdControl(uint32_t value) { m_sh_regs.m_paClObjprimIdCntl = value; }
void SetReuseOff(uint32_t value) { m_sh_regs.m_vgtReuseOff = value; }
void SetTessDistribution(uint32_t value) { m_sh_regs.m_vgtTessDistribution = value; }
void SetLsHsConfig(uint32_t value) { m_sh_regs.m_vgtLsHsConfig = value; }
-1
View File
@@ -23,7 +23,6 @@ constexpr auto MakeRegisterNames() {
names[R_DISPATCH_RESET] = "R_DISPATCH_RESET";
names[R_PUSH_MARKER] = "R_PUSH_MARKER";
names[R_POP_MARKER] = "R_POP_MARKER";
names[R_ACQUIRE_MEM] = "R_ACQUIRE_MEM";
names[R_WRITE_DATA] = "R_WRITE_DATA";
names[R_FLIP] = "R_FLIP";
names[R_RELEASE_MEM] = "R_RELEASE_MEM";
-1
View File
@@ -86,7 +86,6 @@ constexpr uint32_t R_WAIT_FLIP_DONE = 0x06;
constexpr uint32_t R_DISPATCH_RESET = 0x09;
constexpr uint32_t R_PUSH_MARKER = 0x0B;
constexpr uint32_t R_POP_MARKER = 0x0C;
constexpr uint32_t R_ACQUIRE_MEM = 0x14;
constexpr uint32_t R_WRITE_DATA = 0x15;
constexpr uint32_t R_FLIP = 0x17;
constexpr uint32_t R_RELEASE_MEM = 0x18;
+1 -1
View File
@@ -33,7 +33,7 @@ struct GraphicContext {
// bool fp64_denorm_preserve = false; // Temporarily disabled.
bool attachment_feedback_loop_enabled = false;
bool provoking_vertex_last_enabled = false;
bool depth_color_copy_enabled = false;
bool maintenance8_enabled = false;
bool supports_block_texel_view = false;
bool mesh_shader_enabled = false;
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
+37 -25
View File
@@ -27,6 +27,26 @@ namespace {
constexpr uint64_t MiB = 1024 * 1024;
constexpr uint64_t GdsBufferSize = 64 * 1024;
std::pair<Buffer*, uint64_t> UploadMemory(GraphicContext& graphics, CommandScheduler& scheduler,
StreamBuffer& staging, uint64_t size, uint64_t alignment,
vk::BufferUsageFlags usage, const auto& write) {
const auto [mapped, offset] = staging.Map(size, alignment);
if (mapped != nullptr) {
write(mapped, offset);
staging.Commit();
return {&staging, offset};
}
// Large transfers use the same one-time host allocation as ordinary buffer uploads.
auto temporary = std::make_unique<Buffer>(graphics, scheduler, MemoryUsage::Upload, 0,
usage, size);
write(temporary->Mapped().data(), 0);
temporary->Flush(0, size);
auto* buffer = temporary.get();
scheduler.DeferOperation([owner = std::move(temporary)]() mutable { owner.reset(); });
return {buffer, 0};
}
} // namespace
void BufferCache::WriteDataBuffer(Buffer& buffer, uint64_t address, const void* source,
@@ -447,28 +467,17 @@ vk::Buffer BufferCache::UploadCopies(Buffer& buffer, std::span<vk::BufferCopy> c
return nullptr;
}
auto [mapped, base_offset] = m_staging_buffer.Map(total_size, 4);
if (mapped != nullptr) {
const auto source = UploadMemory(m_graphics, m_scheduler, m_staging_buffer, total_size, 4,
vk::BufferUsageFlagBits::eTransferSrc, [&](uint8_t* mapped, uint64_t base_offset) {
for (auto& copy: copies) {
const auto address = buffer.CpuAddress() + copy.dstOffset;
std::memcpy(mapped + copy.srcOffset, reinterpret_cast<const void*>(address), copy.size);
if (!LibKernel::Memory::CopySparseMemory(address, mapped + copy.srcOffset, copy.size)) {
EXIT("BufferCache: failed to read sparse guest buffer memory\n");
}
copy.srcOffset += base_offset;
}
m_staging_buffer.Commit();
return m_staging_buffer.Handle();
}
auto temporary = std::make_unique<Buffer>(m_graphics, m_scheduler, MemoryUsage::Upload, 0,
vk::BufferUsageFlagBits::eTransferSrc, total_size);
for (const auto& copy: copies) {
const auto address = buffer.CpuAddress() + copy.dstOffset;
std::memcpy(temporary->Mapped().data() + copy.srcOffset,
reinterpret_cast<const void*>(address), copy.size);
}
temporary->Flush(0, total_size);
const auto handle = temporary->Handle();
m_scheduler.DeferOperation([owner = std::move(temporary)]() mutable { owner.reset(); });
return handle;
});
return source.first->Handle();
}
std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t size,
@@ -486,7 +495,9 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t
m_graphics.physical_device_properties.limits.minUniformBufferOffsetAlignment, 1);
auto [mapped, offset] = m_stream_buffer.Map(size, alignment, false);
if (mapped != nullptr) {
std::memcpy(mapped, reinterpret_cast<const void*>(vaddr), size);
if (!LibKernel::Memory::CopySparseMemory(vaddr, mapped, size)) {
EXIT("BufferCache: failed to read sparse guest buffer memory\n");
}
m_stream_buffer.Commit();
return {&m_stream_buffer, offset};
}
@@ -521,12 +532,13 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBufferForImage(uint64_t vaddr, u
return ObtainBuffer(vaddr, size, false, false);
}
auto [staging, stage_offset] = m_staging_buffer.Map(size, 16);
if (staging == nullptr || !Libs::LibKernel::Memory::TryReadSparseBacking(vaddr, staging, size)) {
EXIT("BufferCache: failed to read mapped guest image backing\n");
}
m_staging_buffer.Commit();
return {&m_staging_buffer, stage_offset};
return UploadMemory(m_graphics, m_scheduler, m_staging_buffer, size, 16,
vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eStorageBuffer,
[&](uint8_t* mapped, uint64_t) {
if (!Libs::LibKernel::Memory::TryReadSparseBacking(vaddr, mapped, size)) {
EXIT("BufferCache: failed to read mapped guest image backing\n");
}
});
}
void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds) {
+44 -84
View File
@@ -25,6 +25,7 @@
#include <mutex>
#include <span>
#include <tuple>
#include <utility>
#include <vulkan/vulkan_format_traits.hpp>
namespace Libs::Graphics {
@@ -198,9 +199,7 @@ TextureCache::BindingType TextureCache::UploadBinding(const Image& image) {
ImageId TextureCache::InsertImage(const ImageInfo& info) {
const auto id = m_slot_images.insert(m_graphics, m_scheduler, info);
if (!info.data.Empty()) {
RegisterImage(id);
}
RegisterImage(id);
return id;
}
@@ -251,14 +250,11 @@ void TextureCache::DeleteImage(ImageId id) {
if (image == nullptr || !image->registered) {
return;
}
if (!image->depth_id) {
std::vector<ImageId> associations;
m_slot_images.ForEach([&](ImageId candidate, const Image& associated) {
if (associated.depth_id == id) {
associations.push_back(candidate);
}
});
for (const auto association: associations) {
if (image->depth_id) {
std::erase(m_slot_images[image->depth_id].stencil_ids, id);
} else {
// Detach first: deleting each association also unlinks it from its depth owner.
for (const auto association: std::exchange(image->stencil_ids, {})) {
FreeImage(association);
}
}
@@ -299,9 +295,6 @@ void TextureCache::TouchImage(Image& image) {
void TextureCache::MarkAsMaybeDirty(ImageId id, Image& image) {
image.MarkMaybeCpuDirty();
if (image.NeedsMaybeCpuHash()) {
image.SetMaybeCpuHash(image.HashGuestEdges());
}
UntrackImage(id);
}
@@ -413,7 +406,7 @@ void TextureCache::UntrackImageTail(ImageId id) {
}
void TextureCache::TrackImageDownload(ImageId id, Image& image) {
if (m_readback_linear_images && !image.info.IsTiled() && !image.info.data.Empty()) {
if (m_readback_linear_images && !image.info.IsTiled()) {
if (!image.IsGpuModified()) {
EXIT("TextureCache: cannot enroll a non-GPU-owned image for download\n");
}
@@ -457,27 +450,6 @@ TextureCache::ImageIds TextureCache::FindImagesInRegion(uint64_t address, uint64
return result;
}
ImageId TextureCache::GetNullImage(const ImageDesc& desc) {
const auto format = desc.info.pixel_format;
if (const auto found = m_null_images.find(format); found != m_null_images.end()) {
return found->second;
}
ImageInfo info {};
info.pixel_format = desc.info.pixel_format;
info.guest_format = desc.info.guest_format;
info.type = Prospero::ImageType::kColor2D;
info.extent = {1, 1, 1};
info.resources = {1, 1};
info.pitch = 1;
info.bytes_per_block = std::max(desc.info.bytes_per_block, 1u);
info.samples = 1;
info.tile_mode = Prospero::TileMode::kLinear;
info.mip_layout[0] = {0, info.bytes_per_block, 1, 1};
const auto id = InsertImage(info);
m_null_images.emplace(format, id);
return id;
}
void TextureCache::ValidateImageDesc(const ImageDesc& desc) const {
ImageOps::Validate(desc.info);
if (desc.view_info.format == vk::Format::eUndefined || desc.view_info.level_count == 0 ||
@@ -720,7 +692,7 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
info.linear_slice_stride = cached.info.linear_slice_stride;
info.first_tail_level = cached.info.first_tail_level;
}
info.htile_clear_mask = 0;
info.htile_initial_clear = false;
const auto replacement_id = InsertImage(info);
auto& replacement = m_slot_images[replacement_id];
replacement.usage = cached.usage;
@@ -1031,9 +1003,6 @@ void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_off
void TextureCache::InitializeImage(ImageId id) {
auto& image = m_slot_images[id];
if (image.info.data.Empty()) {
return;
}
TrackImage(id);
if (image.info.metadata.compression != VideoOutCompression::Uncompressed) {
if (image.IsCpuDirty()) {
@@ -1146,12 +1115,7 @@ void TextureCache::RefreshImage(ImageId id) {
}
TrackImage(id);
if (image.IsMaybeCpuDirty()) {
const auto hash = image.HashGuestEdges();
if (image.NeedsMaybeCpuHash()) {
image.SetMaybeCpuHash(hash);
return;
}
(void)image.ResolveMaybeCpuHash(hash);
(void)image.ResolveMaybeCpuHash(image.HashGuestEdges());
}
bool cpu_dirty = image.IsBufferModified() || image.IsDefinitelyCpuDirty();
if (image.info.metadata.compression != VideoOutCompression::Uncompressed) {
@@ -1205,6 +1169,12 @@ ImageId TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
plane.IsTiled() ? TileGetDepthPitch(plane.extent.width, 1, 0) : depth.info.pitch;
plane.bytes_per_block = 1;
plane.UpdateSize();
if (record.depth_id != depth_id) {
if (record.depth_id) {
std::erase(m_slot_images[record.depth_id].stencil_ids, association);
}
depth.stencil_ids.push_back(association);
}
record.info = plane;
record.depth_id = depth_id;
record.stencil_subresources = depth.stencil_subresources;
@@ -1219,10 +1189,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
EXIT("TextureCache: image lookup requires a valid command buffer\n");
}
ValidateImageDesc(desc);
if (desc.info.data.Empty()) {
std::scoped_lock lock {m_lock};
return GetNullImage(desc);
}
EXIT_IF(desc.info.data.Empty());
const auto metadata_base_layer = desc.view_info.base_layer;
ImageId result {};
@@ -1374,36 +1341,30 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
std::scoped_lock lock {m_lock};
auto& image = m_slot_images[id];
TouchImage(image);
if (!image.info.data.Empty()) {
if (!image.registered || image.depth_id || image.binding.needs_rebind) {
EXIT("TextureCache: texture requires rediscovery before final acquisition\n");
}
if (!image.registered || image.depth_id || image.binding.needs_rebind) {
EXIT("TextureCache: texture requires rediscovery before final acquisition\n");
}
if (desc.type == BindingType::Storage) {
image.MarkGpuModified();
}
if (!image.info.data.Empty()) {
RefreshImage(id);
if (image.info.HasStencil() &&
desc.info.data.address >= image.info.stencil.address &&
desc.info.data.End() <= image.info.stencil.End()) {
for (const auto stencil_id:
FindImagesInRegion(image.info.stencil.address, image.info.stencil.size, false)) {
const auto* stencil = m_slot_images.try_get(stencil_id);
if (stencil != nullptr && stencil->depth_id == id &&
stencil->info.data == image.info.stencil) {
RefreshImage(stencil_id);
break;
}
RefreshImage(id);
if (image.info.HasStencil() &&
desc.info.data.address >= image.info.stencil.address &&
desc.info.data.End() <= image.info.stencil.End()) {
for (const auto stencil_id:
FindImagesInRegion(image.info.stencil.address, image.info.stencil.size, false)) {
const auto* stencil = m_slot_images.try_get(stencil_id);
if (stencil != nullptr && stencil->depth_id == id &&
stencil->info.data == image.info.stencil) {
RefreshImage(stencil_id);
break;
}
}
}
switch (desc.type) {
case BindingType::Texture: break;
case BindingType::Storage:
if (!image.info.data.Empty()) {
CommitGpuWrite(image);
}
CommitGpuWrite(image);
TrackImageDownload(id, image);
break;
default: EXIT("TextureCache: invalid texture binding\n");
@@ -1440,7 +1401,6 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
}
TouchImage(image);
image.MarkGpuModified();
image.usage.depth_target = true;
if (desc.info.HasStencil()) {
const auto layers = image.info.resources.layers;
EXIT_IF(layers == 0 || image.info.data.size % layers != 0 ||
@@ -1456,9 +1416,13 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
image.info.stencil = desc.info.stencil;
image.info.metadata = desc.info.metadata;
if (desc.info.HasMetadata()) {
m_surface_metas.emplace(desc.info.metadata.range.address,
MetaDataInfo {.type = MetaDataInfo::Type::HTile,
.clear_mask = image.info.htile_clear_mask});
const auto [metadata, inserted] = m_surface_metas.try_emplace(desc.info.metadata.range.address);
if (inserted) {
metadata->second.type = MetaDataInfo::Type::HTile;
if (image.info.htile_initial_clear) {
metadata->second.clear_mask.set();
}
}
}
RefreshImage(id);
CommitGpuWrite(image);
@@ -1856,7 +1820,7 @@ void TextureCache::InvalidateCpuAliases(uint64_t address, uint64_t size) {
continue;
}
if (owner->Overlaps(address, size)) {
owner->InvalidateCpuWrite(address, size);
owner->MarkCpuDirty();
UntrackImage(id);
continue;
}
@@ -1881,10 +1845,10 @@ bool TextureCache::IsMeta(uint64_t address) {
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice) {
std::scoped_lock lock {m_lock};
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end() || slice >= 32) {
if (found == m_surface_metas.end() || slice >= found->second.clear_mask.size()) {
return false;
}
return (found->second.clear_mask & (1u << slice)) != 0;
return found->second.clear_mask.test(slice);
}
bool TextureCache::ClearMeta(uint64_t address) {
@@ -1893,21 +1857,17 @@ bool TextureCache::ClearMeta(uint64_t address) {
if (found == m_surface_metas.end()) {
return false;
}
found->second.clear_mask = UINT32_MAX;
found->second.clear_mask.set();
return true;
}
bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
std::scoped_lock lock {m_lock};
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end() || slice >= 32) {
if (found == m_surface_metas.end() || slice >= found->second.clear_mask.size()) {
return false;
}
if (is_clear) {
found->second.clear_mask |= 1u << slice;
} else {
found->second.clear_mask &= ~(1u << slice);
}
found->second.clear_mask.set(slice, is_clear);
return true;
}
+4 -5
View File
@@ -12,9 +12,9 @@
#include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/renderer/image/tiler.h"
#include <bitset>
#include <map>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@@ -83,8 +83,9 @@ private:
struct MetaDataInfo {
enum class Type : uint8_t { CMask, FMask, HTile };
Type type;
uint32_t clear_mask = UINT32_MAX;
Type type;
// PS5 DB_DEPTH_VIEW encodes 13-bit base and last array-slice indices.
std::bitset<8192> clear_mask;
};
struct OverlapResult {
@@ -114,7 +115,6 @@ private:
}
[[nodiscard]] ImageId InsertImage(const ImageInfo& info);
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
void RegisterImage(ImageId id);
void UnregisterImage(ImageId id);
void DeleteImage(ImageId id);
@@ -173,7 +173,6 @@ private:
BufferCache& m_buffer_cache;
Common::SlotVector<Image> m_slot_images;
ImagePageTable m_image_page_table;
std::unordered_map<vk::Format, ImageId> m_null_images;
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
std::unordered_set<ImageId> m_download_images;
std::map<uint64_t, MetaDataInfo> m_surface_metas;
@@ -236,7 +236,8 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
// DCC owns the clear when both planes are enabled; single-sample CMASK stays expanded.
desc.info.metadata.kind = has_dcc ? ImageMetadataKind::Dcc : ImageMetadataKind::Cmask;
desc.info.metadata.range = {has_dcc ? rt.dcc_addr.addr : rt.cmask.addr, metadata_size.size};
desc.info.metadata.clear_word = rt.clear_word0.word0;
desc.info.metadata.clear_word =
rt.clear_word0.word0 | (uint64_t {rt.clear_word1.word1} << 32u);
desc.info.metadata.clear_register_valid = true;
}
desc.view_info.format = target_format.format;
+23 -4
View File
@@ -874,14 +874,33 @@ void hw_check(const CommandBuffer& buffer) {
log_phase("done");
if (graphics_debug_dump_enabled() && RenderTargetMaskHasBoundMrt(buffer)) {
LOGF("MRT render target mask: 0x%08" PRIx32 "\n", hw.GetRenderTargetMask());
const auto& sh = hw.GetShaderRegisters();
LOGF("MRT render target mask: 0x%08" PRIx32
" export_modes={%u,%u,%u,%u,%u,%u,%u,%u}\n",
hw.GetRenderTargetMask(), sh.target_output_mode[0], sh.target_output_mode[1],
sh.target_output_mode[2], sh.target_output_mode[3], sh.target_output_mode[4],
sh.target_output_mode[5], sh.target_output_mode[6], sh.target_output_mode[7]);
for (uint32_t i = 0; i < 8; i++) {
const auto& mrt = hw.GetRenderTarget(i);
const auto& bc = hw.GetBlendControl(i);
LOGF("\t RT%u addr=0x%010" PRIx64 " mask=0x%x fmt=0x%08" PRIx32
" width=%u height=%u tile=%u\n",
" type=%u order=%u shader_mask=0x%x"
" width=%u height=%u tile=%u blend=%u bypass=%u clamp=%u round_mode=%u"
" samples=%u fragments=%u dcc=%u dcc_addr=0x%010" PRIx64
" cmask_clear=%u cmask_addr=0x%010" PRIx64
" dcc_clear_key=%u clear={%08" PRIx32 ",%08" PRIx32 "}\n",
i, mrt.base.addr, (hw.GetRenderTargetMask() >> (i * 4u)) & 0x0fu,
static_cast<uint32_t>(mrt.info.format), mrt.attrib2.width + 1,
mrt.attrib2.height + 1, static_cast<uint32_t>(mrt.attrib3.tile_mode));
static_cast<uint32_t>(mrt.info.format), static_cast<uint32_t>(mrt.info.channel_type),
static_cast<uint32_t>(mrt.info.channel_order), (sh.m_cbShaderMask >> (i * 4u)) & 0x0fu,
mrt.attrib2.width + 1, mrt.attrib2.height + 1,
static_cast<uint32_t>(mrt.attrib3.tile_mode), static_cast<uint32_t>(bc.enable),
static_cast<uint32_t>(mrt.info.blend_bypass), static_cast<uint32_t>(mrt.info.blend_clamp),
static_cast<uint32_t>(mrt.info.round_mode), mrt.attrib.num_samples,
mrt.attrib.num_fragments,
static_cast<uint32_t>(mrt.info.dcc_compression_enable), mrt.dcc_addr.addr,
static_cast<uint32_t>(mrt.info.cmask_fast_clear_enable), mrt.cmask.addr,
static_cast<uint32_t>(mrt.dcc.dcc_clear_key_enable), mrt.clear_word0.word0,
mrt.clear_word1.word1);
}
}
if (rc.depth_clear_enable && hw.GetDepthClearValue() != 0.0f &&
@@ -177,9 +177,6 @@ static TextureCache::ImageDesc MakeDepthTargetDesc(const CommandBuffer& buffer,
if (z.htile_data_base_addr == 0 || (z.htile_data_base_addr & 0x7fffu) != 0) {
DepthFatal("invalid HTile metadata address");
}
if (z.depth_view.slice_max >= 32) {
DepthFatal("HTile clear tracking supports at most 32 slices");
}
}
if (!z.size.valid) {
DepthFatal("missing depth extent");
+16 -33
View File
@@ -29,10 +29,8 @@ struct CachedImageView {
};
struct ImageUsage {
bool texture = false;
bool storage = false;
bool render_target = false;
bool depth_target = false;
bool video_out = false;
};
@@ -70,40 +68,25 @@ public:
void CopyImageWithBuffer(Image& source, Buffer& buffer, TileManager& tiler);
void CopyMip(Image& source, uint32_t mip, uint32_t layer);
void InvalidateCpuWrite(uint64_t vaddr, uint64_t size) {
if (ImageRangeOverlaps(info.data.address, info.data.size, vaddr, size)) {
m_cpu_dirty = true;
m_maybe_cpu_dirty = false;
m_maybe_hash_valid = false;
} else if (ImagePageRangesOverlap(info.data.address, info.data.size, vaddr, size)) {
m_maybe_cpu_dirty = true;
}
void MarkCpuDirty() {
m_cpu_dirty = true;
m_maybe_cpu_dirty = false;
}
[[nodiscard]] bool IsCpuDirty() const { return m_cpu_dirty || m_maybe_cpu_dirty; }
[[nodiscard]] bool IsDefinitelyCpuDirty() const { return m_cpu_dirty; }
[[nodiscard]] bool IsMaybeCpuDirty() const { return m_maybe_cpu_dirty; }
void MarkMaybeCpuDirty() {
if (!m_cpu_dirty) {
if (!IsCpuDirty()) {
m_maybe_cpu_hash = HashGuestEdges();
m_maybe_cpu_dirty = true;
}
}
[[nodiscard]] bool NeedsMaybeCpuHash() const {
return m_maybe_cpu_dirty && !m_maybe_hash_valid;
}
void SetMaybeCpuHash(uint64_t hash) {
if (!NeedsMaybeCpuHash()) {
EXIT("image cannot initialize maybe-dirty hash\n");
}
m_maybe_cpu_hash = hash;
m_maybe_hash_valid = true;
}
[[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) {
if (!m_maybe_cpu_dirty || !m_maybe_hash_valid || m_cpu_dirty) {
if (!m_maybe_cpu_dirty || m_cpu_dirty) {
EXIT("image cannot resolve maybe-dirty hash\n");
}
m_maybe_cpu_dirty = false;
m_maybe_hash_valid = false;
m_maybe_cpu_dirty = false;
m_cpu_dirty |= hash != m_maybe_cpu_hash;
return m_cpu_dirty;
}
@@ -112,9 +95,8 @@ public:
if (!IsCpuDirty()) {
EXIT("clean image cannot complete a refresh\n");
}
m_cpu_dirty = false;
m_maybe_cpu_dirty = false;
m_maybe_hash_valid = false;
m_cpu_dirty = false;
m_maybe_cpu_dirty = false;
}
[[nodiscard]] bool IsGpuModified() const noexcept { return m_gpu_modified; }
@@ -149,6 +131,8 @@ public:
uint64_t track_addr = 0;
uint64_t track_addr_end = 0;
ImageId depth_id {};
// Stencil associations that refer to this depth image.
std::vector<ImageId> stencil_ids;
// The current stencil plane's mapping into the depth image, also retained by its association.
ImageSubresourceRange stencil_subresources;
uint64_t tick_accessed_last = 0;
@@ -165,12 +149,11 @@ private:
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
uint64_t m_maybe_cpu_hash = 0;
bool m_cpu_dirty = false;
bool m_maybe_cpu_dirty = false;
bool m_maybe_hash_valid = false;
bool m_gpu_modified = false;
bool m_buffer_modified = false;
uint64_t m_maybe_cpu_hash = 0;
bool m_cpu_dirty = false;
bool m_maybe_cpu_dirty = false;
bool m_gpu_modified = false;
bool m_buffer_modified = false;
};
namespace ImageOps {
@@ -21,6 +21,53 @@ bool IsDepthPlane(const ImageInfo& info) {
info.tile_mode == Prospero::TileMode::kLinear);
}
GuestRange TiledMipRange(const ImageInfo& info, uint32_t level) {
const auto& block = info.tiling.block;
if (info.tile_mode != Prospero::TileMode::kStandard4KB ||
block.family != TileBlockFamily::Standard4KB || info.IsVolume() || info.samples != 1 ||
info.HasMetadata() || level >= info.resources.levels || level >= info.mip_layout.size() ||
info.tiling.texel_width == 0 || info.tiling.texel_height == 0) {
return {};
}
const auto extent = info.MipExtent(level);
const auto width = (extent.width + info.tiling.texel_width - 1u) / info.tiling.texel_width;
const auto height = (extent.height + info.tiling.texel_height - 1u) / info.tiling.texel_height;
const auto& mip = info.mip_layout[level];
uint32_t last = 0, origin = 0;
if (!std::has_single_bit(width) || !std::has_single_bit(height) ||
width > block.block_width || height > block.block_height ||
mip.tail_x > block.block_width - width || mip.tail_y > block.block_height - height ||
mip.tail_x % width != 0 || mip.tail_y % height != 0 ||
!TileGetBlockOffset(block, width - 1u, height - 1u, 0, last) ||
!TileGetBlockOffset(block, mip.tail_x, mip.tail_y, 0, origin)) {
return {};
}
// Standard4KB interleaves monotonically ordered X/Y bits, so the last
// corner bounds every active texel. Tiling preserves any holes in this span.
const uint64_t size = uint64_t {last} + block.bytes_per_element;
if ((origin & (std::bit_ceil(size) - 1u)) != 0 || size > block.block_size - origin ||
mip.offset > UINT64_MAX - origin) {
return {};
}
return {mip.offset + origin, size};
}
uint32_t CompactMipSize(const ImageInfo& info) {
if (info.resources.levels != 1 || info.resources.layers != 1) {
return 0;
}
const auto range = TiledMipRange(info, 0);
if (range.size == 0 || range.size % 4 != 0 || range.address != 0) {
return 0;
}
const auto alignment = std::max<uint64_t>(256, std::bit_ceil(range.size));
const auto in_block = info.data.address & (info.tiling.block.block_size - 1u);
return (info.data.address & (alignment - 1u)) == 0 &&
range.size <= info.tiling.block.block_size - in_block
? static_cast<uint32_t>(range.size)
: 0;
}
} // namespace
vk::Extent3D ImageInfo::MipExtent(uint32_t level) const {
@@ -34,6 +81,7 @@ void ImageInfo::UpdateSize() {
extent.height == 0 || extent.depth == 0 || resources.levels == 0 ||
resources.levels > mip_layout.size() || resources.layers == 0 || samples == 0 ||
!std::has_single_bit(samples));
const bool auto_size = data.size == 0;
TileTextureElementLayout element {};
EXIT_NOT_IMPLEMENTED(!TileGetTextureElementLayout(guest_format, element));
if (bytes_per_block == 0) {
@@ -164,9 +212,86 @@ void ImageInfo::UpdateSize() {
linear_offset = Add(mip.linear_offset, Multiply(mip.linear_size, active.depth));
mip_pitch = std::max(mip_pitch >> 1u, 1u);
}
const auto compact_size = CompactMipSize(*this);
if (compact_size != 0 && (auto_size || data.size == compact_size)) {
data.size = mip_layout[0].size = compact_size;
}
}
}
uint32_t ImageInfo::DataAlignment() const {
if (!IsTiled()) {
return 256;
}
const auto compact_size = CompactMipSize(*this);
if (compact_size != 0 && data.size == compact_size && mip_layout[0].size == compact_size) {
return std::max(256u, std::bit_ceil(compact_size));
}
return tiling.block.block_size;
}
int32_t ImageInfo::SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
if (!IsCompatible(container) || tile_mode != container.tile_mode || type != container.type ||
IsVolume() || resources.levels != 1 || resources.layers == 0 ||
container.resources.layers == 0 || mip < 0 ||
static_cast<uint32_t>(mip) >= container.resources.levels ||
container.resources.levels > container.mip_layout.size() ||
!data.Valid() || !container.data.Valid() || data.address < container.data.address ||
data.End() > container.data.End()) {
return -1;
}
if (HasStencil() != container.HasStencil() ||
(HasStencil() && (stencil.address < container.stencil.address ||
stencil.End() > container.stencil.End()))) {
return -1;
}
const auto level = static_cast<uint32_t>(mip);
const auto& layout = container.mip_layout[level];
if (extent.width != std::max(container.extent.width >> level, 1u) ||
extent.height != std::max(container.extent.height >> level, 1u) ||
extent.depth != container.extent.depth || mip_layout[0].pitch != layout.pitch ||
mip_layout[0].height != layout.height || layout.size == 0 ||
layout.size % container.resources.layers != 0 ||
container.data.size % container.resources.layers != 0 ||
data.size % resources.layers != 0) {
return -1;
}
// PS5 block slices contain the entire reversed mip chain. A rebased tail
// descriptor covers its addressed span within the shared tail block.
const auto slice_stride = container.data.size / container.resources.layers;
const auto child_stride = data.size / resources.layers;
auto mip_offset = layout.offset;
auto mip_size = layout.size / container.resources.layers;
const auto compact_size = CompactMipSize(*this);
if (compact_size != 0 && child_stride == compact_size && mip_layout[0].size == compact_size) {
const auto range = TiledMipRange(container, level);
if (range.size != compact_size) {
return -1;
}
mip_offset = range.address;
mip_size = range.size;
} else if (mip_layout[0].tail_x != layout.tail_x || mip_layout[0].tail_y != layout.tail_y) {
return -1;
}
if (child_stride != mip_size || mip_offset >= slice_stride ||
child_stride > slice_stride - mip_offset ||
(resources.layers > 1 && child_stride != slice_stride)) {
return -1;
}
const auto address_delta = data.address - container.data.address;
if (address_delta < mip_offset || (address_delta - mip_offset) % slice_stride != 0) {
return -1;
}
const auto layer = (address_delta - mip_offset) / slice_stride;
if (layer > INT32_MAX || layer >= container.resources.layers ||
resources.layers > container.resources.layers - layer) {
return -1;
}
return static_cast<int32_t>(layer);
}
uint64_t ImageInfo::LinearSize(uint32_t levels) const {
levels = levels == 0 ? resources.levels : levels;
EXIT_IF(levels == 0 || levels > resources.levels || levels > mip_layout.size());
@@ -27,7 +27,7 @@ struct ImageMetadataInfo {
GuestRange range;
ImageMetadataKind kind = ImageMetadataKind::None;
uint32_t control = 0;
uint32_t clear_word = 0;
uint64_t clear_word = 0;
VideoOutCompression compression = VideoOutCompression::Uncompressed;
bool stencil_compressed = false;
bool clear_register_valid = false;
@@ -69,7 +69,7 @@ struct ImageInfo {
GuestRange data;
GuestRange stencil;
ImageMetadataInfo metadata;
uint32_t htile_clear_mask = UINT32_MAX;
bool htile_initial_clear = true;
vk::Format pixel_format = vk::Format::eUndefined;
Prospero::BufferFormat guest_format = Prospero::BufferFormat::kInvalid;
Prospero::ImageType type = Prospero::ImageType::kColor2D;
@@ -88,6 +88,7 @@ struct ImageInfo {
uint32_t first_tail_level = 16;
void UpdateSize();
[[nodiscard]] uint32_t DataAlignment() const;
[[nodiscard]] uint64_t LinearSize(uint32_t levels = 0) const;
[[nodiscard]] vk::Extent3D MipExtent(uint32_t level) const;
[[nodiscard]] std::vector<vk::BufferImageCopy> BufferCopies(uint64_t buffer_offset = 0,
@@ -136,53 +137,7 @@ struct ImageInfo {
}
return -1;
}
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept {
if (!IsCompatible(container) || tile_mode != container.tile_mode || type != container.type ||
IsVolume() || resources.levels != 1 || resources.layers == 0 ||
container.resources.layers == 0 || mip < 0 ||
static_cast<uint32_t>(mip) >= container.resources.levels ||
container.resources.levels > container.mip_layout.size() ||
!data.Valid() || !container.data.Valid() || data.address < container.data.address ||
data.End() > container.data.End()) {
return -1;
}
if (HasStencil() != container.HasStencil() ||
(HasStencil() && (stencil.address < container.stencil.address ||
stencil.End() > container.stencil.End()))) {
return -1;
}
const auto level = static_cast<uint32_t>(mip);
const auto& layout = container.mip_layout[level];
if (extent.width != std::max(container.extent.width >> level, 1u) ||
extent.height != std::max(container.extent.height >> level, 1u) ||
extent.depth != container.extent.depth || mip_layout[0].pitch != layout.pitch ||
mip_layout[0].height != layout.height || layout.size == 0 ||
layout.size % container.resources.layers != 0 ||
container.data.size % container.resources.layers != 0 ||
data.size % resources.layers != 0) {
return -1;
}
// PS5 block slices contain the entire reversed mip chain.
const auto slice_stride = container.data.size / container.resources.layers;
const auto child_stride = data.size / resources.layers;
if (child_stride != layout.size / container.resources.layers ||
layout.offset >= slice_stride || child_stride > slice_stride - layout.offset ||
(resources.layers > 1 && child_stride != slice_stride)) {
return -1;
}
const auto address_delta = data.address - container.data.address;
if (address_delta < layout.offset || (address_delta - layout.offset) % slice_stride != 0) {
return -1;
}
const auto layer = (address_delta - layout.offset) / slice_stride;
if (layer > INT32_MAX || layer >= container.resources.layers ||
resources.layers > container.resources.layers - layer) {
return -1;
}
return static_cast<int32_t>(layer);
}
[[nodiscard]] int32_t SliceOf(const ImageInfo& container, int32_t mip) const noexcept;
};
struct ImageViewInfo {
@@ -414,9 +369,10 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
return false;
}
[[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint32_t packed,
[[nodiscard]] inline bool DecodePackedColorClear(vk::Format format, uint64_t clear_words,
vk::ClearColorValue& clear) {
vk::ClearColorValue next {};
const auto packed = static_cast<uint32_t>(clear_words);
const auto unorm8 = [](uint32_t value) { return static_cast<float>(value & 0xffu) / 255.0f; };
const auto srgb8 = [](uint32_t value) {
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
@@ -434,6 +390,15 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
(mantissa << 13u));
};
switch (format) {
case vk::Format::eB10G11R11UfloatPack32:
next.float32[0] = float16((packed & 0x7ffu) << 4u);
next.float32[1] = float16(((packed >> 11u) & 0x7ffu) << 4u);
next.float32[2] = float16((packed >> 22u) << 5u);
break;
case vk::Format::eR16G16B16A16Sfloat:
next.float32[2] = float16(static_cast<uint32_t>(clear_words >> 32u));
next.float32[3] = float16(static_cast<uint32_t>(clear_words >> 48u));
[[fallthrough]];
case vk::Format::eR16G16Sfloat:
next.float32[1] = float16(packed >> 16u);
[[fallthrough]];
@@ -444,9 +409,10 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
case vk::Format::eR16Unorm:
next.float32[0] = static_cast<float>(packed & 0xffffu) / 65535.0f;
break;
// A single-plane float target carries its clear as raw float bits, the same encoding the
// depth decoder below uses. Without this the clear is discarded and the target keeps stale
// contents.
// Each clear register stores one raw 32-bit float channel.
case vk::Format::eR32G32Sfloat:
next.float32[1] = std::bit_cast<float>(static_cast<uint32_t>(clear_words >> 32u));
[[fallthrough]];
case vk::Format::eR32Sfloat: next.float32[0] = std::bit_cast<float>(packed); break;
case vk::Format::eR32Uint: next.uint32[0] = packed; break;
case vk::Format::eR32Sint: next.int32[0] = static_cast<int32_t>(packed); break;
@@ -507,7 +473,7 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
clear = vk::ClearColorValue {};
return true;
}
return DecodePackedColorClear(format, packed, clear);
return DecodePackedColorClear(format, uint64_t {packed} | (uint64_t {packed} << 32u), clear);
}
[[nodiscard]] inline bool DecodePackedStencilClear(uint32_t packed, uint8_t& clear) {
@@ -31,18 +31,6 @@ namespace ImageViewOps {
ImageViewOps::IsFormatDepthCompatible(view_format);
}
[[nodiscard]] inline bool IsValidImageSwizzle(uint32_t swizzle,
uint32_t component_count = 4) noexcept {
if ((swizzle & ~0xfffu) != 0) {
return false;
}
for (uint32_t channel = 0; channel < 4; channel++) {
const auto selector = GetDstSel(swizzle, channel);
if (selector > 1 && (selector < 4 || selector >= 4 + component_count)) return false;
}
return true;
}
[[noreturn]] inline void UnsupportedColorView(const char* usage, vk::Format image_format,
vk::Format view_format, uint32_t swizzle) noexcept {
EXIT("unsupported %s color image view: image_format=%d view_format=%d swizzle=0x%03x\n", usage,
@@ -64,7 +64,10 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layo
Prospero::ChannelOrder order) {
const auto encoding = Prospero::ResolveRenderTargetFormat(layout, type);
if (encoding.IsValid() && encoding.SupportsOrder(order)) {
const auto host_format = ResolveHostFormat(encoding.buffer_format, order);
const auto backing_format = encoding.buffer_format == Prospero::BufferFormat::k11_11_10UNorm
? Prospero::RemapTextureFormat(encoding.buffer_format)
: encoding.buffer_format;
const auto host_format = ResolveHostFormat(backing_format, order);
const auto bytes = Prospero::RenderTargetBytesPerElement(encoding.buffer_format);
if (host_format.format != vk::Format::eUndefined && bytes != 0) {
const auto order_mapping =
@@ -76,6 +76,10 @@ vk::DescriptorType NativeDescriptorType(BindingKind kind) {
}
vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture, uint32_t element) {
if (!texture.image_id) {
EXIT_IF(texture.image_view != nullptr || !texture.mip_views.empty());
return {nullptr, nullptr, vk::ImageLayout::eGeneral};
}
vk::ImageView view = nullptr;
if (texture.mip_views.empty()) {
if (element == 0u) {
@@ -84,7 +88,7 @@ vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture, uint32_t el
} else if (element < texture.mip_views.size()) {
view = texture.mip_views[element];
}
EXIT_IF(!texture.image_id || view == nullptr || texture.layout == vk::ImageLayout::eUndefined);
EXIT_IF(view == nullptr || texture.layout == vk::ImageLayout::eUndefined);
return {nullptr, view, texture.layout};
}
@@ -308,9 +312,10 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
const bool descriptor_ok = IsSupportedStorageTextureDescriptor(resource, descriptor);
const bool encoding_ok = IsSupportedStorageTextureEncoding(resource, descriptor);
const bool uint_resource = resource.numeric_class == Prospero::TextureNumericClass::Uint;
const bool raw_sint_storage = format == Prospero::BufferFormat::k32SInt && uint_resource &&
resource.written && !resource.read && !resource.atomic;
const auto numeric_class = Prospero::SampledTextureNumericClass(format);
const bool raw_sint_storage = uint_resource &&
ShaderRecompiler::IR::RawSignedStorageFormat(resource, format) != Prospero::BufferFormat::kInvalid;
const auto numeric_class =
Prospero::SampledTextureNumericClass(Prospero::RemapTextureFormat(format));
const bool raw_float_atomic = format == Prospero::BufferFormat::k32Float && uint_resource &&
resource.atomic && !resource.atomic64;
const bool format_ok =
@@ -347,39 +352,6 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]);
}
static TextureCache::ImageDesc NullTextureDesc(const ShaderRecompiler::IR::ImageResource& resource,
TextureCache::BindingType binding) {
TextureCache::ImageDesc desc {};
switch (resource.numeric_class) {
case Prospero::TextureNumericClass::Float:
desc.info.guest_format = Prospero::BufferFormat::k32Float;
break;
case Prospero::TextureNumericClass::Uint:
desc.info.guest_format = resource.atomic64 ? Prospero::BufferFormat::k32_32UInt
: Prospero::BufferFormat::k32UInt;
break;
case Prospero::TextureNumericClass::Sint:
desc.info.guest_format = Prospero::BufferFormat::k32SInt;
break;
default: EXIT("null image has unsupported numeric class\n");
}
desc.info.pixel_format = VulkanFormat(desc.info.guest_format);
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {1, 1, 1};
desc.info.resources = {1, 1};
desc.info.bytes_per_block = Prospero::NumBytesPerElement(desc.info.guest_format);
desc.info.samples = 1;
desc.info.mip_layout[0] = {0, 0, 1, 1};
desc.view_info.format = resource.atomic64 ? vk::Format::eR64Uint : desc.info.pixel_format;
desc.view_info.type = vk::ImageViewType::e2D;
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
desc.view_info.usage = binding == TextureCache::BindingType::Storage
? vk::ImageUsageFlagBits::eStorage
: vk::ImageUsageFlagBits::eSampled;
desc.type = binding;
return desc;
}
static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, vk::Format format,
const SurfaceFormatInfo& surface_format, bool storage,
@@ -515,10 +487,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
auto& texture_cache = m_context.GetTextureCache();
if (descriptor.IsNull()) {
auto desc = NullTextureDesc(resource, storage ? TextureCache::BindingType::Storage
: TextureCache::BindingType::Texture);
const auto id = texture_cache.FindImage(desc);
return {id, nullptr, std::move(desc)};
return {.layout = vk::ImageLayout::eGeneral};
}
const auto address = descriptor.Base40();
@@ -566,7 +535,8 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
const bool shader_conversion =
surface_format.conversion_format != Prospero::BufferFormat::kInvalid;
const bool sampled_numeric_class =
storage || resource.numeric_class == Prospero::SampledTextureNumericClass(format);
storage || resource.numeric_class ==
Prospero::SampledTextureNumericClass(Prospero::RemapTextureFormat(format));
if (!storage && resource.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled &&
!sampled_numeric_class) {
EXIT("sampled image numeric class mismatch: numeric=%u format=%u addr=0x%016" PRIx64 "\n",
@@ -585,10 +555,11 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
pixel_format = depth_format->depth_attachment_format;
}
}
const auto raw_sint_format = ShaderRecompiler::IR::RawSignedStorageFormat(resource, format);
const auto storage_view_format = resource.atomic64 ? vk::Format::eR64Uint
: storage && (resource.atomic ||
format == Prospero::BufferFormat::k32SInt)
? vk::Format::eR32Uint
: storage && resource.atomic ? vk::Format::eR32Uint
: raw_sint_format != Prospero::BufferFormat::kInvalid
? VulkanFormat(raw_sint_format)
: SrgbStorageViewFormat(pixel_format);
const auto view_format = storage && storage_view_format != vk::Format::eUndefined
? storage_view_format
@@ -605,6 +576,9 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
block_bytes != 0 ? block_bytes : Prospero::NumBytesPerElement(format);
desc.info.samples = samples;
desc.info.tile_mode = tile;
if (!resource.r128 && descriptor.MetaCompress() && tile != Prospero::TileMode::kDepth) {
desc.info.metadata.kind = ImageMetadataKind::Dcc;
}
if (levels > physical_levels) {
if (!ResolveTextureMipView(desc.info, physical_levels,
!resource.r128 && descriptor.MetaCompress(), view_levels,
@@ -617,8 +591,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
} else {
desc.info.UpdateSize();
}
const auto alignment =
tile == Prospero::TileMode::kLinear ? 256u : desc.info.tiling.block.block_size;
const auto alignment = desc.info.DataAlignment();
EXIT_NOT_IMPLEMENTED(desc.info.data.size == 0 || alignment == 0 ||
(address & (static_cast<uint64_t>(alignment) - 1u)) != 0);
if (storage) {
@@ -629,7 +602,6 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
(void)TileGetDccSize(width, height, volume ? depth : image_layers,
desc.info.bytes_per_block, physical_levels, tile, metadata_size,
std::countr_zero(samples));
desc.info.metadata.kind = ImageMetadataKind::Dcc;
desc.info.metadata.range = {descriptor.MetaAddr() << 8u, metadata_size.size};
desc.info.metadata.dcc_alpha_msb = descriptor.DccAlphaPos();
}
@@ -685,10 +657,10 @@ static vk::DescriptorBufferInfo NativeUpload(RenderContext& context,
}
void RenderExecutor::BindImage(ImageId id, bool storage) {
auto& image = m_context.GetTextureCache().GetImage(id);
if (image.info.data.Empty()) {
if (!id) {
return;
}
auto& image = m_context.GetTextureCache().GetImage(id);
if (image.binding.is_bound) {
image.binding.force_general |= image.binding.shader_write != storage;
}
@@ -841,8 +813,9 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
auto& texture_cache = m_context.GetTextureCache();
// Acquire each view before a later overlapping descriptor can replace its image.
for (uint32_t i = 0; i < program.info.images.size(); i++) {
if (!images[i].image_id) continue;
const auto old_image = texture_cache.m_slot_images.try_get(images[i].image_id);
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
if (old_image == nullptr || !old_image->registered ||
old_image->binding.needs_rebind) {
if (old_image != nullptr) {
old_image->binding = {};
@@ -873,7 +846,6 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
auto& image = texture_cache.GetImage(binding.image_id);
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
image.usage.storage |= storage;
image.usage.texture |= !storage;
}
}
@@ -894,7 +866,7 @@ void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const>
for (auto& target: colors) {
EXIT_IF(!target.image_id);
const auto old_image = cache.m_slot_images.try_get(target.image_id);
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
if (old_image == nullptr || !old_image->registered ||
old_image->binding.needs_rebind) {
if (old_image != nullptr) {
old_image->binding = {};
@@ -1004,19 +976,14 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
}
for (uint32_t i = 0; i < program.info.images.size(); i++) {
auto& image = m_context.GetTextureCache().GetImage(descriptors.images[i].image_id);
auto& binding = descriptors.images[i];
if (!binding.image_id) continue;
auto& image = m_context.GetTextureCache().GetImage(binding.image_id);
const auto& view = binding.desc.view_info;
const ImageSubresourceRange range {view.base_level, view.level_count, view.base_layer,
view.layer_count};
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
if (image.info.data.Empty()) {
image.Transit(vk::ImageLayout::eGeneral,
storage ? vk::AccessFlagBits2::eShaderRead |
vk::AccessFlagBits2::eShaderWrite
: vk::AccessFlagBits2::eShaderRead,
range, vk_buffer);
} else if (image.binding.is_target) {
if (image.binding.is_target) {
const auto layout = image.binding.attachment_layout;
EXIT_IF(layout == vk::ImageLayout::eUndefined);
if (image.info.IsDepth()) {
@@ -1117,7 +1084,8 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
for (uint32_t i = 0; i < program.info.images.size(); ++i) {
const auto& resource = program.info.images[i];
const auto& texture = descriptors.images[i];
EXIT_IF(resource.mip_count != (texture.mip_views.empty() ? 1u : texture.mip_views.size()));
EXIT_IF(texture.image_id &&
resource.mip_count != (texture.mip_views.empty() ? 1u : texture.mip_views.size()));
const auto kind = *ShaderRecompiler::IR::DescriptorBindingForImage(resource);
const auto start = image_starts[ShaderRecompiler::IR::ImageBindingIndex(kind)] +
resource.descriptor_index;
@@ -6,6 +6,7 @@
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/threads.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
@@ -39,6 +40,8 @@ namespace Libs::Graphics {
namespace {
constexpr auto DriverCacheSaveInterval = std::chrono::minutes {5};
uint8_t RemapSourceAlphaFactor(uint8_t factor) {
switch (static_cast<Prospero::BlendFactor>(factor)) {
case Prospero::BlendFactor::kSrcAlpha:
@@ -148,7 +151,7 @@ void DumpShaderOriginal(const char* stage_name, uint64_t shader_hash,
}
bool ValidateShaderSpirv(const char* label, uint64_t shader_hash,
const std::vector<uint32_t>& spirv) {
const std::vector<uint32_t>& spirv, bool maintenance8_enabled) {
if (!Config::ShaderValidationEnabled()) {
return true;
}
@@ -160,7 +163,9 @@ bool ValidateShaderSpirv(const char* label, uint64_t shader_hash,
static_cast<int>(position.column), static_cast<int>(position.index),
message);
});
if (tools.Validate(spirv)) {
spvtools::ValidatorOptions validator_options;
validator_options.SetAllowOffsetTextureOperand(maintenance8_enabled);
if (tools.Validate(spirv.data(), spirv.size(), validator_options)) {
return true;
}
spvtools::SpirvTools disassembler(SPV_ENV_VULKAN_1_2);
@@ -296,14 +301,15 @@ struct PipelineCache::ProgramCache {
std::span<const uint32_t> function_code) {
auto result = ShaderRecompiler::CompileProgram(std::move(translated), options,
specialization, push_data_start_dword);
if (!ValidateShaderSpirv(options.dump_label, options.shader_hash, result.spirv)) {
if (!ValidateShaderSpirv(options.dump_label, options.shader_hash, result.spirv,
options.maintenance8_enabled)) {
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);
const auto module = CompileSPV(result.spirv, device);
const auto module = CompileSPV(result.spirv, graphics.device);
EXIT_IF(module == nullptr);
if (options.dump_ir) {
LOGF("%s SPIR-V words=%" PRIu64 " wave_size=%u\n", options.dump_label,
@@ -344,6 +350,7 @@ struct PipelineCache::ProgramCache {
};
if constexpr (std::is_same_v<InputInfo, ShaderComputeInputInfo>) {
runtime.workgroup_counts = input_info.workgroup_counts;
runtime.indirect_args_address = input_info.indirect_args_address;
}
bool source_changed = false;
if (entry != programs.end()) {
@@ -384,11 +391,13 @@ struct PipelineCache::ProgramCache {
}
ShaderRecompiler::CompileOptions options;
options.stage = stage;
options.maintenance8_enabled = graphics.maintenance8_enabled;
options.shader_hash = params.hash;
options.user_data = user_data;
options.back_code = params.back_code;
options.dump_ir = Config::GetShaderLogDirection() != Config::LogDirection::Silent;
options.early_dump = options.dump_ir;
options.emit_debug_info = Config::GraphicsDebugDumpEnabled();
options.dump_label = label;
options.input_info = stage_input;
@@ -456,26 +465,27 @@ struct PipelineCache::ProgramCache {
return permutation.handle;
}
explicit ProgramCache(vk::Device device): device(device) {
explicit ProgramCache(GraphicContext& graphics): graphics(graphics) {
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);
graphics.device.destroyShaderModule(permutation.handle.module, nullptr);
}
}
}
std::unordered_map<ProgramKey, SourceEntry, ProgramKeyHash> programs;
ProgramKey lookup_key;
vk::Device device;
GraphicContext& graphics;
uint64_t next_shader_id = 0;
};
PipelineCache::PipelineCache(GraphicContext& graphics)
: m_graphics(graphics), m_program_cache(std::make_unique<ProgramCache>(graphics.device)) {
: m_graphics(graphics), m_program_cache(std::make_unique<ProgramCache>(graphics)),
m_next_driver_cache_save(std::chrono::steady_clock::now() + DriverCacheSaveInterval) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
InitializeDriverCache();
}
@@ -583,10 +593,21 @@ void PipelineCache::InitializeDriverCache() {
}
}
void PipelineCache::Save() {
void PipelineCache::CheckpointDriverCache() {
if (m_driver_cache == nullptr) {
return;
}
m_driver_cache_dirty = true;
if (std::chrono::steady_clock::now() >= m_next_driver_cache_save) {
Save();
}
}
void PipelineCache::Save() {
if (m_driver_cache == nullptr || !m_driver_cache_dirty) {
return;
}
m_next_driver_cache_save = std::chrono::steady_clock::now() + DriverCacheSaveInterval;
size_t size = 0;
vk::Result result;
@@ -629,16 +650,19 @@ void PipelineCache::Save() {
}
const bool flushed = !file.IsInvalid() && file.Flush();
file.Close();
if (prefix_written != prefix.size() || payload_written != payload.size() || !flushed ||
!Common::File::RenameFile(temp_path, m_driver_cache_path)) {
const bool written = prefix_written == prefix.size() && payload_written == payload.size() && flushed;
std::error_code error;
if (written) {
std::filesystem::rename(temp_path, m_driver_cache_path, error);
}
if (!written || error) {
PipelineCacheLog("Vulkan pipeline cache: failed to write {}",
Common::PathToString(m_driver_cache_path));
return;
}
PipelineCacheLog("Vulkan pipeline cache: saved {} bytes to {}", payload.size(),
Common::PathToString(m_driver_cache_path));
m_graphics.device.destroyPipelineCache(m_driver_cache, nullptr);
m_driver_cache = nullptr;
m_driver_cache_dirty = false;
}
PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
@@ -654,6 +678,17 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
vertex_params[0] = PrepareProgram(vertex_regs, context, user_config, vertex_info[0]);
}
const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh;
const bool object_primitive_id = context.HasObjectPrimitiveIdOutput();
if (object_primitive_id) {
// CxObjectIdMode: register source plus a 32-bit pipelined primitive-ID addition.
EXIT_NOT_IMPLEMENTED(sh.m_paClObjprimIdCntl != 2u || mesh_active || tess_active ||
!pixel_active || (sh.m_vgtDrawPayloadCntl & 8u) == 0 ||
user_config.GetPrimType() != Prospero::PrimitiveType::kTriList);
const auto& target = context.GetRenderTarget(7);
EXIT_NOT_IMPLEMENTED(Prospero::ResolveRenderTargetFormat(target.info.format,
target.info.channel_type)
.buffer_format != Prospero::BufferFormat::k32UInt);
}
if (mesh_active) {
EXIT_NOT_IMPLEMENTED(!m_graphics.mesh_shader_enabled);
auto& mesh = vertex_info[0].mesh;
@@ -675,6 +710,29 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
ShaderParams pixel_params;
if (pixel_active) {
pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
pixel_info.object_primitive_id = object_primitive_id;
for (uint32_t export_index = 0; export_index < 8u; export_index++) {
const auto slot = ShaderPixelExportTarget(pixel_info.target_shader_mask, export_index);
if (slot >= 8u || pixel_info.target_output_mode[export_index] == 0u) continue;
const auto& rt = context.GetRenderTarget(slot);
const auto target_mask = render_target_mask_slot(context.GetRenderTargetMask(), slot);
if (rt.base.addr == 0 || target_mask == 0u) continue;
const auto format = Prospero::ResolveRenderTargetFormat(rt.info.format, rt.info.channel_type);
if (format.buffer_format != Prospero::BufferFormat::k11_11_10UNorm) continue;
const auto mapping = pixel_info.target_export_mapping[export_index];
const auto shader_mask = render_target_mask_slot(pixel_info.target_shader_mask, slot);
const bool blend = context.GetBlendControl(slot).enable && !rt.info.blend_bypass;
if (blend || (mapping.ApplyMask(target_mask) & 7u) != 7u ||
(mapping.ApplyMask(shader_mask) & 7u) != 7u ||
pixel_info.target_output_mode[export_index] != 9u) {
EXIT("unsupported packed UNorm render-target export: slot=%u export=%u mode=%u "
"target_mask=0x%x shader_mask=0x%x mapping=0x%02x blend=%u\n",
slot, export_index, pixel_info.target_output_mode[export_index], target_mask,
shader_mask, mapping.packed, static_cast<uint32_t>(blend));
}
pixel_info.target_packed_unorm_mask |= 1u << export_index;
pixel_info.target_round_mode |= static_cast<uint8_t>(rt.info.round_mode) << export_index;
}
if (!mesh_active && !tess_active && Prospero::IsRectList(user_config.GetPrimType())) {
pixel_info.parameter_mode = ShaderPixelParameterMode::Rectangle;
} else if (context.GetModeControl().provoking_vtx_last) {
@@ -731,8 +789,8 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
static_cast<float>(std::min(limits.maxViewportDimensions[1], 16384u)) * 0.5f;
clip.enabled = true;
}
uint32_t push_data_cursor =
mesh_active ? ShaderRecompiler::IR::PushData::MeshDrawDwordCount : 0;
uint32_t push_data_cursor = ShaderRecompiler::IR::PushData::GraphicsDrawDwords(
mesh_active, object_primitive_id);
GraphicsPrograms result;
if (pixel_active) {
result.pixel = m_program_cache->Get(pixel_params, pixel_info, push_data_cursor);
@@ -797,6 +855,9 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
EXIT_IF(!colors[i].image_id || colors[i].desc.view_info.format == vk::Format::eUndefined);
static_params.color_mask[slot] = colors[i].export_mapping.ApplyMask(
render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot));
if (colors[i].desc.info.guest_format == Prospero::BufferFormat::k11_11_10UNorm) {
static_params.color_mask[slot] = 1u;
}
rendering.color_formats[slot] = colors[i].desc.view_info.format;
if (attachment_samples == 0) {
attachment_samples = colors[i].desc.info.samples;
@@ -952,6 +1013,7 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
auto [iter, inserted] = m_graphics_pipelines.emplace(std::move(key), std::move(cached));
EXIT_IF(!inserted);
CheckpointDriverCache();
return *iter->second;
}
@@ -980,6 +1042,7 @@ PipelineCache::GetComputePipeline(const ShaderComputeInputInfo& input_info,
auto [iter, inserted] = m_compute_pipelines.emplace(compute_program.id, std::move(cached));
EXIT_IF(!inserted);
CheckpointDriverCache();
return *iter->second;
}
@@ -8,6 +8,7 @@
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/shader.h"
#include <chrono>
#include <cstddef>
#include <filesystem>
#include <memory>
@@ -173,11 +174,14 @@ private:
std::unique_ptr<ProgramCache> m_program_cache;
vk::PipelineCache m_driver_cache = nullptr;
std::filesystem::path m_driver_cache_path;
std::chrono::steady_clock::time_point m_next_driver_cache_save;
bool m_driver_cache_dirty = false;
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<Pipeline>, GraphicsPipelineKeyHash>
m_graphics_pipelines;
std::unordered_map<uint64_t, std::unique_ptr<Pipeline>> m_compute_pipelines;
void InitializeDriverCache();
void CheckpointDriverCache();
};
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
@@ -19,6 +19,7 @@
#include "graphics/shader/shader.h"
#include <algorithm>
#include <array>
#include <bit>
#include <limits>
#include <span>
@@ -192,9 +193,30 @@ static void AddLayoutBindings(std::vector<vk::DescriptorSetLayoutBinding>& descr
static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
std::span<const vk::DescriptorSetLayoutBinding> bindings) {
uint32_t descriptor_count = 0;
const auto& limits = graphics.GetPhysicalDeviceProperties().limits;
uint64_t descriptor_count = 0;
uint64_t storage_buffers = 0;
std::array<uint64_t, 32> stage_buffers {}, stage_resources {};
for (const auto& binding: bindings) {
descriptor_count += binding.descriptorCount;
const bool storage = binding.descriptorType == vk::DescriptorType::eStorageBuffer;
if (storage) storage_buffers += binding.descriptorCount;
for (auto stages = static_cast<uint32_t>(binding.stageFlags); stages != 0; stages &= stages - 1u) {
const auto stage = std::countr_zero(stages);
if (storage) stage_buffers[stage] += binding.descriptorCount;
if (binding.descriptorType != vk::DescriptorType::eSampler)
stage_resources[stage] += binding.descriptorCount;
if (stage_buffers[stage] > limits.maxPerStageDescriptorStorageBuffers ||
stage_resources[stage] > limits.maxPerStageResources) {
EXIT("native descriptor stage %u exceeds device limits: buffers=%" PRIu64
"/%u resources=%" PRIu64 "/%u\n", stage, stage_buffers[stage],
limits.maxPerStageDescriptorStorageBuffers, stage_resources[stage], limits.maxPerStageResources);
}
}
}
if (storage_buffers > limits.maxDescriptorSetStorageBuffers) {
EXIT("native storage-buffer descriptors exceed device limit: %" PRIu64 "/%u\n",
storage_buffers, limits.maxDescriptorSetStorageBuffers);
}
pipeline.uses_push_descriptors = descriptor_count <= graphics.max_push_descriptors;
+4 -2
View File
@@ -196,11 +196,13 @@ private:
std::span<PreparedBindings* const> stages = {});
[[nodiscard]] bool ResolveColorTargets(CommandBuffer& buffer,
uint32_t render_target_slice_offset);
[[nodiscard]] bool ConsumeMetadataColorOperation(CommandBuffer& buffer,
uint32_t render_target_slice_offset);
void BindImage(ImageId id, bool storage);
void BindRenderTarget(ImageId id);
void ResetBindings();
[[nodiscard]] bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const CommandBuffer& buffer);
void UpdateComputeMetaClears(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);
@@ -22,6 +22,7 @@
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/shader.h"
#include "kernel/eventQueue.h"
#include "kernel/memory.h"
#include "kernel/pthread.h"
#include "libs/errno.h"
@@ -49,8 +50,8 @@ static bool FillSourcesDisjoint(std::span<const ShaderRecompiler::IR::Descriptor
return true;
}
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const CommandBuffer& buffer) {
void RenderExecutor::UpdateComputeMetaClears(const ShaderComputeInputInfo& input,
const CommandBuffer& buffer) {
const auto& program = *input.stage.program;
const auto& resources = *input.stage.resources;
if (resources.buffers.size() != program.info.buffers.size()) {
@@ -60,10 +61,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]);
// 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.
// Reads and transforms of metadata do not establish a clear state.
if ((!resource.written || resource.read) && cache.IsMeta(descriptor.Base48())) {
return false;
return;
}
}
@@ -73,13 +73,10 @@ bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& in
if (resource.written) {
const auto descriptor =
DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
if (cache.ClearMeta(descriptor.Base48())) {
return true;
}
(void)cache.ClearMeta(descriptor.Base48());
}
}
}
return false;
}
bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t group_x,
@@ -306,12 +303,14 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
const auto& program = *input_info.stage.program;
const auto& resources = *input_info.stage.resources;
if (resources.specialization_reads.empty() &&
(TryConsumeComputeMetaClear(input_info, buffer) ||
TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y,
thread_group_z, mode))) {
ResetBindings();
return;
if (resources.specialization_reads.empty()) {
// Cached metadata addresses can be reused as ordinary buffers; preserve their guest writes.
UpdateComputeMetaClears(input_info, buffer);
if (TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y,
thread_group_z, mode)) {
ResetBindings();
return;
}
}
if (use_thread_dimensions) {
@@ -376,11 +375,12 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
});
const uint32_t frame_num = static_cast<uint32_t>(m_context.GetGpu().GetFrameNum());
LOGF("GraphicsRenderDispatchDirect: frame=%u shader=0x%016" PRIx64
" hash=0x%016" PRIx64 " tick=%" PRIu64
" hash=0x%016" PRIx64 " tick=%" PRIu64 " completed_tick=%" PRIu64
" 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",
frame_num, sh_ctx.GetCs().cs_regs.data_addr, program.shader_hash,
m_context.GetCommandScheduler().CurrentTick(),
m_context.GetCommandScheduler().GetMasterSemaphore().KnownGpuTick(),
thread_group_x, thread_group_y, 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(),
@@ -446,12 +446,19 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
static_cast<uint32_t>(args_addr), static_cast<uint32_t>(args_addr >> 32u),
0, mode, buffer.GetShaders().GetCs().cs_regs.data_addr);
Common::LockGuard lock(m_context.GetMutex());
vk::DispatchIndirectCommand args {};
if (!m_context.GetBufferCache().HasGpuDirtyBytes(args_addr, sizeof(args)) &&
LibKernel::Memory::TryReadBacking(args_addr, &args, sizeof(args)) &&
(args.x == 0 || args.y == 0 || args.z == 0)) {
return;
}
const auto& cs_regs = buffer.GetShaders().GetCs();
if (cs_regs.cs_regs.data_addr == 0) {
return;
}
ShaderComputeInputInfo input_info {};
input_info.async_compute = async_compute;
input_info.indirect_args_address = args_addr;
const auto compute_program = m_context.GetPipelineCache().GetComputeProgram(
cs_regs, buffer.GetRegisters().GetShaderRegisters(), input_info);
buffer.EndRendering();
+40 -26
View File
@@ -585,7 +585,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
static uint32_t DrawColorOutputMask(const HW::Context& ctx) {
const auto& sh_regs = ctx.GetShaderRegisters();
const auto write_mask = ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask;
uint32_t output_mask = 0;
uint32_t output_mask = ctx.HasObjectPrimitiveIdOutput() ? 1u << 7u : 0;
for (uint32_t index = 0; index < RENDER_COLOR_ATTACHMENTS_MAX; index++) {
const auto slot = ShaderPixelExportTarget(sh_regs.m_cbShaderMask, index);
if (slot >= RENDER_COLOR_ATTACHMENTS_MAX) {
@@ -608,16 +608,19 @@ enum class CbColorMode : uint8_t {
DccDecompress = 6,
};
static bool ConsumeMetadataColorOperation(const CommandBuffer& buffer) {
bool RenderExecutor::ConsumeMetadataColorOperation(CommandBuffer& buffer,
uint32_t render_target_slice_offset) {
const auto& ctx = buffer.GetRegisters();
const auto mode = ctx.GetColorControl().mode;
// These special modes run color-buffer metadata or decompression operations. The shader is a
// vehicle for that operation, and its exported color must not be applied as a normal draw.
// Kyty stores expanded Vulkan images rather than compressed guest surfaces, so no equivalent
// hardware pass is emitted. Tracked DCC clear state is materialized on attachment bind;
// future CMask/FMask support can consume its state through the same TextureCache path.
return mode == static_cast<uint8_t>(CbColorMode::EliminateFastClear) ||
mode == static_cast<uint8_t>(CbColorMode::FmaskDecompress) ||
if (mode == static_cast<uint8_t>(CbColorMode::EliminateFastClear)) {
// FCE can precede any ordinary draw. Discover the target to materialize its native
// clear metadata before the expanded image is sampled.
RenderColorInfo target {};
ResolveRenderColorTarget(buffer, target, render_target_slice_offset, 0, true);
return true;
}
// Metadata/decompression shaders must not export colors into the expanded image.
return mode == static_cast<uint8_t>(CbColorMode::FmaskDecompress) ||
mode == static_cast<uint8_t>(CbColorMode::DccDecompress);
}
@@ -902,7 +905,8 @@ bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCal
uint32_t mrt_mask = 0;
if (state.ps_active) {
for (const auto& output: state.ps_input_info.stage.program->info.outputs) {
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt) {
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt ||
output.kind == ShaderRecompiler::IR::StageOutputKind::ObjectPrimitiveId) {
mrt_mask |= 1u << output.location;
}
}
@@ -1038,7 +1042,7 @@ bool RenderExecutor::CopyDepthToColor(CommandBuffer& buffer, const DrawCallInfo&
const auto& hw = buffer.GetRegisters();
auto& depth = state.depth_info;
auto& color = state.color_info[0];
EXIT_NOT_IMPLEMENTED(!m_context.GetGraphics().depth_color_copy_enabled ||
EXIT_NOT_IMPLEMENTED(!m_context.GetGraphics().maintenance8_enabled ||
!Prospero::IsRectList(ucfg.GetPrimType()) ||
draw.IsIndexed() || draw.index_count != 3 || draw.instance_count != 1 ||
emit.first_vertex != 0 || emit.first_instance != 0 ||
@@ -1179,21 +1183,29 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
}
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline, stages);
if (mesh_active) {
const uint32_t draw_data[] {
draw.index_count,
draw.IsIndexed() ? static_cast<uint32_t>(emit.vertex_offset) : emit.first_vertex,
emit.first_instance, index_source.guest_element_size,
static_cast<uint32_t>(index_source.address),
static_cast<uint32_t>(index_source.address >> 32u)};
static_assert(std::size(draw_data) == ShaderRecompiler::IR::PushData::MeshDrawDwordCount);
vk_buffer.pushConstants(pipeline.pipeline_layout,
vk::ShaderStageFlagBits::eMeshEXT |
vk::ShaderStageFlagBits::eFragment,
0, sizeof(draw_data), draw_data);
} else {
if (!mesh_active) {
CommitIndexBuffer(vk_buffer, index_binding);
}
const auto draw_dwords = ShaderRecompiler::IR::PushData::GraphicsDrawDwords(
mesh_active, state.ps_input_info.object_primitive_id);
if (draw_dwords != 0) {
std::array<uint32_t, ShaderRecompiler::IR::PushData::MeshDrawDwordCount> draw_data {};
const auto draw_stages = vk::ShaderStageFlagBits::eFragment |
(mesh_active ? vk::ShaderStageFlagBits::eMeshEXT
: vk::ShaderStageFlagBits::eVertex);
if (mesh_active) {
draw_data = {
draw.index_count,
draw.IsIndexed() ? static_cast<uint32_t>(emit.vertex_offset) : emit.first_vertex,
emit.first_instance, index_source.guest_element_size,
static_cast<uint32_t>(index_source.address),
static_cast<uint32_t>(index_source.address >> 32u)};
} else {
draw_data[0] = buffer.GetUserConfig().GetObjectId();
}
vk_buffer.pushConstants(pipeline.pipeline_layout, draw_stages, 0,
draw_dwords * sizeof(uint32_t), draw_data.data());
}
auto& dynamic_state = m_context.GetCommandScheduler().GetDynamicState();
UpdateGraphicsDynamicState(buffer, dynamic_state, vertex_stages.back(), state.depth_info,
@@ -1257,7 +1269,8 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
return;
}
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) ||
if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) ||
DepthStencilCopy(buffer) ||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
ResetBindings();
return;
@@ -1370,7 +1383,8 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
return;
}
if (ConsumeMetadataColorOperation(buffer) || DepthStencilCopy(buffer) ||
if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) ||
DepthStencilCopy(buffer) ||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
ResetBindings();
return;
+1 -1
View File
@@ -1199,7 +1199,7 @@ struct SystemOverlay::Impl {
ImGui::UnregisterUserTexture(texture.get());
return true;
});
constexpr float TOAST_LIFETIME = 5.0f;
const float TOAST_LIFETIME = static_cast<float>(Config::GetTrophyDurationSeconds());
if (trophy_notification &&
std::chrono::duration<float>(now - trophy_notification_born).count() >=
TOAST_LIFETIME) {
+1
View File
@@ -145,6 +145,7 @@ struct VideoOutOutputStatus {
uint64_t flags = 0;
uint64_t reserved[3] = {};
};
static_assert(sizeof(VideoOutOutputStatus) == 0x30); // Also used by VideoOutVrrStatus.
struct VideoOutOutputOptions {
uint32_t internalData[16] = {};
+12 -4
View File
@@ -266,7 +266,7 @@ public:
void Create();
void Recreate(bool surface_lost = false);
[[nodiscard]] bool NeedsResize() const;
[[nodiscard]] Status AcquireNextImage();
[[nodiscard]] Status AcquireNextImage(CommandScheduler& scheduler);
[[nodiscard]] bool PrepareSystemOverlay();
void RecordPresentCommands(CommandBuffer& command, Presenter::Frame* source,
const Presenter::Layer& overlay, bool draw_system_overlay);
@@ -301,6 +301,7 @@ private:
std::vector<vk::Image> m_images;
std::vector<vk::ImageView> m_image_views;
std::vector<vk::Semaphore> m_image_acquired;
std::vector<uint64_t> m_image_acquired_ticks;
std::vector<vk::Semaphore> m_render_complete;
std::unique_ptr<SystemOverlay> m_system_overlay;
vk::DescriptorSetLayout m_overlay_descriptors = nullptr;
@@ -501,6 +502,7 @@ void Swapchain::Create() {
vk::SemaphoreCreateInfo semaphore_info {};
semaphore_info.sType = vk::StructureType::eSemaphoreCreateInfo;
m_image_acquired.resize(m_images.size());
m_image_acquired_ticks.assign(m_images.size(), 0);
m_render_complete.resize(m_images.size());
for (size_t i = 0; i < m_images.size(); i++) {
RequireVulkanSuccess(
@@ -569,6 +571,7 @@ void Swapchain::Destroy() {
m_images.clear();
m_image_views.clear();
m_image_acquired.clear();
m_image_acquired_ticks.clear();
m_render_complete.clear();
}
@@ -594,8 +597,11 @@ bool Swapchain::NeedsResize() const {
m_window_extent.height != m_window.graphic_ctx.screen_height;
}
Swapchain::Status Swapchain::AcquireNextImage() {
Swapchain::Status Swapchain::AcquireNextImage(CommandScheduler& scheduler) {
EXIT_IF(m_handle == nullptr || m_frame_index >= m_image_acquired.size());
// Repeated frames can wrap the semaphore ring without acquiring a new source frame.
// Wait for the submission that consumed this slot before signaling it again.
scheduler.Wait(m_image_acquired_ticks[m_frame_index]);
m_image_index = static_cast<uint32_t>(-1);
const auto result = m_window.graphic_ctx.device.acquireNextImageKHR(
m_handle, std::numeric_limits<uint64_t>::max(), m_image_acquired[m_frame_index], nullptr,
@@ -867,7 +873,9 @@ uint64_t Swapchain::Submit(CommandScheduler& scheduler) {
SubmitInfo submit;
submit.AddWait(m_image_acquired[m_frame_index], 1, vk::PipelineStageFlagBits::eTransfer);
submit.AddSignal(m_render_complete[m_image_index]);
return scheduler.Submit(submit);
const auto tick = scheduler.Submit(submit);
m_image_acquired_ticks[m_frame_index] = tick;
return tick;
}
Swapchain::Status Swapchain::Present() {
@@ -1023,7 +1031,7 @@ void Presenter::Impl::Present() {
RecoverSwapchain(Swapchain::Status::Recreate);
}
for (uint32_t attempt = 0; attempt < 2; attempt++) {
auto status = swapchain.AcquireNextImage();
auto status = swapchain.AcquireNextImage(present_scheduler);
if (status != Swapchain::Status::Success) {
RecoverSwapchain(status);
continue;
@@ -63,6 +63,8 @@ vk::PhysicalDeviceVulkan12Features WindowContext::RequiredVulkan12Features() noe
features.shaderBufferInt64Atomics = VK_TRUE;
features.storageBuffer8BitAccess = VK_TRUE;
features.shaderSampledImageArrayNonUniformIndexing = VK_TRUE;
features.shaderStorageImageArrayNonUniformIndexing = VK_TRUE;
features.shaderStorageBufferArrayNonUniformIndexing = VK_TRUE;
return features;
}
@@ -215,6 +217,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
vk::PhysicalDeviceVulkan12Features features12 {};
vk::PhysicalDeviceVulkan11Features features11 {};
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2 {};
#if defined(__APPLE__)
features12.pNext = &depth_clip_control;
#else
@@ -222,7 +225,8 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
fragment_barycentric.pNext = &depth_clip_control;
features12.pNext = &fragment_barycentric;
#endif
features11.pNext = features12.pNext;
robustness2.pNext = features12.pNext;
features11.pNext = &robustness2;
features12.pNext = &features11;
features13.pNext = &features12;
device_features2.pNext = &features13;
@@ -284,7 +288,14 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
check_feature(features12.shaderSampledImageArrayNonUniformIndexing,
"shaderSampledImageArrayNonUniformIndexing",
required_features12.shaderSampledImageArrayNonUniformIndexing);
check_feature(features12.shaderStorageImageArrayNonUniformIndexing,
"shaderStorageImageArrayNonUniformIndexing",
required_features12.shaderStorageImageArrayNonUniformIndexing);
check_feature(features12.shaderStorageBufferArrayNonUniformIndexing,
"shaderStorageBufferArrayNonUniformIndexing",
required_features12.shaderStorageBufferArrayNonUniformIndexing);
check_feature(features13.robustImageAccess, "robustImageAccess");
check_feature(robustness2.nullDescriptor, "nullDescriptor");
check_feature(features13.dynamicRendering, "dynamicRendering",
required_features13.dynamicRendering);
check_feature(features13.synchronization2, "synchronization2",
@@ -446,14 +457,9 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
vk::PhysicalDeviceVulkan13Features supported_features13 {};
supported_features13.pNext = &supported_features12;
const auto robustness2_ext_enabled =
HasExtension(device_extensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
vk::PhysicalDeviceRobustness2FeaturesEXT supported_robustness2 {};
if (robustness2_ext_enabled) {
supported_robustness2.pNext = supported_features13.pNext;
supported_features13.pNext = &supported_robustness2;
}
supported_robustness2.pNext = supported_features13.pNext;
supported_features13.pNext = &supported_robustness2;
const bool mesh_extension = HasExtension(device_extensions, VK_EXT_MESH_SHADER_EXTENSION_NAME);
vk::PhysicalDeviceMeshShaderFeaturesEXT supported_mesh {};
@@ -529,6 +535,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
subgroup_size_control.pNext = &graphics.mesh_shader_properties;
}
physical_device.getProperties2(&properties2);
EXIT_NOT_IMPLEMENTED(!(properties11.subgroupSupportedOperations & vk::SubgroupFeatureFlagBits::eVote));
graphics.subgroup_size = properties11.subgroupSize;
graphics.min_subgroup_size = subgroup_size_control.minSubgroupSize;
@@ -549,9 +556,9 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
graphics.attachment_feedback_loop_enabled =
feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout &&
feedback_dynamic.attachmentFeedbackLoopDynamicState;
graphics.depth_color_copy_enabled = maintenance8_extension && maintenance8.maintenance8;
LOGF("Vulkan depth/color copy support: %s\n",
graphics.depth_color_copy_enabled ? "true" : "false");
graphics.maintenance8_enabled = maintenance8_extension && maintenance8.maintenance8;
LOGF("Vulkan maintenance8 support: %s\n",
graphics.maintenance8_enabled ? "true" : "false");
LOGF("Vulkan depth feedback support: %s\n",
graphics.attachment_feedback_loop_enabled ? "true" : "false");
if (graphics.mesh_shader_enabled) {
@@ -607,27 +614,19 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
fragment_barycentric.fragmentShaderBarycentric = VK_TRUE;
robustness2.pNext = &fragment_barycentric;
#endif
if (robustness2_ext_enabled) {
robustness2.robustBufferAccess2 = supported_robustness2.robustBufferAccess2;
robustness2.robustImageAccess2 = supported_robustness2.robustImageAccess2;
robustness2.nullDescriptor = supported_robustness2.nullDescriptor;
}
robustness2.robustBufferAccess2 = supported_robustness2.robustBufferAccess2;
robustness2.robustImageAccess2 = supported_robustness2.robustImageAccess2;
robustness2.nullDescriptor = VK_TRUE;
auto features13 = WindowContext::RequiredVulkan13Features();
#if defined(__APPLE__)
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2)
: static_cast<void*>(&features12);
#else
features13.pNext = robustness2_ext_enabled ? static_cast<void*>(&robustness2)
: static_cast<void*>(&fragment_barycentric);
#endif
features13.pNext = &robustness2;
features13.robustImageAccess = supported_features13.robustImageAccess;
features13.subgroupSizeControl =
graphics.compute_subgroup_size_control_enabled ? VK_TRUE : VK_FALSE;
LOGF("Vulkan robustness: robustImageAccess=%s robustImageAccess2=%s\n",
features13.robustImageAccess == VK_TRUE ? "true" : "false",
robustness2_ext_enabled && robustness2.robustImageAccess2 == VK_TRUE ? "true" : "false");
robustness2.robustImageAccess2 == VK_TRUE ? "true" : "false");
vk::DeviceCreateInfo create_info {};
vk::PhysicalDeviceMeshShaderFeaturesEXT mesh_features {};
@@ -648,7 +647,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
image_atomic_int64.sparseImageInt64Atomics = VK_FALSE;
create_info.pNext = &image_atomic_int64;
}
if (graphics.depth_color_copy_enabled) {
if (graphics.maintenance8_enabled) {
maintenance8.pNext = const_cast<void*>(create_info.pNext);
create_info.pNext = &maintenance8;
}
@@ -957,7 +956,7 @@ void WindowContext::CreateVulkan() {
std::vector<const char*> device_extensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME,
VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
"VK_KHR_maintenance1"};
VK_EXT_ROBUSTNESS_2_EXTENSION_NAME, "VK_KHR_maintenance1"};
#if defined(__APPLE__)
// MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable; the renderer
@@ -1020,8 +1019,7 @@ void WindowContext::CreateVulkan() {
device_extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
graphic_ctx.memory_budget_ext_enabled = true;
}
for (const auto* extension: {VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
VK_KHR_MAINTENANCE_8_EXTENSION_NAME,
for (const auto* extension: {VK_KHR_MAINTENANCE_8_EXTENSION_NAME,
VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME,
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME,
VK_EXT_MESH_SHADER_EXTENSION_NAME,
@@ -566,7 +566,7 @@ void PrepareCallTarget(ShaderSource& source, const CompileOptions& options) {
}
RewriteToSsa(query.blocks);
ConstantPropagationPass(query.blocks, query.wave_size);
RemoveIdentities(query.blocks);
RemoveIdentities(query);
EliminateDeadCode(query.blocks);
for (auto& value: source.call->target) value = value.Instruction()->Arg(0).Resolve();
query.value_storage.clear();
@@ -789,19 +789,19 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
static_cast<uint64_t>(ir.blocks.size()), phase_ms());
IR::RewriteToSsa(ir.blocks);
IR::ConstantPropagationPass(ir.blocks, ir.wave_size);
IR::ResolveControlFlowIdentities(ir);
IR::RemoveIdentities(ir.blocks);
IR::EliminateDeadCode(ir.blocks);
const auto read_lane_stats = IR::EliminateReadLane(ir, ir.wave_size);
if (read_lane_stats.rewritten_reads != 0) {
LOGF("%s read-lane elimination: reads=%" PRIu32 "\n", GetDumpLabel(options),
read_lane_stats.rewritten_reads);
const auto simplify = [&ir] {
IR::ConstantPropagationPass(ir.blocks, ir.wave_size);
IR::ResolveControlFlowIdentities(ir);
IR::RemoveIdentities(ir.blocks);
IR::RemoveIdentities(ir);
IR::EliminateDeadCode(ir.blocks);
};
simplify();
const auto read_lane_stats = IR::EliminateReadLane(ir, ir.wave_size);
if (read_lane_stats.rewritten_reads != 0 || read_lane_stats.rewritten_reductions != 0) {
LOGF("%s read-lane elimination: reads=%" PRIu32 " reductions=%" PRIu32 "\n",
GetDumpLabel(options), read_lane_stats.rewritten_reads, read_lane_stats.rewritten_reductions);
}
// Cleanup also exposes profitable folds by removing dead predicate consumers.
simplify();
if (decoded.has_swap_pc) {
// Leaf returns are resolved CFG edges; native PC values must not escape into GPU data.
for (const auto* block: ir.blocks)
@@ -817,7 +817,7 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
MakeIrDump(cfg_dump, ir).c_str());
}
}
IR::TrackResources(ir, decoded, native_cfg);
IR::TrackResources(ir, decoded, native_cfg, options.input_info);
TranslateResult result;
result.program = std::move(ir);
if (options.dump_ir) {
@@ -871,7 +871,7 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
}
}
ir.value_storage.clear();
IR::RemoveIdentities(ir.blocks);
IR::RemoveIdentities(ir);
IR::EliminateDeadCode(ir.blocks);
IR::CollectShaderInfo(ir, options.input_info);
@@ -885,7 +885,8 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n",
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash);
auto spirv = Spirv::EmitProgram(ir, options.input_info, push_data_start_dword);
auto spirv = Spirv::EmitProgram(ir, options.input_info, push_data_start_dword,
options.maintenance8_enabled, options.emit_debug_info);
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
" elapsed_ms=%" PRIu64 "\n",
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash,
@@ -21,6 +21,8 @@ struct CompileOptions {
uint64_t shader_hash = 0;
bool dump_ir = true;
bool early_dump = false;
bool emit_debug_info = false;
bool maintenance8_enabled = false;
const char* dump_label = nullptr;
std::span<const uint32_t> user_data;
std::span<const uint32_t> back_code;
@@ -318,7 +318,7 @@ void LowerTessellationMemory(IR::Program& program, const CompileOptions& options
}
}
ConstantPropagationPass(program.blocks, program.wave_size);
RemoveIdentities(program.blocks);
RemoveIdentities(program);
EliminateDeadCode(program.blocks);
LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u\n",
options.stage == ShaderType::Local ? "LS"
@@ -160,6 +160,14 @@ void Builder::AddEntryPoint(spv::ExecutionModel execution_model, uint32_t entry_
AppendInstruction(m_entry_points, spv::OpEntryPoint, operands);
}
uint32_t Builder::AddString(const char* text) {
const auto id = AllocateId();
std::vector<uint32_t> operands = {id};
AppendString(operands, text);
AppendInstruction(m_debug, spv::OpString, operands);
return id;
}
void Builder::AddName(uint32_t target, const char* name) {
std::vector<uint32_t> operands = {target};
AppendString(operands, name);
@@ -66,6 +66,7 @@ public:
AppendInstruction(m_execution_modes, spv::OpExecutionMode, entry_point, mode, operands...);
}
uint32_t AddString(const char* text);
void AddName(uint32_t target, const char* name);
template <typename... Args>
void AddAnnotation(spv::Op opcode, const Args&... operands) {
@@ -6,6 +6,7 @@
#include <algorithm>
#include <array>
#include <cstdio>
namespace Libs::Graphics::ShaderRecompiler::Spirv {
@@ -18,7 +19,7 @@ namespace {
}
void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
uint64_t live_buffers = 0;
std::vector<bool> live_buffers(program.info.buffers.size());
bool uses_lds = false;
bool uses_gds = false;
const auto uses_mapping = [](const auto& resource) {
@@ -85,12 +86,12 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
if (memory.resource >= program.info.buffers.size()) {
Fail(program, "typed shader contains an invalid buffer resource");
}
live_buffers |= uint64_t{1} << memory.resource;
live_buffers[memory.resource] = true;
for (const auto child: program.info.buffers[memory.resource].indirect_resources) {
if (child >= program.info.buffers.size()) {
Fail(program, "typed shader contains an invalid indirect buffer resource");
}
live_buffers |= uint64_t{1} << child;
live_buffers[child] = true;
}
}
}
@@ -105,6 +106,11 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
Fail(program, "typed buffer handle has an invalid dense resource");
}
break;
case IR::ValueOpcode::GetBufferResourceIndex:
if (dense >= program.info.buffers.size()) {
Fail(program, "typed buffer selector has an invalid dense resource");
}
break;
case IR::ValueOpcode::GetAddressResource:
if (planning_only_handle(inst)) {
break;
@@ -144,8 +150,9 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
std::array<uint32_t, static_cast<size_t>(Kind::Count)> expected {};
auto& buffer_count = expected[static_cast<size_t>(Kind::Buffers)];
for (uint32_t index = 0; index < program.info.buffers.size(); ++index) {
const auto slot = (live_buffers & (uint64_t{1} << index)) != 0 ? buffer_count++ : UINT32_MAX;
if (program.info.buffers[index].descriptor_index != slot) {
const auto slot = live_buffers[index] ? buffer_count++ : UINT32_MAX;
if (program.info.buffers[index].live != live_buffers[index] ||
program.info.buffers[index].descriptor_index != slot) {
Fail(program, "native buffer descriptor index does not match shader topology");
}
}
@@ -197,7 +204,8 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
} // namespace
std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo input_info,
uint32_t push_data_start_dword) {
uint32_t push_data_start_dword, bool maintenance8_enabled,
bool emit_debug_info) {
using namespace Emitter;
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
@@ -210,12 +218,15 @@ std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo inp
!program.shader_info_complete) {
Fail(program, "SPIR-V emitter requires a fully planned native shader program");
}
if (program.info.buffers.size() > IR::ShaderInfo::MaxBuffers) {
Fail(program, "native shader exceeds the buffer resource limit");
}
IR::ValidateProgram(program, true);
program.bindings = {.push_data_start_dword = push_data_start_dword};
EmitterState state(program, input_info);
EmitterState state(program, input_info, maintenance8_enabled);
if (emit_debug_info) {
char name[64];
std::snprintf(name, sizeof(name), "rdna2-%016" PRIx64 "-block-byte-pc-plus-one",
program.shader_hash);
state.debug_source = state.builder.AddString(name);
}
const auto* workgroup = ShaderWorkgroupInput(program.stage, input_info);
state.lane_count =
workgroup != nullptr && program.wave_size == 64u && workgroup->host_subgroup_size == 32u
@@ -11,7 +11,9 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv {
// Resets and writes backend IDs and descriptor indices; do not emit the same program concurrently.
std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo input_info,
uint32_t push_data_start_dword = 0);
uint32_t push_data_start_dword = 0,
bool maintenance8_enabled = false,
bool emit_debug_info = false);
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
@@ -210,10 +210,23 @@ uint32_t EmitCompositeExtractU32x2(EmitterState& state, uint32_t arg0, IR::Value
}
uint32_t EmitPackFloat2x16Rtz(EmitterState& state, uint32_t arg0, uint32_t arg1) {
const auto low = EmitF32ToF16RtzBits(state, arg0);
const auto high = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
EmitF32ToF16RtzBits(state, arg1), ConstantU32(state, 16));
return Binary(state, spv::OpBitwiseOr, TypeU32(state), low, high);
const auto pair = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 2), pair, arg0, arg1);
const auto packed = EmitPackHalf2x16(state, pair);
const auto back = EmitExt(state, TypeF32Vector(state, 2), GLSLstd450UnpackHalf2x16, {packed});
auto result = packed;
for (uint32_t i = 0; i < 2; i++) {
const auto rounded = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), rounded, back, i);
const auto above =
Binary(state, spv::OpFOrdGreaterThan, TypeBool(state),
EmitExt(state, TypeF32(state), GLSLstd450FAbs, {rounded}),
EmitExt(state, TypeF32(state), GLSLstd450FAbs, {i == 0 ? arg0 : arg1}));
result = Binary(state, spv::OpISub, TypeU32(state), result,
Select(state, TypeU32(state), above,
ConstantU32(state, i == 0 ? 1u : 0x10000u), ConstantU32(state, 0)));
}
return result;
}
uint32_t EmitFPSaturate32(EmitterState& state, uint32_t arg0) {
@@ -9,13 +9,6 @@ uint32_t EmitAndConstant(EmitterState& state, uint32_t value, uint32_t mask) {
return ret;
}
uint32_t EmitShiftRightConstant(EmitterState& state, uint32_t value, uint32_t shift) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), ret, value,
ConstantU32(state, shift));
return ret;
}
uint32_t EmitCompareU32Constant(EmitterState& state, spv::Op opcode, uint32_t value,
uint32_t constant) {
const auto ret = state.builder.AllocateId();
@@ -23,65 +16,6 @@ uint32_t EmitCompareU32Constant(EmitterState& state, spv::Op opcode, uint32_t va
return ret;
}
uint32_t EmitSubConstantMinusU32(EmitterState& state, uint32_t constant, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(spv::OpISub, TypeU32(state), ret, ConstantU32(state, constant),
value);
return ret;
}
uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32) {
const auto bits = state.builder.AllocateId();
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, f32);
const auto sign = EmitAndConstant(state, EmitShiftRightConstant(state, bits, 16), 0x8000u);
const auto exp = EmitAndConstant(state, EmitShiftRightConstant(state, bits, 23), 0xffu);
const auto mant = EmitAndConstant(state, bits, 0x007fffffu);
const auto half_exp = state.builder.AllocateId();
const auto normal_exp = state.builder.AllocateId();
const auto normal_mant = EmitShiftRightConstant(state, mant, 13);
const auto normal_payload = state.builder.AllocateId();
const auto normal = state.builder.AllocateId();
state.builder.AddFunction(spv::OpISub, TypeU32(state), half_exp, exp, ConstantU32(state, 112));
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), normal_exp, half_exp,
ConstantU32(state, 10));
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), normal_payload, normal_exp,
normal_mant);
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), normal, sign, normal_payload);
const auto mant_with_hidden = EmitOrU32(state, mant, ConstantU32(state, 0x00800000u));
const auto raw_sub_shift = EmitSubConstantMinusU32(state, 126, exp);
const auto exp_lt_103 = EmitCompareU32Constant(state, spv::OpULessThan, exp, 103);
const auto exp_gt_112 = EmitCompareU32Constant(state, spv::OpUGreaterThan, exp, 112);
const auto sub_shift_low =
EmitSelectValueU32(state, exp_lt_103, ConstantU32(state, 31), raw_sub_shift);
const auto sub_shift =
EmitSelectValueU32(state, exp_gt_112, ConstantU32(state, 14), sub_shift_low);
const auto sub_mant = state.builder.AllocateId();
const auto subnormal = state.builder.AllocateId();
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), sub_mant, mant_with_hidden,
sub_shift);
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), subnormal, sign, sub_mant);
const auto nan_payload =
EmitOrU32(state, EmitShiftRightConstant(state, mant, 13), ConstantU32(state, 0x0200u));
const auto nan =
EmitOrU32(state, sign, EmitOrU32(state, ConstantU32(state, 0x7c00u), nan_payload));
const auto inf = EmitOrU32(state, sign, ConstantU32(state, 0x7c00u));
const auto max_finite = EmitOrU32(state, sign, ConstantU32(state, 0x7bffu));
const auto mant_zero = EmitCompareU32Constant(state, spv::OpIEqual, mant, 0);
const auto special = EmitSelectValueU32(state, mant_zero, inf, nan);
const auto exp_le_112 = EmitCompareU32Constant(state, spv::OpULessThanEqual, exp, 112);
const auto exp_ge_143 = EmitCompareU32Constant(state, spv::OpUGreaterThanEqual, exp, 143);
const auto exp_eq_255 = EmitCompareU32Constant(state, spv::OpIEqual, exp, 255);
const auto finite0 = EmitSelectValueU32(state, exp_le_112, subnormal, normal);
const auto finite1 = EmitSelectValueU32(state, exp_lt_103, sign, finite0);
const auto finite2 = EmitSelectValueU32(state, exp_ge_143, max_finite, finite1);
return EmitAndConstant(state, EmitSelectValueU32(state, exp_eq_255, special, finite2), 0xffffu);
}
F32Class EmitClassifyF32Bits(EmitterState& state, uint32_t bits) {
F32Class cls;
cls.bits = bits;
@@ -138,10 +138,10 @@ uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension) {
}
namespace {
void DecorateNonUniformDescriptor(EmitterState& state, uint32_t id) {
void DecorateNonUniformDescriptor(EmitterState& state, uint32_t id, spv::Capability capability) {
state.builder.RequireExtension("SPV_EXT_descriptor_indexing");
state.builder.RequireCapability(spv::CapabilityShaderNonUniform);
state.builder.RequireCapability(spv::CapabilitySampledImageArrayNonUniformIndexing);
state.builder.RequireCapability(capability);
state.builder.AddAnnotation(spv::OpDecorate, id, spv::DecorationNonUniform);
}
} // namespace
@@ -154,7 +154,10 @@ uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mi
state.builder.AddFunction(spv::OpLoad, state.images[IR::ImageBindingIndex(kind)].type,
image, pointer);
if (array_index != 0u) {
DecorateNonUniformDescriptor(state, image);
DecorateNonUniformDescriptor(state, image,
IR::ImageBindingResourceClass(kind) == IR::ImageResourceClass::Storage
? spv::CapabilityStorageImageArrayNonUniformIndexing
: spv::CapabilitySampledImageArrayNonUniformIndexing);
}
return image;
}
@@ -170,7 +173,8 @@ uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler, uint32_t a
IR::DescriptorBindingKind::Samplers, sampler, "sampler descriptor array was not emitted");
const auto sampler_id = state.builder.AllocateId();
state.builder.AddFunction(spv::OpLoad, state.sampler_type, sampler_id, pointer);
if (array_index != 0u) DecorateNonUniformDescriptor(state, sampler_id);
if (array_index != 0u)
DecorateNonUniformDescriptor(state, sampler_id, spv::CapabilitySampledImageArrayNonUniformIndexing);
return sampler_id;
}
@@ -188,7 +192,7 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
}
state.builder.AddFunction(spv::OpSampledImage, sampled_type, sampled_image, image, sampler_id);
if (array_index != 0u || sampler_dynamic) {
DecorateNonUniformDescriptor(state, sampled_image);
DecorateNonUniformDescriptor(state, sampled_image, spv::CapabilitySampledImageArrayNonUniformIndexing);
}
return sampled_image;
}
@@ -209,14 +213,14 @@ uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t
}
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
uint32_t texel) {
uint32_t texel, uint32_t array_index) {
const auto& image = state.program.info.images.at(resource);
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage);
if (!image.atomic) {
state.builder.RequireCapability(spv::CapabilityStorageImageWriteWithoutFormat);
}
const auto EmitWrite = [&](uint32_t mip) {
state.builder.AddFunction(spv::OpImageWrite, LoadImageDescriptor(state, resource, mip),
state.builder.AddFunction(spv::OpImageWrite, LoadImageDescriptor(state, resource, mip, array_index),
coord, texel);
};
if (image.mip_mode != IR::ImageMipMode::Dynamic) {
@@ -567,6 +567,28 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
mapping.Map(3));
value = mapped;
}
if ((state.input_info.pixel->target_packed_unorm_mask & (1u << exp.index)) != 0u) {
if (exp.compr || MrtOutputMode(state, exp) != 9u ||
(mapping.ApplyMask(exp.en) & 7u) != 7u) {
EXIT("unsupported packed UNorm EXP: export=%u mode=%u en=0x%x compr=%u\n",
exp.index, MrtOutputMode(state, exp), exp.en, static_cast<uint32_t>(exp.compr));
}
const auto info = Format::GetFormatInfo(Prospero::BufferFormat::k11_11_10UNorm);
const auto rounding = (state.input_info.pixel->target_round_mode & (1u << exp.index)) != 0
? UnormRounding::Truncate : UnormRounding::HalfUp;
const auto raw = Unary(state, spv::OpBitcast, TypeU32Vector(state, 4), value);
auto packed = ConstantU32(state, 0);
for (uint32_t component = 0; component < info.component_count; component++) {
const auto encoded = EncodePackedColorComponent(
state, info, component, ExportRawComponent(ctx, raw, component), 32u, rounding);
packed = component == 0u ? encoded
: Binary(state, spv::OpBitwiseOr, TypeU32(state), packed, encoded);
}
value = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), value,
packed, ConstantU32(state, 0), ConstantU32(state, 0),
ConstantU32(state, 0));
}
}
if (exp.kind == IR::ExportTargetKind::Position &&
state.input_info.vertex->clip_space.enabled) {
@@ -654,17 +676,11 @@ uint32_t EmitLaneId(EmitterState& state) {
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto result = state.builder.AllocateId();
const auto index = inst.Arg(0).U32();
if (state.program.stage != ShaderType::Mesh || index >= IR::PushData::MeshDrawDwordCount) {
ctx.Fail(inst, "invalid mesh draw parameter");
}
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state), pointer,
state.push_constant_variable, ConstantU32(state, 0),
ConstantU32(state, index));
state.builder.AddFunction(spv::OpLoad, TypeU32(state), result, pointer);
return result;
return EmitPushDataLoad(state, ConstantU32(state, index));
}
uint32_t EmitGetUserData(EmitterState& state, IR::ScalarReg reg) {
@@ -721,17 +737,22 @@ uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& inst) {
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst) {
// Scalar branches include lanes disabled by EXEC, which the branch body may restore.
if (ctx.other_half != nullptr && ctx.half != 0) return ctx.other_half->Def(IR::Value(&inst));
if (ctx.other_half != nullptr && ctx.half != 0) return ctx.other_half->Result(inst);
const auto kind = inst.Flags<CFG::BranchCondition>();
if (kind == CFG::BranchCondition::ScalarInstruction) return ctx.Arg(inst, 0);
const bool zero = kind == CFG::BranchCondition::ExecZero ||
kind == CFG::BranchCondition::VccZero || kind == CFG::BranchCondition::SccZero;
const auto ballot = ctx.Ballot(inst.Arg(0));
const auto reference = zero ? ctx.Ballot(IR::Value(true))
: ConstantU32CompositeZero(ctx.state, 4);
const auto compare = Binary(ctx.state, zero ? spv::OpIEqual : spv::OpINotEqual,
TypeBoolVector(ctx.state, 4), ballot, reference);
return Unary(ctx.state, zero ? spv::OpAll : spv::OpAny, TypeBool(ctx.state), compare);
// Zero-kind predicates are already inverted by translation.
auto predicate = ctx.Arg(inst, 0);
if (ctx.other_half != nullptr) {
predicate = Binary(ctx.state, zero ? spv::OpLogicalAnd : spv::OpLogicalOr,
TypeBool(ctx.state), predicate, ctx.HalfArg(inst, 0, 1));
}
const auto result = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(zero ? spv::OpGroupNonUniformAll : spv::OpGroupNonUniformAny,
TypeBool(ctx.state), result,
ConstantU32(ctx.state, spv::ScopeSubgroup), predicate);
return result;
}
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate) {
@@ -748,6 +769,18 @@ uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst) {
return ctx.Shuffle(inst, 0, ctx.Arg(inst, 1));
}
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
auto value = ctx.Arg(inst, 0);
if (ctx.other_half != nullptr) {
value = EmitUMin32(state, ctx.HalfArg(inst, 0, 0), ctx.HalfArg(inst, 0, 1));
}
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpGroupNonUniformUMin, TypeU32(state), result,
ConstantU32(state, spv::ScopeSubgroup), spv::GroupOperationReduce, value);
return result;
}
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto hit = state.builder.AllocateId();
@@ -220,6 +220,10 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value,
Prospero::TextureNumericClass numeric_class, bool dref,
const IR::MemoryInfo& mem, bool gather = false) {
auto value_class = numeric_class;
const auto conversion = ctx.state.program.info.images[mem.resource].conversion_format;
if (conversion != Prospero::BufferFormat::kInvalid) {
value_class = Prospero::SampledTextureNumericClass(conversion);
}
if (dref) {
value_class = Prospero::TextureNumericClass::Float;
}
@@ -269,20 +273,13 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value,
result, packed[0], packed[1], packed[2], packed[3]);
return result;
}
uint32_t component[4] {};
for (uint32_t index = 0; index < 4u; index++) {
if (dref) {
component[index] = index == 0u ? FloatBits(ctx, value) : ConstantU32(ctx.state, 0);
continue;
}
const auto scalar = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
spv::OpCompositeExtract, ImageScalarType(ctx.state, value_class), scalar, value, index);
component[index] = SampledComponentBits(ctx, scalar, value_class);
}
if (!dref)
return value_class == Prospero::TextureNumericClass::Uint
? value : Unary(ctx.state, spv::OpBitcast, TypeU32Vector(ctx.state, 4), value);
const auto result = ctx.state.builder.AllocateId();
const auto zero = ConstantU32(ctx.state, 0);
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
component[0], component[1], component[2], component[3]);
FloatBits(ctx, value), zero, zero, zero);
return result;
}
@@ -340,6 +337,7 @@ uint32_t PackedOffset(ValueEmitContext& ctx, IR::Value value, ImageDimension dim
TypeI32Vector(ctx.state, components),
std::span(values).first(components));
}
ctx.state.builder.RequireCapability(spv::CapabilityImageGatherExtended);
mask |= spv::ImageOperandsOffsetMask;
const auto packed = Unary(ctx.state, spv::OpBitcast, TypeI32(ctx.state), ctx.Def(value));
for (uint32_t index = 0; index < components; index++) {
@@ -383,17 +381,29 @@ uint32_t InverseSwizzle(uint32_t swizzle, uint32_t component) {
Format::BufferFormatInfo ImageConversionFormat(const EmitterState& state,
const IR::MemoryInfo& mem) {
const auto format = state.program.info.images[mem.resource].conversion_format;
const auto& image = state.program.info.images[mem.resource];
const auto format = image.conversion_format;
if (format == Prospero::BufferFormat::kInvalid) return {};
const auto info = Format::GetFormatInfo(format);
EXIT_IF(Prospero::RemapTextureFormat(format) == format || info.component_count == 0u ||
const bool raw_signed = IR::RawSignedStorageFormat(image, format) != Prospero::BufferFormat::kInvalid;
EXIT_IF((Prospero::RemapTextureFormat(format) == format && !raw_signed) || info.component_count == 0u ||
info.component_count > 4u);
EXIT_IF(info.type != Format::ComponentType::Uscaled &&
(info.type != Format::ComponentType::Uint || !info.packed_bitfield ||
EXIT_IF(!raw_signed && info.type != Format::ComponentType::Uscaled &&
((info.type != Format::ComponentType::Uint &&
info.type != Format::ComponentType::Unorm) || !info.packed_bitfield ||
info.byte_size != sizeof(uint32_t)));
return info;
}
uint32_t ExtractPackedImageComponent(EmitterState& state, const Format::BufferFormatInfo& info,
uint32_t packed, uint32_t type, uint32_t component) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpBitFieldUExtract, type, result, packed,
ConstantU32(state, info.component_bit_offset[component]),
ConstantU32(state, info.component_bits[component]));
return result;
}
uint32_t ImageGatherSource(const EmitterState& state, const IR::MemoryInfo& mem) {
const auto component = ImageGatherComponent(mem.dmask);
const auto info = ImageConversionFormat(state, mem);
@@ -417,13 +427,13 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
}
uint32_t components[4] {};
for (uint32_t component = 0; component < info.component_count; component++) {
components[component] = ctx.state.builder.AllocateId();
if (info.packed_bitfield) {
ctx.state.builder.AddFunction(spv::OpBitFieldUExtract, scalar_type,
components[component], packed,
ConstantU32(ctx.state, info.component_bit_offset[component]),
ConstantU32(ctx.state, info.component_bits[component]));
components[component] =
ExtractPackedImageComponent(ctx.state, info, packed, scalar_type, component);
components[component] =
NormalizeFormatComponent(ctx.state, info, component, components[component]);
} else {
components[component] = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpCompositeExtract, scalar_type,
components[component], texel, component);
// UNorm backing preserves the guest's filtering; scaling precedes swizzle constants.
@@ -440,8 +450,9 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
for (uint32_t component = 0; component < 4u; component++) {
const auto selector = (swizzle >> (component * 3u)) & 7u;
if (selector == 1u) {
selected[component] = info.packed_bitfield ? ConstantU32(ctx.state, 1u)
: ConstantF32Value(ctx.state, 1.0f);
selected[component] = info.packed_bitfield
? ConstantU32(ctx.state, Format::FormattedConstantBits(info, Format::FormattedSourceKind::One))
: ConstantF32Value(ctx.state, 1.0f);
} else if (selector >= 4u) {
selected[component] = components[selector - 4u];
} else {
@@ -453,7 +464,8 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
ctx.state.builder.AddFunction(spv::OpCompositeConstruct,
ImageVectorType(ctx.state, numeric_class, 4), result, selected[0],
selected[1], selected[2], selected[3]);
return result;
return info.type == Format::ComponentType::Unorm
? Unary(ctx.state, spv::OpBitcast, TypeF32Vector(ctx.state, 4), result) : result;
}
uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t gathered) {
@@ -483,21 +495,74 @@ uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uin
return Binary(ctx.state, spv::OpFMul, vector_type, gathered, scales);
}
uint32_t values[4] {};
for (uint32_t lane = 0; lane < 4u; lane++) {
const auto packed = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), packed, gathered,
lane);
values[lane] = ctx.state.builder.AllocateId();
const auto physical = (selector - 4u) % info.component_count;
ctx.state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(ctx.state), values[lane],
packed,
ConstantU32(ctx.state, info.component_bit_offset[physical]),
ConstantU32(ctx.state, info.component_bits[physical]));
return ExtractPackedImageComponent(ctx.state, info, gathered, TypeU32Vector(ctx.state, 4),
(selector - 4u) % info.component_count);
}
uint32_t SamplePackedUnormLz(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t coord) {
auto& state = ctx.state;
const auto& image = state.program.info.images[mem.resource];
const auto info = ImageConversionFormat(state, mem);
const auto sampled = MakeSampledImage(state, mem.resource,
LoadSamplerDescriptor(state, mem.sampler));
const auto gathered = state.builder.AllocateId();
state.builder.AddFunction(spv::OpImageGather, TypeU32Vector(state, 4), gathered, sampled,
coord, ConstantU32(state, 0u));
state.builder.RequireCapability(spv::CapabilityImageQuery);
const auto size = state.builder.AllocateId();
state.builder.AddFunction(spv::OpImageQuerySizeLod, TypeU32Vector(state, 2), size,
LoadImageDescriptor(state, mem.resource), ConstantU32(state, 0u));
const auto size_f32 = Unary(state, spv::OpConvertUToF, TypeF32Vector(state, 2), size);
const auto half = ConstantF32Value(state, 0.5f);
const auto halves = state.builder.Constant(spv::OpConstantComposite,
TypeF32Vector(state, 2), half, half);
const auto fractions = state.builder.AllocateId();
state.builder.AddFunction(
spv::OpExtInst, TypeF32Vector(state, 2), fractions, GlslStd450(state), GLSLstd450Fract,
Binary(state, spv::OpFSub, TypeF32Vector(state, 2),
Binary(state, spv::OpFMul, TypeF32Vector(state, 2), coord, size_f32), halves));
uint32_t fraction[2];
uint32_t complement[2];
for (uint32_t axis = 0; axis < 2u; axis++) {
fraction[axis] = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), fraction[axis],
fractions, axis);
complement[axis] = Binary(state, spv::OpFSub, TypeF32(state),
ConstantF32Value(state, 1.0f), fraction[axis]);
}
const auto result = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
values[0], values[1], values[2], values[3]);
// Gather order is (x0, y1), (x1, y1), (x1, y0), (x0, y0).
const auto weights = state.builder.AllocateId();
state.builder.AddFunction(
spv::OpCompositeConstruct, TypeF32Vector(state, 4), weights,
Binary(state, spv::OpFMul, TypeF32(state), complement[0], fraction[1]),
Binary(state, spv::OpFMul, TypeF32(state), fraction[0], fraction[1]),
Binary(state, spv::OpFMul, TypeF32(state), fraction[0], complement[1]),
Binary(state, spv::OpFMul, TypeF32(state), complement[0], complement[1]));
uint32_t decoded[4] {};
uint32_t selected[4] {ZeroF32(state), ZeroF32(state), ZeroF32(state), ZeroF32(state)};
for (uint32_t component = 0; component < 4u; component++) {
if ((mem.dmask & (1u << component)) == 0u) continue;
const auto selector = (image.shader_swizzle >> (component * 3u)) & 7u;
if (selector == 1u) {
selected[component] = ConstantF32Value(state, 1.0f);
} else if (selector >= 4u) {
const auto physical = (selector - 4u) % info.component_count;
if (decoded[physical] == 0u) {
const auto texels = Unary(
state, spv::OpConvertUToF, TypeF32Vector(state, 4),
ExtractPackedImageComponent(state, info, gathered,
TypeU32Vector(state, 4), physical));
decoded[physical] = Binary(
state, spv::OpFMul, TypeF32(state),
Binary(state, spv::OpDot, TypeF32(state), texels, weights),
ConstantF32Value(state, 1.0f / ((1u << info.component_bits[physical]) - 1u)));
}
selected[component] = decoded[physical];
}
}
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4), result,
selected[0], selected[1], selected[2], selected[3]);
return result;
}
@@ -543,47 +608,23 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
return result;
}
uint32_t PackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t texel) {
const auto info = ImageConversionFormat(ctx.state, mem);
if (info.format == Prospero::BufferFormat::kInvalid) return texel;
EXIT_IF(info.type != Format::ComponentType::Uint || !info.packed_bitfield);
auto packed = ConstantU32(ctx.state, 0u);
for (uint32_t component = 0; component < info.component_count; component++) {
const auto value = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), value, texel,
component);
const auto maximum =
ConstantU32(ctx.state, info.component_bits[component] == 32u
? UINT32_MAX
: (1u << info.component_bits[component]) - 1u);
const auto within =
Binary(ctx.state, spv::OpULessThan, TypeBool(ctx.state), value, maximum);
const auto clamped = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpSelect, TypeU32(ctx.state), clamped, within, value,
maximum);
const auto shifted =
info.component_bit_offset[component] == 0u
? clamped
: Binary(ctx.state, spv::OpShiftLeftLogical, TypeU32(ctx.state), clamped,
ConstantU32(ctx.state, info.component_bit_offset[component]));
packed = Binary(ctx.state, spv::OpBitwiseOr, TypeU32(ctx.state), packed, shifted);
}
const auto zero = ConstantU32(ctx.state, 0u);
const auto result = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
packed, zero, zero, zero);
return result;
}
uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t data, bool integer) {
const auto swizzle = ctx.state.program.info.images[mem.resource].shader_swizzle;
uint32_t values[4] {};
const auto info = ImageConversionFormat(ctx.state, mem);
const bool converted = info.format != Prospero::BufferFormat::kInvalid;
const bool packed = info.packed_bitfield;
EXIT_IF(converted && info.type != Format::ComponentType::Sint &&
(!packed || (info.type != Format::ComponentType::Uint &&
info.type != Format::ComponentType::Unorm)));
const auto zero = ConstantU32(ctx.state, 0);
uint32_t values[4] {zero, zero, zero, zero};
auto packed_value = zero;
const auto dmask = mem.dmask != 0u ? mem.dmask : 1u;
for (uint32_t component = 0; component < 4u; component++) {
for (uint32_t component = 0; component < (converted ? info.component_count : 4u); component++) {
const auto source = InverseSwizzle(swizzle, component);
uint32_t raw = ConstantU32(ctx.state, 0);
if (source < 4u && ((dmask >> source) & 1u) != 0u) {
const bool selected = source < 4u && ((dmask >> source) & 1u) != 0u;
uint32_t raw = zero;
if (selected) {
const auto packed_index = DmaskComponentIndex(dmask, source);
raw = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), raw, data,
@@ -593,26 +634,42 @@ uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t d
raw = Binary(ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), raw,
ConstantU32(ctx.state, 16u));
}
raw = Binary(ctx.state, spv::OpBitwiseAnd, TypeU32(ctx.state), raw,
ConstantU32(ctx.state, 0xffffu));
if (!packed) {
raw = Binary(ctx.state, spv::OpBitwiseAnd, TypeU32(ctx.state), raw,
ConstantU32(ctx.state, 0xffffu));
}
}
}
if (converted) {
if (!selected) continue;
if (!packed) {
values[component] =
EncodeFormattedStoreComponent(ctx.state, info, component, raw, mem.data_bits);
continue;
}
const auto value =
EncodePackedColorComponent(ctx.state, info, component, raw, mem.data_bits);
packed_value = packed_value == zero ? value
: Binary(ctx.state, spv::OpBitwiseOr, TypeU32(ctx.state), packed_value, value);
continue;
}
values[component] = integer ? raw
: mem.data_bits == 16u
? EmitF16BitsToF32(ctx.state, raw)
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), raw);
}
if (packed) values[0] = packed_value;
const auto texel = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpCompositeConstruct,
integer ? TypeU32Vector(ctx.state, 4)
: TypeF32Vector(ctx.state, 4),
texel, values[0], values[1], values[2], values[3]);
return PackImageTexel(ctx, mem, texel);
return texel;
}
template <typename Emit>
uint32_t EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t result_type,
const Emit& emit) {
auto EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t result_type,
const Emit& emit) {
auto& state = ctx.state;
const auto& mem = ctx.Memory(inst);
const auto& image = state.program.info.images.at(mem.resource);
@@ -649,6 +706,8 @@ uint32_t EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t r
const auto next_slot = run.slot_bias + ordinal;
if (candidate.dimension == first.dimension && candidate.cube == first.cube &&
IR::DescriptorBindingForImage(first) == kind &&
(inst.GetOpcode() != IR::ValueOpcode::ImageWrite ||
candidate.shader_swizzle == first.shader_swizzle) &&
candidate.mip_count == 1u && first.mip_count == 1u &&
(ordinal == 1u || candidate.descriptor_index == next_slot)) {
// Native roots precede appended children; only the root may have a slot gap.
@@ -694,6 +753,92 @@ uint32_t EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t r
result_type, EmitRun);
}
template <typename Emit>
uint32_t EmitImageResult(ValueEmitContext& ctx, const IR::Inst& inst, const Emit& emit) {
auto& state = ctx.state;
const auto& mem = ctx.Memory(inst);
const auto& image = state.program.info.images[mem.resource];
const bool gather = inst.GetOpcode() == IR::ValueOpcode::ImageGatherRaw;
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
const bool scalar = dref && !gather;
const bool mixed = std::ranges::any_of(image.indirect_resources, [&](uint32_t resource) {
return state.program.info.images[resource].numeric_class != image.numeric_class;
});
const auto native_type = [&](uint32_t resource) {
return scalar ? TypeF32(state) : ImageVectorType(
state, dref ? Prospero::TextureNumericClass::Float
: state.program.info.images[resource].numeric_class, 4);
};
const auto result_bits = [&](uint32_t resource, uint32_t value) {
auto candidate_mem = mem;
candidate_mem.resource = resource;
if (!scalar) {
value = gather ? UnpackImageGather(ctx, candidate_mem, value)
: UnpackImageTexel(ctx, candidate_mem, value);
}
return ResultVector(ctx, value,
dref ? Prospero::TextureNumericClass::Float
: state.program.info.images[resource].numeric_class,
scalar, candidate_mem, gather);
};
// RDNA returns register bits. Convert per typed run only when the join mixes
// numeric classes; homogeneous accesses keep their single conversion after it.
const auto value = EmitImageAccess(
ctx, inst, mixed ? TypeU32Vector(state, 4) : native_type(mem.resource),
[&](uint32_t resource, uint32_t array_index) {
const auto native = emit(resource, array_index, native_type(resource));
return mixed ? result_bits(resource, native) : native;
});
return mixed ? value : result_bits(mem.resource, value);
}
struct ImageSampler {
uint32_t id = 0;
bool dynamic = false;
};
std::array<ImageSampler, 2> LoadImageSamplers(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto& mem = ctx.Memory(inst);
const auto& samplers = state.program.info.samplers;
const auto& root = samplers.at(mem.sampler);
std::array<ImageSampler, 2> result {};
const auto load = [&](const IR::ImageResource& image) {
const bool integer = image.numeric_class != Prospero::TextureNumericClass::Float;
auto& sampler = result[integer];
if (sampler.id != 0u) return;
uint32_t index = mem.sampler;
if (root.integer_border != integer) {
const auto found = std::ranges::find_if(samplers, [&](const auto& candidate) {
return candidate.snapshot_index == root.snapshot_index &&
candidate.integer_border == integer;
});
if (found == samplers.end()) ctx.Fail(inst, "has no typed sampler variant");
index = static_cast<uint32_t>(found - samplers.begin());
}
uint32_t array_index = 0;
const auto& candidates = samplers[index].indirect_resources;
if (!candidates.empty()) {
const auto* handle = inst.Arg(1).ResolveInstruction();
if (handle == nullptr || handle->GetOpcode() != IR::ValueOpcode::GetSamplerResource)
ctx.Fail(inst, "has invalid finite sampler key provenance");
auto selected = ctx.Def(handle->Arg(0));
if (samplers[index].indirect_root != IR::SamplerResource::NoIndirectSampler)
selected = EmitIndirectResourceIndex(state, selected,
samplers[index].indirect_mapping_offset,
samplers[index].indirect_search_iterations, 0u);
array_index = EmitIndexSwitch(
state, selected, static_cast<uint32_t>(candidates.size()),
TypeU32(state), [&](uint32_t ordinal) { return ConstantU32(state, candidates[ordinal]); });
}
sampler = {LoadSamplerDescriptor(state, index, array_index), array_index != 0u};
};
const auto& image = state.program.info.images[mem.resource];
load(image);
for (const auto candidate: image.indirect_resources) load(state.program.info.images[candidate]);
return result;
}
} // namespace
void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
@@ -710,6 +855,22 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
ctx.Define(inst, QueryDimensions(ctx, mem, *address));
return;
}
if (image_info.needs_sampler &&
image.conversion_format == Prospero::BufferFormat::k11_11_10UNorm) {
if (op != IR::ValueOpcode::ImageSampleRaw ||
image.dimension != ImageDimension::Dim2D ||
mem.image_dimension != ImageDimension::Dim2D || mem.data_bits != 32u ||
mem.image_sample_flags != Decoder::ImageSampleFlagLevelZero ||
image.indirect_root != IR::ImageResource::NoIndirectImage ||
!state.program.info.samplers.at(mem.sampler).indirect_resources.empty()) {
ctx.Fail(inst, "packed UNorm sampling requires a direct 2D sample_lz");
return;
}
const auto coord = CoordF32(ctx, mem, *address, Layout(mem).coord, 2u);
ctx.Define(inst, ResultVector(ctx, SamplePackedUnormLz(ctx, mem, coord),
Prospero::TextureNumericClass::Float, false, mem));
return;
}
if (op == IR::ValueOpcode::ImageQueryLod) {
state.builder.RequireCapability(spv::CapabilityImageQuery);
const auto dimension = image.dimension;
@@ -735,16 +896,14 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
return;
}
if (op == IR::ValueOpcode::ImageRead) {
const auto numeric_class = image.numeric_class;
const auto condition = ctx.Arg(inst, 2);
ctx.Define(
inst,
EmitValueOrDefaultIfCondition(
state, condition, TypeU32Vector(state, 4), ConstantU32CompositeZero(state, 4),
[&]() {
const auto color = EmitImageAccess(
ctx, inst, ImageVectorType(state, numeric_class, 4),
[&](uint32_t resource, uint32_t array_index) {
return EmitImageResult(
ctx, inst, [&](uint32_t resource, uint32_t array_index, uint32_t result_type) {
const auto& candidate = state.program.info.images[resource];
const auto dimension = candidate.dimension;
const auto& dimension_info = ImageDimensionInfoFor(dimension);
@@ -753,33 +912,33 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto coord = CoordU32(ctx, mem, *address, dimension);
if (dimension_info.multisampled != 0u) {
state.builder.AddFunction(
spv::OpImageFetch, ImageVectorType(state, candidate.numeric_class, 4), fetched,
spv::OpImageFetch, result_type, fetched,
descriptor, coord, spv::ImageOperandsSampleMask,
AddressU32(ctx, mem, *address, dimension_info.coordinate_components));
} else {
state.builder.AddFunction(
spv::OpImageFetch, ImageVectorType(state, candidate.numeric_class, 4), fetched,
spv::OpImageFetch, result_type, fetched,
descriptor, coord, spv::ImageOperandsLodMask,
LodU32(ctx, mem, *address, mem.image_dimension));
}
return fetched;
});
return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), numeric_class,
false, mem);
}));
return;
}
if (op == IR::ValueOpcode::ImageWrite) {
const bool uint_image = image.numeric_class == Prospero::TextureNumericClass::Uint;
const auto dimension = image.dimension;
EmitIfCondition(state, ctx.Arg(inst, 3), [&]() {
const auto mip_lod =
state.program.info.images[mem.resource].mip_mode == IR::ImageMipMode::Dynamic
? LodU32(ctx, mem, *address, dimension)
: 0u;
const auto coord = CoordU32(ctx, mem, *address, dimension);
const auto texel = StoreTexel(ctx, mem, ctx.Arg(inst, 2), uint_image);
EmitStorageImageWrite(state, mem.resource, mip_lod, coord, texel);
EmitImageAccess(ctx, inst, 0u, [&](uint32_t resource, uint32_t array_index) {
const auto& candidate = state.program.info.images[resource];
auto selected = mem;
selected.resource = resource;
const auto mip_lod = candidate.mip_mode == IR::ImageMipMode::Dynamic
? LodU32(ctx, mem, *address, mem.image_dimension) : 0u;
const auto coord = CoordU32(ctx, mem, *address, candidate.dimension);
const auto texel = StoreTexel(ctx, selected, ctx.Arg(inst, 2),
candidate.numeric_class == Prospero::TextureNumericClass::Uint);
EmitStorageImageWrite(state, resource, mip_lod, coord, texel, array_index);
});
});
return;
}
@@ -820,9 +979,6 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
ctx.Fail(inst, "has an unsupported 1D-array gather");
return;
}
const auto result_numeric_class = dref ? Prospero::TextureNumericClass::Float
: numeric_class;
const auto result_type = ImageVectorType(state, result_numeric_class, 4);
uint32_t component_or_dref;
if (dref) {
component_or_dref = layout.dref != NoImageComponent
@@ -831,9 +987,10 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
} else {
component_or_dref = ConstantU32(state, ImageGatherSource(state, mem));
}
const auto sampler_id = LoadSamplerDescriptor(state, mem.sampler);
const auto EmitGather = [&](uint32_t resource, uint32_t array_index) {
const auto samplers = LoadImageSamplers(ctx, inst);
const auto EmitGather = [&](uint32_t resource, uint32_t array_index, uint32_t result_type) {
const auto& candidate = state.program.info.images[resource];
const auto sampler = samplers[candidate.numeric_class != Prospero::TextureNumericClass::Float];
const auto dimension = candidate.dimension;
const auto coord = CoordF32(
ctx, mem, *address, layout.coord,
@@ -847,7 +1004,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
offset = PackedOffset(ctx, offset_value, dimension, operand_mask);
}
const auto EmitMip = [&](uint32_t mip) {
const auto sampled = MakeSampledImage(state, resource, sampler_id, mip, array_index);
const auto sampled = MakeSampledImage(state, resource, sampler.id, mip, array_index,
sampler.dynamic);
const auto sample = state.builder.AllocateId();
const std::array operands {operand_mask, offset};
state.builder.AddFunction(
@@ -862,9 +1020,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
candidate.mip_count, result_type, EmitMip)
: EmitMip(0u);
};
const auto sample = EmitImageAccess(ctx, inst, result_type, EmitGather);
ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample),
result_numeric_class, false, mem, true));
ctx.Define(inst, EmitImageResult(ctx, inst, EmitGather));
return;
}
const bool explicit_lod = HasFlag(mem, Decoder::ImageSampleFlagDerivative) ||
@@ -877,10 +1033,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
} else if (dref) {
opcode = spv::OpImageSampleDrefImplicitLod;
}
uint32_t result_type = ImageVectorType(state, numeric_class, 4);
uint32_t dref_value = 0;
if (dref) {
result_type = TypeF32(state);
dref_value = ZeroF32(state);
if (layout.dref != NoImageComponent) {
dref_value = AddressF32(ctx, mem, *address, layout.dref);
@@ -906,23 +1060,14 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
operands[1] = AddressF32(ctx, mem, *address, layout.bias);
operand_count = 2;
}
const auto& samplers = state.program.info.samplers.at(mem.sampler).indirect_resources;
uint32_t sampler_index = 0;
if (!samplers.empty()) {
const auto* handle = inst.Arg(1).ResolveInstruction();
if (handle == nullptr || handle->GetOpcode() != IR::ValueOpcode::GetSamplerResource)
ctx.Fail(inst, "has invalid finite sampler key provenance");
sampler_index = EmitIndexSwitch(
state, ctx.Def(handle->Arg(0)), static_cast<uint32_t>(samplers.size()),
TypeU32(state), [&](uint32_t ordinal) { return ConstantU32(state, samplers[ordinal]); });
}
if (layout.offset != NoImageComponent &&
!offset_value.IsImmediate()) {
ctx.Fail(inst, "has a nonconstant sample texel offset");
!offset_value.IsImmediate() && !state.maintenance8_enabled) {
ctx.Fail(inst, "requires maintenance8 for a dynamic sample texel offset");
}
const auto sampler_id = LoadSamplerDescriptor(state, mem.sampler, sampler_index);
const auto EmitSample = [&](uint32_t resource, uint32_t array_index) {
const auto samplers = LoadImageSamplers(ctx, inst);
const auto EmitSample = [&](uint32_t resource, uint32_t array_index, uint32_t result_type) {
const auto& candidate = state.program.info.images[resource];
const auto sampler = samplers[candidate.numeric_class != Prospero::TextureNumericClass::Float];
auto sample_operands = operands;
auto sample_operand_count = operand_count;
if (layout.offset != NoImageComponent) {
@@ -934,19 +1079,15 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
CoordF32(ctx, mem, *address, layout.coord,
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
candidate.cube);
const auto sampled = MakeSampledImage(state, resource, sampler_id, 0u, array_index,
sampler_index != 0u);
const auto sampled = MakeSampledImage(state, resource, sampler.id, 0u, array_index,
sampler.dynamic);
const auto sample = state.builder.AllocateId();
state.builder.AddFunction(opcode, result_type, sample, sampled, coord,
std::span(&dref_value, dref ? 1u : 0u),
std::span<const uint32_t>(sample_operands).first(sample_operand_count));
return sample;
};
auto result = EmitImageAccess(ctx, inst, result_type, EmitSample);
if (!dref) {
result = UnpackImageTexel(ctx, mem, result);
}
ctx.Define(inst, ResultVector(ctx, result, numeric_class, dref, mem));
ctx.Define(inst, EmitImageResult(ctx, inst, EmitSample));
return;
}
if (image_info.access == IR::ImageAccess::Atomic) {
@@ -5,15 +5,17 @@
#include <bit>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
uint32_t EmitIdentity(ValueEmitContext& ctx, uint32_t value);
// Operand signatures distinguish SPIR-V IDs (uint32_t), raw literals/values
// (IR::Value), and instructions whose metadata or operand loads must stay lazy.
#define EMIT_NATIVE(name, opcode, type, ...) \
inline constexpr auto Emit##name = EmitNative<spv::opcode, IR::Type::type, __VA_ARGS__>;
EMIT_NATIVE(BitCastU16F16, OpBitcast, U32, uint32_t)
inline constexpr auto EmitBitCastU16F16 = EmitIdentity;
inline constexpr auto EmitBitCastF16U16 = EmitBitCastU16F16;
inline constexpr auto EmitConvertU32U16 = EmitBitCastU16F16;
inline constexpr auto EmitConvertU32U8 = EmitBitCastU16F16;
inline constexpr auto EmitBitCastU32F32 = EmitBitCastU16F16;
EMIT_NATIVE(BitCastU32F32, OpBitcast, U32, uint32_t)
EMIT_NATIVE(BitCastF32U32, OpBitcast, F32, uint32_t)
EMIT_NATIVE(BitCastU64F64, OpBitcast, U64, uint32_t)
EMIT_NATIVE(BitCastF64U64, OpBitcast, F64, uint32_t)
@@ -207,7 +209,6 @@ uint32_t EmitFPSin(EmitterState& state, uint32_t arg0);
uint32_t EmitFPCos(EmitterState& state, uint32_t arg0);
#undef EMIT_NATIVE
uint32_t EmitIdentity(ValueEmitContext& ctx, uint32_t value);
void EmitVoid(ValueEmitContext&);
inline constexpr auto EmitReference = EmitVoid;
inline constexpr auto EmitReferenceU32 = EmitVoid;
@@ -237,6 +238,7 @@ uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
@@ -245,6 +247,7 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& in
uint32_t EmitGetShaderBase(ValueEmitContext& ctx);
inline constexpr auto EmitGetSrtResource = EmitVoid;
inline constexpr auto EmitGetBufferResource = EmitGetSrtResource;
uint32_t EmitGetBufferResourceIndex(ValueEmitContext& ctx, const IR::Inst& inst);
inline constexpr auto EmitGetAddressResource = EmitGetSrtResource;
inline constexpr auto EmitGetScratchResource = EmitGetSrtResource;
inline constexpr auto EmitGetImageResource = EmitGetSrtResource;
@@ -321,6 +324,8 @@ inline constexpr auto EmitSharedAtomicDec32 = EmitSharedIncDec;
inline constexpr auto EmitSharedAtomicSMin32 = EmitAtomic32;
inline constexpr auto EmitSharedAtomicUMin32 = EmitAtomic32;
inline constexpr auto EmitSharedAtomicSMax32 = EmitAtomic32;
inline constexpr auto EmitSharedAtomicUMax64 = EmitSharedAtomic64;
inline constexpr auto EmitSharedAtomicCmpSwap64 = EmitSharedAtomic64;
inline constexpr auto EmitSharedAtomicUMax32 = EmitAtomic32;
inline constexpr auto EmitSharedAtomicAnd32 = EmitAtomic32;
inline constexpr auto EmitSharedAtomicOr32 = EmitAtomic32;
@@ -73,9 +73,9 @@ struct ImageDefinition {
};
struct EmitterState {
EmitterState(IR::Program& program_, ShaderStageInputInfo input_info_)
EmitterState(IR::Program& program_, ShaderStageInputInfo input_info_, bool maintenance8_enabled_)
: builder(program_.stage == ShaderType::Mesh ? 0x00010400u : 0x00010300u),
program(program_), input_info(input_info_) {
program(program_), input_info(input_info_), maintenance8_enabled(maintenance8_enabled_) {
if (ShaderWorkgroupInput(program.stage, input_info) != nullptr) {
lds_storage_class = spv::StorageClassWorkgroup;
}
@@ -88,6 +88,8 @@ struct EmitterState {
Builder builder;
IR::Program& program;
ShaderStageInputInfo input_info;
bool maintenance8_enabled;
uint32_t debug_source = 0;
uint32_t void_type = 0;
uint32_t bool_type = 0;
uint32_t u32_type = 0;
@@ -109,10 +111,13 @@ struct EmitterState {
uint32_t lane_count = 1;
uint32_t lane_half = 0;
std::array<BufferDefinition, 4> storage_buffers {};
std::array<uint32_t, IR::ShaderInfo::MaxBuffers> memory_byte_offsets {};
std::vector<uint32_t> memory_byte_offsets;
uint32_t bda_pagetable_variable = 0;
uint32_t fault_buffer_variable = 0;
uint32_t bda_pointer_function = 0;
uint32_t indirect_format_function = 0;
uint32_t indirect_format_geometry_function = 0;
std::map<std::array<uint32_t, 3>, uint32_t> indirect_buffer_load_functions;
uint32_t bvh_intersect_function = 0;
uint32_t gds_variable = 0;
uint32_t gds_length = 0;
@@ -147,6 +152,7 @@ struct EmitterState {
uint32_t layer_variable = 0;
uint32_t viewport_index_variable = 0;
uint32_t depth_variable = 0;
uint32_t object_primitive_id_variable = 0;
uint32_t sample_mask_variable = 0;
std::vector<InputBinding> inputs;
std::vector<OutputBinding> outputs;
@@ -179,6 +185,11 @@ uint32_t TypeU32ElementPointer(EmitterState& state, spv::StorageClass storage_cl
inline void EmitLabel(EmitterState& state, uint32_t label) {
state.current_label = label;
state.builder.AddFunction(spv::OpLabel, label);
if (state.debug_source != 0 && state.current_block != nullptr) {
// Source lines encode the containing native block's byte offset plus one.
state.builder.AddFunction(spv::OpLine, state.debug_source,
state.current_block->start_pc + 1u, 0u);
}
}
uint32_t TypeId(EmitterState& state, IR::Type type);
@@ -329,7 +340,7 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
bool sampler_dynamic = false);
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
uint32_t texel);
uint32_t texel, uint32_t array_index);
spv::ExecutionModel ExecutionModelForStage(ShaderType stage);
@@ -393,11 +404,14 @@ inline constexpr auto EmitAddU32 = EmitNative<spv::OpIAdd, IR::Type::U32, uint32
uint32_t EmitBinaryU32(EmitterState& state, spv::Op opcode, uint32_t lhs, uint32_t rhs);
uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index);
uint32_t EmitPushDataLoad(EmitterState& state, uint32_t index);
uint32_t StorageBufferPackedStride(const EmitterState& state, const IR::MemoryInfo& mem);
Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::MemoryInfo& mem);
uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resource);
void EmitMemoryOffsets(EmitterState& state);
uint32_t LdsDwordCount(const EmitterState& state);
@@ -409,16 +423,19 @@ struct MemoryResourceAccess {
uint32_t length = 0;
uint32_t byte_offset = 0;
uint32_t element_bits = 32;
bool non_uniform = false;
spv::MemoryAccessMask memory_access = spv::MemoryAccessMaskNone;
};
uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t mapping_offset,
uint32_t search_iterations, uint32_t default_resource);
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem);
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem,
uint32_t array_index = 0u);
MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
const IR::MemoryInfo& mem,
BufferDefinition& buffer);
BufferDefinition& buffer,
uint32_t array_index = 0);
uint32_t EmitMemoryElementInBounds(EmitterState& state, const MemoryResourceAccess& access,
uint32_t index);
@@ -441,6 +458,16 @@ uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits);
uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferFormatInfo& info,
uint32_t component, uint32_t raw);
enum class UnormRounding { RoundEven, HalfUp, Truncate };
uint32_t EncodeFormattedStoreComponent(EmitterState& state, const Format::BufferFormatInfo& info,
uint32_t component, uint32_t data, uint32_t data_bits,
UnormRounding rounding = UnormRounding::RoundEven);
uint32_t EncodePackedColorComponent(EmitterState& state, const Format::BufferFormatInfo& info,
uint32_t component, uint32_t data, uint32_t data_bits,
UnormRounding rounding = UnormRounding::RoundEven);
spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode);
uint32_t EmitAtomicOperation(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t pointer,
@@ -461,17 +488,11 @@ inline constexpr auto EmitSelectValueU32 =
uint32_t EmitAndConstant(EmitterState& state, uint32_t value, uint32_t mask);
uint32_t EmitShiftRightConstant(EmitterState& state, uint32_t value, uint32_t shift);
inline constexpr auto EmitOrU32 = EmitNative<spv::OpBitwiseOr, IR::Type::U32, uint32_t, uint32_t>;
uint32_t EmitCompareU32Constant(EmitterState& state, spv::Op opcode, uint32_t value,
uint32_t constant);
uint32_t EmitSubConstantMinusU32(EmitterState& state, uint32_t constant, uint32_t value);
uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32);
inline constexpr auto EmitBitcastF32ToU32 = EmitNative<spv::OpBitcast, IR::Type::U32, uint32_t>;
inline constexpr auto EmitBitcastU32ToF32 = EmitNative<spv::OpBitcast, IR::Type::F32, uint32_t>;
@@ -513,6 +534,8 @@ uint32_t EmitF16BitsToF32(EmitterState& state, uint32_t bits);
void EmitProgram(EmitterState& state);
void DefineGetBdaPointer(EmitterState& state);
void DefineIndirectBufferFormats(ValueEmitContext& ctx);
void DefineIndirectBufferLoads(ValueEmitContext& ctx);
void DefineBvhIntersect(EmitterState& state);
uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
@@ -249,10 +249,11 @@ uint32_t LoadBda(ValueEmitContext& ctx, uint32_t address, uint32_t active, uint3
});
}
uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
uint32_t prefix = 0) {
if (mem.kind == IR::ResourceKind::Buffer) {
const auto resource = ctx.state.program.info.buffers.at(mem.resource).descriptor_index;
const auto prefix = ctx.state.memory_byte_offsets[resource];
if (prefix == 0u) prefix = ctx.state.memory_byte_offsets[resource];
const auto address = Binary(ctx.state, spv::OpIAdd, TypeU32(ctx.state),
BufferByteAddress(ctx, inst, mem), prefix);
// The host binding prefix must not wrap an out-of-range guest address into the buffer.
@@ -273,15 +274,10 @@ uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memo
return ScratchByteAddress(ctx, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2));
}
uint32_t DwordIndex(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
auto address = ByteAddress(ctx, inst, mem);
if (mem.kind == IR::ResourceKind::Lds || mem.kind == IR::ResourceKind::Gds) {
// RDNA2 DS region addresses retain bits [15:2] after adding the byte offset.
address = Binary(ctx.state, spv::OpBitwiseAnd, TypeU32(ctx.state), address,
ConstantU32(ctx.state, 0xffffu));
}
uint32_t DwordIndex(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
uint32_t prefix = 0) {
return Binary(ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state),
address, ConstantU32(ctx.state, 2));
ByteAddress(ctx, inst, mem, prefix), ConstantU32(ctx.state, 2));
}
uint32_t LoadWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& resource,
@@ -290,9 +286,99 @@ uint32_t LoadWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& res
uint32_t LoadSubwordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& resource,
uint32_t address, uint32_t index, uint32_t bits, bool sign_extend);
template <typename Fn>
auto EmitBufferAccess(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem,
uint32_t components, Fn&& emit) {
auto& state = ctx.state;
constexpr bool has_result = !std::is_void_v<
std::invoke_result_t<Fn, const IR::MemoryInfo&, const MemoryResourceAccess&>>;
const auto access = [&](uint32_t resource) {
mem.resource = resource;
if (mem.kind == IR::ResourceKind::Buffer) {
const auto& buffer = state.program.info.buffers[resource];
if (buffer.packed_stride == 0u &&
buffer.source < state.program.descriptor_sources.size() &&
state.program.descriptor_sources[buffer.source].indirect_descriptor) {
// Empty candidates still carry the format/swizzle needed for OOB loads.
return emit(mem, MemoryResourceAccess{});
}
}
return emit(mem, PrepareMemoryResourceAccess(state, mem));
};
if (mem.kind != IR::ResourceKind::Buffer ||
state.program.info.buffers[mem.resource].indirect_root != mem.resource) {
return access(mem.resource);
}
const auto root_resource = mem.resource;
const auto& buffer = state.program.info.buffers[root_resource];
const auto& handle = *inst.Arg(0).ResolveInstruction();
const auto selected = ctx.Arg(handle, 0);
const auto representative = SharedIndirectBufferResource(state, root_resource);
const auto shared = representative != UINT32_MAX;
const auto empty_root = shared && buffer.packed_stride == 0u;
const auto selected_access = [&]() {
if (shared) {
const auto root_slot = buffer.descriptor_index;
const auto child_slot = state.program.info.buffers[buffer.indirect_resources[1]].descriptor_index;
auto slot = EmitAddU32(state, selected, ConstantU32(state, child_slot - 1u));
if (!empty_root && child_slot != root_slot + 1u) {
slot = Select(state, TypeU32(state),
Binary(state, spv::OpIEqual, TypeBool(state), selected, ConstantU32(state, 0u)),
ConstantU32(state, root_slot), slot);
}
mem.resource = representative;
return emit(mem, PrepareMemoryResourceAccess(state, mem, slot));
}
return EmitIndexSwitch(state, selected, buffer.indirect_resources.size(),
has_result ? TypeU32Composite(state, components) : 0u,
[&](uint32_t item) { return access(buffer.indirect_resources[item]); });
};
if constexpr (!has_result) {
if (!shared) return selected_access();
}
auto valid = Binary(state, spv::OpULessThan, TypeBool(state), selected,
ConstantU32(state, buffer.indirect_resources.size()));
if constexpr (has_result) {
return EmitValueOrDefaultIfCondition(state, valid, TypeU32Composite(state, components),
ConstantU32CompositeZero(state, components), [&]() {
if (!empty_root) return selected_access();
return EmitValueOrDefaultIfCondition(state,
Binary(state, spv::OpINotEqual, TypeBool(state), selected, ConstantU32(state, 0u)),
TypeU32Composite(state, components), access(root_resource), selected_access);
});
} else {
if (empty_root) valid = AndCondition(state, valid,
Binary(state, spv::OpINotEqual, TypeBool(state), selected, ConstantU32(state, 0u)));
EmitIfCondition(state, valid, selected_access);
}
}
uint32_t BufferIndexInBounds(ValueEmitContext& ctx, const IR::Inst& inst,
const IR::MemoryInfo& mem, const MemoryResourceAccess& resource,
uint32_t payload_in_bounds) {
if (mem.kind != IR::ResourceKind::Buffer) return payload_in_bounds;
auto& state = ctx.state;
const auto& buffer = state.program.info.buffers[mem.resource];
const auto& sources = state.program.descriptor_sources;
if (buffer.source >= sources.size() || !sources[buffer.source].indirect_descriptor ||
!sources[buffer.source].indirect_descriptor->indexed) return payload_in_bounds;
const auto bytes = Binary(state, spv::OpIMul, TypeU32(state), resource.length,
ConstantU32(state, resource.element_bits / 8u));
const auto available = Binary(state, spv::OpISub, TypeU32(state), bytes, resource.byte_offset);
// Test the original index before multiplication can wrap. A truncated binding
// may omit final-record padding; the payload check below still checks every byte.
const auto last_record = Binary(state, spv::OpUDiv, TypeU32(state),
Binary(state, spv::OpISub, TypeU32(state), available, ConstantU32(state, 1u)),
ConstantU32(state, buffer.packed_stride & 0x3fffu));
return AndCondition(state, payload_in_bounds,
AndCondition(state,
Binary(state, spv::OpUGreaterThan, TypeBool(state), bytes, resource.byte_offset),
Binary(state, spv::OpULessThanEqual, TypeBool(state), ctx.Arg(inst, 1), last_record)));
}
uint32_t LoadWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
const MemoryResourceAccess& resource) {
const auto index = DwordIndex(ctx, inst, mem);
const auto index = DwordIndex(ctx, inst, mem, resource.byte_offset);
return EmitValueOrZeroIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index),
[&]() { return LoadWordInBounds(ctx, resource, index); });
@@ -308,7 +394,7 @@ uint32_t LoadWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo me
uint32_t LoadSubwordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
const MemoryResourceAccess& resource, uint32_t bits,
bool sign_extend) {
const auto address = ByteAddress(ctx, inst, mem);
const auto address = ByteAddress(ctx, inst, mem, resource.byte_offset);
const auto index = Binary(
ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), address,
ConstantU32(ctx.state, std::countr_zero(resource.element_bits / 8u)));
@@ -470,20 +556,23 @@ void StoreSubwordInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
void StoreSubwordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
const MemoryResourceAccess& resource, uint32_t bits, uint32_t data) {
const auto address = ByteAddress(ctx, inst, mem);
if (resource.object_pointer == 0u) return;
const auto address = ByteAddress(ctx, inst, mem, resource.byte_offset);
const auto index = Binary(
ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), address,
ConstantU32(ctx.state, std::countr_zero(resource.element_bits / 8u)));
EmitIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
ctx.state, BufferIndexInBounds(ctx, inst, mem, resource,
EmitMemoryElementInBounds(ctx.state, resource, index)), [&]() {
StoreSubwordInBounds(ctx, mem, resource, address, index, bits, data);
});
}
void StoreSubword(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem, uint32_t bits) {
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto resource = PrepareMemoryResourceAccess(ctx.state, mem);
StoreSubwordPrepared(ctx, inst, mem, resource, bits, ctx.Arg(inst, inst.NumArgs() - 2));
EmitBufferAccess(ctx, inst, mem, 0u, [&](const auto& selected, const auto& resource) {
StoreSubwordPrepared(ctx, inst, selected, resource, bits, ctx.Arg(inst, inst.NumArgs() - 2));
});
});
}
@@ -540,58 +629,20 @@ void StoreLocalFlat(ValueEmitContext& ctx, const IR::Inst& inst) {
void StoreWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
const MemoryResourceAccess& resource, uint32_t data) {
const auto index = DwordIndex(ctx, inst, mem);
EmitIfCondition(ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
if (resource.object_pointer == 0u) return;
const auto index = DwordIndex(ctx, inst, mem, resource.byte_offset);
const auto in_bounds = BufferIndexInBounds(ctx, inst, mem, resource,
EmitMemoryElementInBounds(ctx.state, resource, index));
EmitIfCondition(ctx.state, in_bounds, [&]() {
StoreWordInBounds(ctx, resource, index, data);
});
}
void StoreWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem) {
auto& state = ctx.state;
EmitIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto data = ctx.Arg(inst, inst.NumArgs() - 2);
const auto store = [&](uint32_t resource) {
if (mem.kind == IR::ResourceKind::Buffer) {
const auto& buffer = state.program.info.buffers[resource];
if (buffer.packed_stride == 0u &&
buffer.source < state.program.descriptor_sources.size() &&
state.program.descriptor_sources[buffer.source].indirect_descriptor) return;
}
mem.resource = resource;
const auto access = PrepareMemoryResourceAccess(state, mem);
StoreWordPrepared(ctx, inst, mem, access, data);
};
if (mem.kind != IR::ResourceKind::Buffer ||
state.program.info.buffers[mem.resource].indirect_root != mem.resource) {
store(mem.resource);
return;
}
const auto& buffer = state.program.info.buffers[mem.resource];
const auto* handle = inst.Arg(0).ResolveInstruction();
if (handle == nullptr || buffer.indirect_resources.empty() ||
state.flattened_srt_variable == 0 || buffer.indirect_search_iterations == 0u) {
ctx.Fail(inst, "has no indirect buffer runtime mapping");
return;
}
const auto selected = EmitIndirectResourceIndex(
state, ctx.Arg(*handle, 0), buffer.indirect_mapping_offset,
buffer.indirect_search_iterations, UINT32_MAX);
const auto merge = state.builder.AllocateId();
std::vector<uint32_t> labels(buffer.indirect_resources.size());
std::vector<uint32_t> branches {spv::OpSwitch, selected, merge};
for (uint32_t i = 0; i < labels.size(); ++i) {
labels[i] = state.builder.AllocateId();
branches.push_back(i);
branches.push_back(labels[i]);
}
state.builder.AddFunction(spv::OpSelectionMerge, merge, spv::SelectionControlMaskNone);
state.builder.AddFunction(branches);
for (uint32_t i = 0; i < labels.size(); ++i) {
EmitLabel(state, labels[i]);
store(buffer.indirect_resources[i]);
state.builder.AddFunction(spv::OpBranch, merge);
}
EmitLabel(state, merge);
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
EmitBufferAccess(ctx, inst, mem, 0u, [&](const auto& selected, const auto& resource) {
StoreWordPrepared(ctx, inst, selected, resource, ctx.Arg(inst, inst.NumArgs() - 2));
});
});
}
@@ -599,7 +650,12 @@ template <typename Fn>
uint32_t EmitAtomicAccess(ValueEmitContext& ctx, const IR::Inst& inst,
const IR::MemoryInfo& mem, Fn&& operation) {
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto index = DwordIndex(ctx, inst, mem);
auto index = DwordIndex(ctx, inst, mem);
if (mem.kind == IR::ResourceKind::Lds || mem.kind == IR::ResourceKind::Gds) {
// Keep region-address truncation local to DS atomics.
index = Binary(ctx.state, spv::OpBitwiseAnd, TypeU32(ctx.state), index,
ConstantU32(ctx.state, 0x3fffu));
}
const auto resource = PrepareMemoryResourceAccess(ctx.state, mem);
return EmitValueOrZeroIfCondition(
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
@@ -649,15 +705,15 @@ PreparedFormattedMemory PrepareFormattedMemory(ValueEmitContext& ctx, const IR::
PreparedFormattedMemory plan;
plan.info = info;
plan.resource = resource;
const auto start = ByteAddress(ctx, inst, mem);
const auto start = ByteAddress(ctx, inst, mem, resource.byte_offset);
// Check the whole, unaligned format span before aligning the actual memory address.
const auto end = Binary(ctx.state, spv::OpIAdd, TypeU32(ctx.state), start,
ConstantU32(ctx.state, info.byte_size - 1u));
const auto last_index = Binary(ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), end,
ConstantU32(ctx.state, std::countr_zero(resource.element_bits / 8u)));
plan.in_bounds = AndCondition(
plan.in_bounds = BufferIndexInBounds(ctx, inst, mem, resource, AndCondition(
ctx.state, Binary(ctx.state, spv::OpUGreaterThanEqual, TypeBool(ctx.state), end, start),
EmitMemoryElementInBounds(ctx.state, plan.resource, last_index));
EmitMemoryElementInBounds(ctx.state, plan.resource, last_index)));
plan.base = Binary(ctx.state, spv::OpBitwiseAnd, TypeU32(ctx.state), start,
ConstantU32(ctx.state, ~(std::min(4u, info.byte_size) - 1u)));
return plan;
@@ -709,98 +765,86 @@ uint32_t FormattedOutOfBoundsValue(ValueEmitContext& ctx, const IR::MemoryInfo&
return ConstructU32Composite(ctx.state, components, values);
}
uint32_t FormattedLoad(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto resource = PrepareMemoryResourceAccess(ctx.state, mem);
const auto info = Format::GetFormatInfo(BufferFormat(ctx, mem));
if (info.type == Format::ComponentType::Unknown) {
return LoadWordPrepared(ctx, inst, RebaseRawComponent(mem, 0u), resource);
}
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info);
const auto value = EmitValueOrDefaultIfCondition(
ctx.state, plan.in_bounds, TypeU32(ctx.state),
FormattedOutOfBoundsValue(ctx, mem, plan, 1u),
[&]() { return LoadFormattedInBounds(ctx, mem, plan, 0u); });
if (mem.data_bits != 16u) {
return value;
uint32_t LoadFormattedPrepared(ValueEmitContext& ctx, const IR::Inst& inst,
const IR::MemoryInfo& mem, const MemoryResourceAccess& resource,
const Format::BufferFormatInfo& info, uint32_t components) {
auto& state = ctx.state;
const auto count = mem.data_bits == 16u ? mem.component_count : components;
const auto plan = resource.object_pointer == 0u ? PreparedFormattedMemory{.info = info}
: PrepareFormattedMemory(ctx, inst, mem, resource, info);
const auto out_of_bounds = FormattedOutOfBoundsValue(ctx, mem, plan, count);
const auto value = resource.object_pointer == 0u ? out_of_bounds : EmitValueOrDefaultIfCondition(
state, plan.in_bounds, TypeU32Composite(state, count), out_of_bounds, [&]() {
std::array<uint32_t, 4> values {};
for (uint32_t component = 0; component < count; component++) {
values[component] = LoadFormattedInBounds(ctx, mem, plan, component);
}
return ConstructU32Composite(state, count, values);
});
if (mem.data_bits != 16u) return value;
const bool integer = info.type == Format::ComponentType::Uint ||
info.type == Format::ComponentType::Sint;
const auto scalar = [&](uint32_t component) {
if (component >= count) return integer ? ConstantU32(state, 0u) : ConstantF32(state, 0);
auto result = value;
if (count != 1u) {
result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), result, value,
component);
}
if (info.type == Format::ComponentType::Uint) {
return EmitUMin32(ctx.state, value, ConstantU32(ctx.state, 0xffffu));
return EmitUMin32(state, result, ConstantU32(state, 0xffffu));
}
if (info.type == Format::ComponentType::Sint) {
const auto lower = EmitSMax32(ctx.state, value, ConstantU32(ctx.state, 0xffff8000u));
return EmitSMin32(ctx.state, lower, ConstantU32(ctx.state, 0x7fffu));
const auto lower = EmitSMax32(state, result, ConstantU32(state, 0xffff8000u));
return EmitSMin32(state, lower, ConstantU32(state, 0x7fffu));
}
return EmitConvertF16F32(ctx.state, EmitBitCastF32U32(ctx.state, value));
});
}
uint32_t EncodeFormattedStoreComponent(ValueEmitContext& ctx,
const Format::BufferFormatInfo& info,
uint32_t component, uint32_t data, uint32_t data_bits) {
auto& state = ctx.state;
const auto bits = info.component_bits[component];
if (data_bits == 16u) {
if (info.type == Format::ComponentType::Uint) {
data = EmitBitFieldUExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
} else if (info.type == Format::ComponentType::Sint) {
data = EmitBitFieldSExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
return EmitBitCastF32U32(state, result);
};
std::array<uint32_t, 4> packed {};
for (uint32_t word = 0; word < components; word++) {
const auto low = scalar(word * 2u);
const auto high = scalar(word * 2u + 1u);
if (integer) {
packed[word] = Binary(
state, spv::OpBitwiseOr, TypeU32(state),
Binary(state, spv::OpBitwiseAnd, TypeU32(state), low, ConstantU32(state, 0xffffu)),
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), high, ConstantU32(state, 16u)));
} else {
data = EmitBitCastU32F32(state, EmitF16BitsToF32(state, data));
const auto pair = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 2), pair,
low, high);
packed[word] = EmitPackHalf2x16(state, pair);
}
}
const bool normalized = info.type == Format::ComponentType::Unorm ||
info.type == Format::ComponentType::Snorm;
const bool scaled = info.type == Format::ComponentType::Uscaled ||
info.type == Format::ComponentType::Sscaled;
if (normalized || scaled) {
// Two-bit packed channels are unsigned even in signed formats.
const bool is_signed = bits != 2u && (info.type == Format::ComponentType::Snorm ||
info.type == Format::ComponentType::Sscaled);
const auto max_value = static_cast<float>((1u << (bits - (is_signed ? 1u : 0u))) - 1u);
const auto lower = normalized ? (is_signed ? -1.0f : 0.0f)
: (is_signed ? -max_value - 1.0f : 0.0f);
const auto upper = normalized ? 1.0f : max_value;
auto value = Select(state, TypeF32(state), EmitClassifyF32Bits(state, data).nan,
ConstantF32Value(state, 0.0f), EmitBitCastF32U32(state, data));
value = EmitGlsl<GLSLstd450FClamp, IR::Type::F32>(
state, value, ConstantF32Value(state, lower), ConstantF32Value(state, upper));
if (normalized) {
value = EmitFPRoundEven32(
state, EmitFPMul32(state, value, ConstantF32Value(state, max_value)));
}
// Float-to-integer conversion truncates scaled values toward zero.
value = Unary(state, is_signed ? spv::OpConvertFToS : spv::OpConvertFToU,
is_signed ? TypeI32(state) : TypeU32(state), value);
return is_signed ? Unary(state, spv::OpBitcast, TypeU32(state), value) : value;
}
if (bits == 16u && info.type == Format::ComponentType::Float) {
const auto pair = EmitCompositeConstructF32x2(
state, EmitBitCastF32U32(state, data), ConstantF32Value(state, 0.0f));
return EmitPackHalf2x16(state, pair);
}
return data;
return ConstructU32Composite(state, components, packed);
}
void StoreFormattedPrepared(ValueEmitContext& ctx, const IR::Inst& inst,
const IR::MemoryInfo& mem, const MemoryResourceAccess& resource,
const Format::BufferFormatInfo& info, uint32_t data,
uint32_t components) {
if (resource.object_pointer == 0u) return;
// RDNA2 formatted stores transfer the entire format, zero-filling missing VGPRs.
const auto count = mem.data_bits == 16u ? mem.component_count : components;
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info);
EmitIfCondition(ctx.state, plan.in_bounds, [&]() {
auto packed = ConstantU32(ctx.state, 0);
for (uint32_t component = 0; component < info.component_count; component++) {
auto value = ConstantU32(ctx.state, 0);
if (component < components) {
if (component < count) {
const auto word = mem.data_bits == 16u ? component / 2u : component;
value = data;
if (components != 1u) {
value = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state),
value, data, component);
value, data, word);
}
if (mem.data_bits == 16u && (component & 1u) != 0u)
value = Binary(ctx.state, spv::OpShiftRightLogical, TypeU32(ctx.state), value,
ConstantU32(ctx.state, 16u));
}
value = EncodeFormattedStoreComponent(ctx, info, component, value, mem.data_bits);
value = EncodeFormattedStoreComponent(ctx.state, info, component, value, mem.data_bits);
const auto bits = info.component_bits[component];
if (info.packed_bitfield) {
packed = EmitBitFieldInsert(
@@ -823,19 +867,6 @@ void StoreFormattedPrepared(ValueEmitContext& ctx, const IR::Inst& inst,
});
}
void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto resource = PrepareMemoryResourceAccess(ctx.state, mem);
const auto data = ctx.Arg(inst, inst.NumArgs() - 2);
const auto info = Format::GetFormatInfo(BufferFormat(ctx, mem));
if (info.type == Format::ComponentType::Unknown) {
StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, 0u), resource, data);
return;
}
StoreFormattedPrepared(ctx, inst, mem, resource, info, data, 1u);
});
}
uint32_t LoadIndirectScalarBuffer(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto& handle = *inst.Arg(0).ResolveInstruction();
@@ -879,12 +910,22 @@ struct IndirectBufferAccess {
uint32_t raw_index_in_bounds;
};
IndirectBufferAccess PrepareIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
std::array<uint32_t, 8> IndirectBufferArgs(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto& handle = *inst.Arg(0).ResolveInstruction();
const auto word1 = ctx.Arg(handle, 1);
const auto records = ctx.Arg(handle, 2);
const auto word3 = ctx.Arg(handle, 3);
auto offset = ctx.Arg(inst, 2);
if (ctx.Memory(inst).offset != 0u) {
const auto immediate = ConstantU32(state, ctx.Memory(inst).offset);
offset = offset == ConstantU32(state, 0) ? immediate
: Binary(state, spv::OpIAdd, TypeU32(state), offset, immediate);
}
return {ctx.Arg(handle, 0), ctx.Arg(handle, 1), ctx.Arg(handle, 2), ctx.Arg(handle, 3),
ctx.Arg(inst, 1), offset, ctx.Arg(inst, 3), BufferLane(state)};
}
IndirectBufferAccess PrepareIndirectBuffer(EmitterState& state,
const std::array<uint32_t, 8>& args, bool formatted) {
const auto& [word0, word1, records, word3, input_index, offset, soffset, lane] = args;
const auto field = [&](uint32_t word, uint32_t first, uint32_t count) {
return EmitBitFieldUExtract(state, word, ConstantU32(state, first), ConstantU32(state, count));
};
@@ -894,14 +935,12 @@ IndirectBufferAccess PrepareIndirectBuffer(ValueEmitContext& ctx, const IR::Inst
const auto stride = field(word1, 16, 14);
const auto swizzle = AndCondition(state, nonzero(stride), nonzero(field(word1, 31, 1)));
const auto index = Binary(
state, spv::OpIAdd, TypeU32(state), ctx.Arg(inst, 1),
Select(state, TypeU32(state), nonzero(field(word3, 23, 1)), BufferLane(state),
state, spv::OpIAdd, TypeU32(state), input_index,
Select(state, TypeU32(state), nonzero(field(word3, 23, 1)), lane,
ConstantU32(state, 0)));
const auto soffset = ctx.Arg(inst, 3);
const auto address = CalculateBufferAddress(state, index, ctx.Arg(inst, 2), soffset,
ctx.Memory(inst).offset, stride, swizzle,
const auto address = CalculateBufferAddress(state, index, offset, soffset, 0u, stride, swizzle,
field(word3, 21, 2));
const auto base = PackU64(state, ctx.Arg(handle, 0), field(word1, 0, 16));
const auto base = PackU64(state, word0, field(word1, 0, 16));
const auto raw_records = Binary(state, spv::OpISub, TypeU32(state), records, soffset);
return {
.address = Binary(state, spv::OpIAdd, TypeU64(state), base,
@@ -910,7 +949,7 @@ IndirectBufferAccess PrepareIndirectBuffer(ValueEmitContext& ctx, const IR::Inst
.stride = stride,
.swizzle = swizzle,
.format = field(word3, 12, 7),
.selector = ctx.Memory(inst).formatted ? field(word3, 0, 3) : 0u,
.selector = formatted ? field(word3, 0, 3) : 0u,
.mode = field(word3, 28, 2),
.records = records,
.index_in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index, records),
@@ -926,10 +965,9 @@ uint32_t IndirectBufferInBounds(EmitterState& state, const IndirectBufferAccess&
state, spv::OpIAdd, TypeU32(state), buffer.offset, ConstantU32(state, displacement));
const auto has_payload = [&](uint32_t size) {
return AndCondition(
state, Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), size,
ConstantU32(state, bytes)),
state, Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), size, bytes),
Binary(state, spv::OpULessThanEqual, TypeBool(state), offset,
Binary(state, spv::OpISub, TypeU32(state), size, ConstantU32(state, bytes))));
Binary(state, spv::OpISub, TypeU32(state), size, bytes)));
};
const auto structured = AndCondition(
state, buffer.index_in_bounds,
@@ -956,9 +994,9 @@ uint32_t IndirectBufferInBounds(EmitterState& state, const IndirectBufferAccess&
return in_bounds;
}
uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IndirectBufferAccess& buffer,
uint32_t bits, uint32_t components, bool coherent) {
auto& state = ctx.state;
const auto buffer = PrepareIndirectBuffer(ctx, inst);
const auto valid_format = Binary(state, spv::OpINotEqual, TypeBool(state), buffer.format,
ConstantU32(state, 0));
const auto base = Binary(state, spv::OpBitwiseAnd, TypeU64(state), buffer.address,
@@ -970,8 +1008,20 @@ uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_
ConstantU64(state, component * 4u));
values[component] = EmitValueOrZeroIfCondition(
state, AndCondition(state, valid_format,
IndirectBufferInBounds(state, buffer, component * 4u, 4u, false)),
[&]() { return LoadBdaDword(ctx, address); });
IndirectBufferInBounds(state, buffer, component * 4u,
ConstantU32(state, bits / 8u), false)),
[&]() {
const auto value = LoadBdaDword(ctx, address, coherent);
if (bits == 32u) return value;
const auto byte = Binary(
state, spv::OpBitwiseAnd, TypeU32(state),
Unary(state, spv::OpUConvert, TypeU32(state), buffer.address),
ConstantU32(state, bits == 8u ? 3u : 2u));
return EmitBitFieldUExtract(
state, value,
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte, ConstantU32(state, 3u)),
ConstantU32(state, bits));
});
}
return ConstructU32Composite(state, components, values);
}
@@ -979,100 +1029,109 @@ uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_
uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
return EmitValueOrZeroIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto buffer = PrepareIndirectBuffer(ctx, inst);
return EmitIndexSwitch(
state, buffer.format, static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u,
TypeU32(state), [&](uint32_t format) {
const auto info = Format::GetFormatInfo(static_cast<Prospero::BufferFormat>(format));
if (info.type == Format::ComponentType::Unknown) return ConstantU32(state, 0);
const auto constant = Select(
state, TypeU32(state),
Binary(state, spv::OpIEqual, TypeBool(state), buffer.selector, ConstantU32(state, 1)),
FormattedConstant(ctx, info, FormattedSourceKind::One), ConstantU32(state, 0));
return EmitValueOrDefaultIfCondition(
state, Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), buffer.selector,
ConstantU32(state, 4)),
TypeU32(state), constant, [&]() {
const auto in_bounds = IndirectBufferInBounds(state, buffer, 0u, info.byte_size, true);
const auto base = Binary(
state, spv::OpBitwiseAnd, TypeU64(state), buffer.address,
ConstantU64(state, ~(uint64_t(std::min(4u, info.byte_size)) - 1u)));
const auto load = [&](uint32_t component, uint32_t bits) {
const auto offset = Format::GetFormatComponentByteOffset(info, component);
const auto address = offset == 0u
? base
: Binary(state, spv::OpIAdd, TypeU64(state),
base, ConstantU64(state, offset));
return LoadBda(ctx, address, in_bounds, bits);
};
const auto emit_component = [&](uint32_t component) {
return LoadFormattedComponent(
ctx, info, {FormattedSourceKind::Memory, component},
[&](uint32_t source) { return load(source, 32u); }, load);
};
if (info.component_count == 1u) return emit_component(0u);
const auto component = Binary(
state, spv::OpUMod, TypeU32(state),
Binary(state, spv::OpISub, TypeU32(state), buffer.selector, ConstantU32(state, 4)),
ConstantU32(state, info.component_count));
return EmitIndexSwitch(state, component, info.component_count, TypeU32(state),
emit_component);
});
const auto buffer = PrepareIndirectBuffer(state, IndirectBufferArgs(ctx, inst), true);
const auto geometry = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry,
state.indirect_format_geometry_function, buffer.format);
const auto field = [&](uint32_t index) {
const auto value = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), value, geometry, index);
return value;
};
const auto bytes = field(0);
const auto component = Binary(
state, spv::OpUMod, TypeU32(state),
Binary(state, spv::OpISub, TypeU32(state), buffer.selector, ConstantU32(state, 4)), field(1));
const auto alignment = Unary(state, spv::OpUConvert, TypeU64(state), field(3));
const auto base = Binary(state, spv::OpBitwiseAnd, TypeU64(state), buffer.address,
Unary(state, spv::OpNot, TypeU64(state), alignment));
const auto address = Binary(
state, spv::OpIAdd, TypeU64(state), base,
Unary(state, spv::OpUConvert, TypeU64(state),
Binary(state, spv::OpIMul, TypeU32(state), component, field(2))));
const auto memory = AndCondition(
state, Binary(state, spv::OpINotEqual, TypeBool(state), bytes, ConstantU32(state, 0)),
Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), buffer.selector,
ConstantU32(state, 4)));
const auto raw = EmitValueOrZeroIfCondition(
state, AndCondition(state, memory, IndirectBufferInBounds(state, buffer, 0u, bytes, true)),
[&]() {
// Format alignment and component spacing keep every selected component within one DWORD.
const auto word = LoadBdaDword(
ctx, Binary(state, spv::OpBitwiseAnd, TypeU64(state), address,
ConstantU64(state, ~uint64_t {3})));
const auto byte = Binary(
state, spv::OpBitwiseAnd, TypeU32(state),
Unary(state, spv::OpUConvert, TypeU32(state), address), ConstantU32(state, 3));
return Binary(state, spv::OpShiftRightLogical, TypeU32(state), word,
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte,
ConstantU32(state, 3)));
});
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32(state), result,
state.indirect_format_function, buffer.format, buffer.selector,
component, raw);
return result;
});
}
uint32_t LoadBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
auto& state = ctx.state;
const auto mem = ctx.Memory(inst);
if (mem.kind == IR::ResourceKind::IndirectBuffer && mem.formatted) {
return LoadIndirectFormattedX(ctx, inst);
}
return EmitValueOrDefaultIfCondition(
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components),
ConstantU32CompositeZero(state, components), [&]() {
const auto mem = ctx.Memory(inst);
if (mem.kind == IR::ResourceKind::IndirectBuffer) {
return LoadIndirectBuffer(ctx, inst, components);
const auto function = state.indirect_buffer_load_functions.at(
{mem.data_bits, components, uint32_t(mem.coherent)});
const auto args = IndirectBufferArgs(ctx, inst);
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, components),
result, function, args);
return result;
}
const auto resource = PrepareMemoryResourceAccess(state, mem);
const auto info = Format::GetFormatInfo(
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
if (info.type != Format::ComponentType::Unknown) {
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info);
return EmitValueOrDefaultIfCondition(
state, plan.in_bounds, TypeU32Composite(state, components),
FormattedOutOfBoundsValue(ctx, mem, plan, components), [&]() {
std::array<uint32_t, 4> values {};
for (uint32_t component = 0; component < components; component++) {
values[component] = LoadFormattedInBounds(ctx, mem, plan, component);
}
return ConstructU32Composite(state, components, values);
});
}
std::array<uint32_t, 4> values {};
for (uint32_t component = 0; component < components; component++) {
values[component] =
LoadWordPrepared(ctx, inst, RebaseRawComponent(mem, component), resource);
}
return ConstructU32Composite(state, components, values);
return EmitBufferAccess(ctx, inst, mem, components, [&](const auto& selected, const auto& resource) {
const auto info = Format::GetFormatInfo(
selected.formatted ? BufferFormat(ctx, selected) : Prospero::BufferFormat::kInvalid);
if (info.type != Format::ComponentType::Unknown) {
return LoadFormattedPrepared(ctx, inst, selected, resource, info, components);
}
if (resource.object_pointer == 0u) return ConstantU32CompositeZero(state, components);
std::array<uint32_t, 4> values {};
for (uint32_t component = 0; component < components; component++) {
values[component] =
LoadWordPrepared(ctx, inst, RebaseRawComponent(selected, component), resource);
}
return ConstructU32Composite(state, components, values);
});
});
}
void StoreWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
void StoreBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
auto& state = ctx.state;
EmitIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto mem = ctx.Memory(inst);
const auto resource = PrepareMemoryResourceAccess(state, mem);
const auto mem = ctx.Memory(inst);
const auto composite = ctx.Arg(inst, inst.NumArgs() - 2);
const auto info = Format::GetFormatInfo(
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
if (info.type != Format::ComponentType::Unknown) {
StoreFormattedPrepared(ctx, inst, mem, resource, info, composite, components);
return;
}
for (uint32_t component = 0; component < components; component++) {
const auto data = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), data, composite,
component);
StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, component), resource, data);
}
EmitBufferAccess(ctx, inst, mem, 0u, [&](const auto& selected, const auto& resource) {
const auto info = Format::GetFormatInfo(
selected.formatted ? BufferFormat(ctx, selected) : Prospero::BufferFormat::kInvalid);
if (info.type != Format::ComponentType::Unknown) {
StoreFormattedPrepared(ctx, inst, selected, resource, info, composite, components);
return;
}
for (uint32_t component = 0; component < components; component++) {
auto data = composite;
if (components != 1u) {
data = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), data, composite,
component);
}
StoreWordPrepared(ctx, inst, RebaseRawComponent(selected, component), resource, data);
}
});
});
}
@@ -1129,6 +1188,124 @@ uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
return result;
}
void DefineIndirectBufferLoads(ValueEmitContext& ctx) {
auto& state = ctx.state;
for (const auto* block: state.program.blocks) {
for (const auto& inst: *block) {
const auto components = IR::BufferComponentCount(inst.GetOpcode());
if (components == 0u || inst.GetOpcode() == IR::ValueOpcode::ReadConstBuffer) continue;
const auto& memory = ctx.Memory(inst);
if (memory.kind != IR::ResourceKind::IndirectBuffer || memory.formatted ||
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) continue;
auto [entry, inserted] = state.indirect_buffer_load_functions.try_emplace(
std::array {memory.data_bits, components, uint32_t(memory.coherent)}, 0u);
if (!inserted) continue;
const auto function = entry->second = state.builder.AllocateId();
const auto type = TypeU32(state);
const auto result_type = TypeU32Composite(state, components);
const auto signature = state.builder.Type(
spv::OpTypeFunction, result_type, type, type, type, type, type, type, type, type);
const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" +
std::to_string(components) + (memory.coherent ? "_coherent" : "");
state.builder.AddName(function, name.c_str());
state.builder.AddFunction(spv::OpFunction, result_type, function,
spv::FunctionControlMaskNone, signature);
std::array<uint32_t, 8> args;
for (auto& arg: args) {
arg = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionParameter, type, arg);
}
EmitLabel(state, state.builder.AllocateId());
const auto result = LoadIndirectBuffer(ctx, PrepareIndirectBuffer(state, args, false),
memory.data_bits, components, memory.coherent);
state.builder.AddFunction(spv::OpReturnValue, result);
state.builder.AddFunction(spv::OpFunctionEnd);
}
}
}
void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
auto& state = ctx.state;
const auto needed = std::ranges::any_of(state.program.blocks, [&](const IR::Block* block) {
return std::ranges::any_of(*block, [&](const IR::Inst& inst) {
if (inst.GetOpcode() != IR::ValueOpcode::LoadBufferU32) return false;
const auto& memory = ctx.Memory(inst);
return memory.kind == IR::ResourceKind::IndirectBuffer && memory.formatted;
});
});
if (!needed) return;
const auto type = TypeU32(state);
constexpr auto format_count =
static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u;
{
const auto result_type = TypeU32Composite(state, 4);
const auto function_type = state.builder.Type(spv::OpTypeFunction, result_type, type);
state.indirect_format_geometry_function = state.builder.AllocateId();
const auto format = state.builder.AllocateId();
state.builder.AddName(state.indirect_format_geometry_function,
"indirect_buffer_format_geometry");
state.builder.AddFunction(spv::OpFunction, result_type,
state.indirect_format_geometry_function,
spv::FunctionControlMaskNone, function_type);
state.builder.AddFunction(spv::OpFunctionParameter, type, format);
EmitLabel(state, state.builder.AllocateId());
const auto result = EmitIndexSwitch(
state, format, format_count, result_type, [&](uint32_t format) {
const auto info = Format::GetFormatInfo(static_cast<Prospero::BufferFormat>(format));
return state.builder.Constant(
spv::OpConstantComposite, result_type, ConstantU32(state, info.byte_size),
ConstantU32(state, std::max(1u, info.component_count)),
ConstantU32(state, info.packed_bitfield ? 0u : info.component_bits[0] / 8u),
ConstantU32(state, info.byte_size == 0u ? 0u : std::min(4u, info.byte_size) - 1u));
});
state.builder.AddFunction(spv::OpReturnValue, result);
state.builder.AddFunction(spv::OpFunctionEnd);
}
const auto function_type = state.builder.Type(spv::OpTypeFunction, type, type, type, type, type);
state.indirect_format_function = state.builder.AllocateId();
const auto format = state.builder.AllocateId();
const auto selector = state.builder.AllocateId();
const auto component = state.builder.AllocateId();
const auto raw = state.builder.AllocateId();
state.builder.AddName(state.indirect_format_function, "convert_indirect_buffer_format");
state.builder.AddFunction(spv::OpFunction, type, state.indirect_format_function,
spv::FunctionControlMaskNone, function_type);
state.builder.AddFunction(spv::OpFunctionParameter, type, format);
state.builder.AddFunction(spv::OpFunctionParameter, type, selector);
state.builder.AddFunction(spv::OpFunctionParameter, type, component);
state.builder.AddFunction(spv::OpFunctionParameter, type, raw);
EmitLabel(state, state.builder.AllocateId());
const auto result = EmitIndexSwitch(
state, format, format_count, TypeU32(state), [&](uint32_t format) {
const auto info = Format::GetFormatInfo(static_cast<Prospero::BufferFormat>(format));
if (info.type == Format::ComponentType::Unknown) return ConstantU32(state, 0);
const auto constant = Select(
state, TypeU32(state),
Binary(state, spv::OpIEqual, TypeBool(state), selector, ConstantU32(state, 1)),
FormattedConstant(ctx, info, FormattedSourceKind::One), ConstantU32(state, 0));
return EmitValueOrDefaultIfCondition(
state, Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), selector,
ConstantU32(state, 4)),
TypeU32(state), constant, [&]() {
const auto emit_component = [&](uint32_t component) {
return LoadFormattedComponent(
ctx, info, {FormattedSourceKind::Memory, component},
[&](uint32_t) { return raw; },
[&](uint32_t, uint32_t bits) {
return Binary(state, spv::OpBitwiseAnd, TypeU32(state), raw,
ConstantU32(state, bits == 8u ? 0xffu : 0xffffu));
});
};
if (!info.packed_bitfield) return emit_component(0u);
return EmitIndexSwitch(state, component, info.component_count, TypeU32(state),
emit_component);
});
});
state.builder.AddFunction(spv::OpReturnValue, result);
state.builder.AddFunction(spv::OpFunctionEnd);
}
void DefineGetBdaPointer(EmitterState& state) {
if (!state.program.info.uses_dma) {
return;
@@ -1222,32 +1399,48 @@ void EmitSharedAtomic64(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto& mem = ctx.Memory(inst);
EnsureLdsStorage(state);
EmitIfCondition(state, ctx.Arg(inst, 2), [&]() {
const auto address = Binary(state, spv::OpBitwiseAnd, TypeU32(state),
ByteAddress(ctx, inst, mem), ConstantU32(state, 0xfff8u));
const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), address,
ConstantU32(state, 3u));
const auto in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index,
ConstantU32(state, LdsDwordCount(state) / 2u));
EmitIfCondition(state, in_bounds, [&]() {
auto native_index = index;
auto semantics = spv::MemorySemanticsWorkgroupMemoryMask;
if (state.lds_storage_class == spv::StorageClassStorageBuffer) {
native_index = EmitAddU32(state, index, EmitBinaryU32(
state, spv::OpShiftRightLogical, state.lds_base_dwords, ConstantU32(state, 1)));
semantics = spv::MemorySemanticsUniformMemoryMask;
}
const auto pointer = state.builder.AllocateId();
const auto address = Binary(state, spv::OpBitwiseAnd, TypeU32(state),
ByteAddress(ctx, inst, mem), ConstantU32(state, 0xfff8u));
const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), address,
ConstantU32(state, 3u));
const auto in_bounds = Binary(state, spv::OpULessThan, TypeBool(state), index,
ConstantU32(state, LdsDwordCount(state) / 2u));
const auto active = EmitLogicalAndBool(state, ctx.Arg(inst, inst.NumArgs() - 1), in_bounds);
const auto emit = [&]() {
auto native_index = index;
auto semantics = spv::MemorySemanticsWorkgroupMemoryMask;
if (state.lds_storage_class == spv::StorageClassStorageBuffer) {
native_index = EmitAddU32(state, index, EmitBinaryU32(
state, spv::OpShiftRightLogical, state.lds_base_dwords, ConstantU32(state, 1)));
semantics = spv::MemorySemanticsUniformMemoryMask;
}
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(
spv::OpAccessChain, TypePointer(state, state.lds_storage_class, TypeU64(state)),
pointer, state.lds_u64_variable, ConstantU32(state, 0), native_index);
const auto old = state.builder.AllocateId();
const auto opcode = SpirvAtomicOpcode(inst.GetOpcode());
const auto scope = ConstantU32(state, spv::ScopeWorkgroup);
const auto acquire_release =
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | semantics);
if (opcode == spv::OpAtomicCompareExchange) {
// RDNA2 DS compares DATA and stores DATA2, opposite to BUFFER atomics.
state.builder.AddFunction(
spv::OpAccessChain, TypePointer(state, state.lds_storage_class, TypeU64(state)),
pointer, state.lds_u64_variable, ConstantU32(state, 0), native_index);
const auto value = ctx.Arg(inst, 1);
state.builder.AddFunction(
SpirvAtomicOpcode(inst.GetOpcode()), TypeU64(state), state.builder.AllocateId(),
pointer, ConstantU32(state, spv::ScopeWorkgroup),
ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | semantics), value);
});
});
opcode, TypeU64(state), old, pointer, scope, acquire_release,
ConstantU32(state, spv::MemorySemanticsAcquireMask | semantics),
ctx.Arg(inst, 2), ctx.Arg(inst, 1));
} else {
state.builder.AddFunction(opcode, TypeU64(state), old, pointer, scope,
acquire_release, ctx.Arg(inst, 1));
}
return old;
};
if (inst.HasUses()) {
ctx.Define(inst, EmitValueOrDefaultIfCondition(
state, active, TypeU64(state), ConstantU64(state, 0), emit));
} else {
EmitIfCondition(state, active, emit);
}
}
uint32_t EmitBufferFloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst) {
@@ -1377,6 +1570,13 @@ void EmitReadConstBuffer(ValueEmitContext& ctx, const IR::Inst& inst) {
}));
}
uint32_t EmitGetBufferResourceIndex(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto& buffer = ctx.state.program.info.buffers.at(inst.Flags<uint32_t>());
if (buffer.indirect_resources.empty()) return ConstantU32(ctx.state, 0u);
return EmitIndirectResourceIndex(ctx.state, ctx.Arg(inst, 0), buffer.indirect_mapping_offset,
buffer.indirect_search_iterations, UINT32_MAX);
}
void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto op = inst.GetOpcode();
const auto& mem = ctx.Memory(inst);
@@ -1388,7 +1588,8 @@ void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
if (mem.kind == IR::ResourceKind::FlatLocal)
value = LoadLocalFlat(ctx, inst);
else if (buffer_components > 1u ||
(buffer_components == 1u && mem.kind == IR::ResourceKind::IndirectBuffer && !mem.formatted))
(buffer_components == 1u && mem.kind == IR::ResourceKind::IndirectBuffer) ||
(op == IR::ValueOpcode::LoadBufferU32 && mem.formatted))
value = LoadBuffer(ctx, inst, buffer_components);
else if (shared_components > 1u)
value = LoadWideShared(ctx, inst, shared_components);
@@ -1398,9 +1599,6 @@ void EmitLoadMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
mem.kind != IR::ResourceKind::Scratch)
value = LoadBda(ctx, GuestAddress(ctx, inst, mem), ctx.Arg(inst, inst.NumArgs() - 1),
address_info.data_bits, mem.coherent);
else if (op == IR::ValueOpcode::LoadBufferU32 && mem.formatted)
value = mem.kind == IR::ResourceKind::IndirectBuffer ? LoadIndirectFormattedX(ctx, inst)
: FormattedLoad(ctx, inst, mem);
else if (inst.GetType() == IR::Type::U8)
value = LoadSubword(ctx, inst, mem, 8, false);
else if (inst.GetType() == IR::Type::U16)
@@ -1419,11 +1617,11 @@ void EmitStoreMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
if (mem.kind == IR::ResourceKind::FlatLocal)
StoreLocalFlat(ctx, inst);
else if (buffer_components > 1u)
StoreWideBuffer(ctx, inst, buffer_components);
StoreBuffer(ctx, inst, buffer_components);
else if (shared_components > 1u)
StoreWideShared(ctx, inst, shared_components);
else if (op == IR::ValueOpcode::StoreBufferU32 && mem.formatted)
FormattedStore(ctx, inst, mem);
StoreBuffer(ctx, inst, 1u);
else if (type == IR::Type::U8)
StoreSubword(ctx, inst, mem, 8);
else if (type == IR::Type::U16)
@@ -1,30 +1,37 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
if (state.program.bindings.UsesPushData()) {
dword_index += state.program.bindings.push_data_start_dword;
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state),
pointer, state.push_constant_variable, ConstantU32(state, 0),
ConstantU32(state, dword_index));
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
return value;
}
uint32_t EmitPushDataLoad(EmitterState& state, uint32_t index) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state),
pointer, state.push_constant_variable, ConstantU32(state, 0), index);
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
return value;
}
static uint32_t EmitShaderDataDwordLoadAt(EmitterState& state, uint32_t dword_index) {
if (state.program.bindings.UsesPushData()) return EmitPushDataLoad(state, dword_index);
if (state.shader_data_storage_variable != 0) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state),
pointer, state.shader_data_storage_variable,
ConstantU32(state, 0), ConstantU32(state, dword_index));
ConstantU32(state, 0), dword_index);
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
return value;
}
return ConstantU32(state, 0);
}
uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
if (state.program.bindings.UsesPushData()) {
dword_index += state.program.bindings.push_data_start_dword;
}
return EmitShaderDataDwordLoadAt(state, ConstantU32(state, dword_index));
}
uint32_t EmitBinaryU32(EmitterState& state, spv::Op opcode, uint32_t lhs, uint32_t rhs) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(opcode, TypeU32(state), ret, lhs, rhs);
@@ -102,12 +109,48 @@ Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::
return state.program.info.buffers[mem.resource].descriptor_format;
}
uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resource) {
const auto& buffers = state.program.info.buffers;
const auto& root = buffers[resource];
if (root.indirect_root != resource || root.descriptor_index == UINT32_MAX ||
root.indirect_resources.size() < 2u) {
return UINT32_MAX;
}
const auto first = root.packed_stride == 0u ? 1u : 0u;
const auto representative = root.indirect_resources[first];
const auto& shared = buffers[representative];
if (shared.packed_stride == 0u) return UINT32_MAX;
uint32_t next_child_slot = UINT32_MAX;
for (uint32_t ordinal = first; ordinal < root.indirect_resources.size(); ++ordinal) {
const auto& candidate = buffers[root.indirect_resources[ordinal]];
if (candidate.packed_stride != shared.packed_stride ||
candidate.descriptor_format != shared.descriptor_format ||
candidate.descriptor_swizzle != shared.descriptor_swizzle ||
candidate.descriptor_index == UINT32_MAX ||
(ordinal > 1u && candidate.descriptor_index != next_child_slot)) return UINT32_MAX;
if (ordinal != 0u) next_child_slot = candidate.descriptor_index + 1u;
}
return representative;
}
void EmitMemoryOffsets(EmitterState& state) {
const auto count = state.program.bindings.descriptor_counts[static_cast<size_t>(IR::DescriptorBindingKind::Buffers)];
state.memory_byte_offsets.resize(count);
std::vector<bool> dynamic_offsets(count);
for (uint32_t resource = 0; resource < state.program.info.buffers.size(); ++resource) {
if (SharedIndirectBufferResource(state, resource) == UINT32_MAX) continue;
for (const auto candidate: state.program.info.buffers[resource].indirect_resources) {
dynamic_offsets[state.program.info.buffers[candidate].descriptor_index] = true;
}
}
for (uint32_t i = 0; i < count; i += 4u) {
const auto lanes = std::min(4u, count - i);
if (std::all_of(dynamic_offsets.begin() + i, dynamic_offsets.begin() + i + lanes,
[](bool dynamic) { return dynamic; })) continue;
const auto word =
EmitShaderDataDwordLoad(state, state.program.bindings.memory_offset_dword + i / 4u);
for (uint32_t lane = 0; lane < std::min(4u, count - i); ++lane) {
for (uint32_t lane = 0; lane < lanes; ++lane) {
if (dynamic_offsets[i + lane]) continue;
state.memory_byte_offsets[i + lane] = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
state, word, ConstantU32(state, lane * 8u), ConstantU32(state, 8u));
}
@@ -152,7 +195,8 @@ void EnsureLdsStorage(EmitterState& state) {
MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
const IR::MemoryInfo& mem,
BufferDefinition& buffer) {
BufferDefinition& buffer,
uint32_t array_index) {
if (buffer.variable == 0) {
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::Buffers, mem.resource,
"storage buffer descriptor array was not emitted");
@@ -160,22 +204,42 @@ MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
if (buffer.pointer_type == 0) {
buffer.pointer_type = TypePointer(state, spv::StorageClassStorageBuffer, buffer.type);
}
const auto array_index =
state.program.info.buffers.at(mem.resource).descriptor_index;
const bool non_uniform = array_index != 0u;
const auto static_index = state.program.info.buffers.at(mem.resource).descriptor_index;
if (!non_uniform) array_index = ConstantU32(state, static_index);
MemoryResourceAccess access {
.kind = mem.kind,
.non_uniform = non_uniform,
.memory_access = mem.coherent ? spv::MemoryAccessVolatileMask : spv::MemoryAccessMaskNone};
access.object_pointer = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain, buffer.pointer_type, access.object_pointer,
buffer.variable, ConstantU32(state, array_index));
access.byte_offset = state.memory_byte_offsets[array_index];
buffer.variable, array_index);
if (non_uniform) {
state.builder.RequireExtension("SPV_EXT_descriptor_indexing");
state.builder.RequireCapability(spv::CapabilityShaderNonUniform);
state.builder.RequireCapability(spv::CapabilityStorageBufferArrayNonUniformIndexing);
state.builder.AddAnnotation(spv::OpDecorate, access.object_pointer, spv::DecorationNonUniform);
const auto offsets_start = state.program.bindings.memory_offset_dword +
(state.program.bindings.UsesPushData() ? state.program.bindings.push_data_start_dword : 0u);
const auto word = EmitShaderDataDwordLoadAt(state, EmitAddU32(
state, ConstantU32(state, offsets_start),
Binary(state, spv::OpShiftRightLogical, TypeU32(state), array_index, ConstantU32(state, 2))));
const auto shift = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
Binary(state, spv::OpBitwiseAnd, TypeU32(state), array_index, ConstantU32(state, 3)),
ConstantU32(state, 3));
access.byte_offset = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
state, word, shift, ConstantU32(state, 8));
} else {
access.byte_offset = state.memory_byte_offsets[static_index];
}
access.length = state.builder.AllocateId();
state.builder.AddFunction(spv::OpArrayLength, TypeU32(state), access.length,
access.object_pointer, 0);
return access;
}
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem) {
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem,
uint32_t array_index) {
MemoryResourceAccess access {.kind = mem.kind};
switch (mem.kind) {
case IR::ResourceKind::Lds:
@@ -215,7 +279,7 @@ MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::
} else if (bits == 16u) {
buffer = &state.storage_buffers[1];
}
access = PrepareStorageBufferResourceAccess(state, mem, *buffer);
access = PrepareStorageBufferResourceAccess(state, mem, *buffer, array_index);
access.element_bits = bits;
return access;
}
@@ -260,6 +324,9 @@ uint32_t EmitStorageBufferElementPointer(EmitterState& state,
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain, pointer_type, pointer, access.object_pointer,
ConstantU32(state, 0), index);
if (access.non_uniform) {
state.builder.AddAnnotation(spv::OpDecorate, pointer, spv::DecorationNonUniform);
}
return pointer;
}
@@ -362,8 +429,82 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
}
}
uint32_t EncodeFormattedStoreComponent(EmitterState& state, const Format::BufferFormatInfo& info,
uint32_t component, uint32_t data, uint32_t data_bits,
UnormRounding rounding) {
EXIT_IF(rounding != UnormRounding::RoundEven && info.type != Format::ComponentType::Unorm);
const auto bits = info.component_bits[component];
if (data_bits == 16u) {
if (info.type == Format::ComponentType::Uint) {
data = EmitBitFieldUExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
} else if (info.type == Format::ComponentType::Sint) {
data = EmitBitFieldSExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
} else {
data = EmitBitCastU32F32(state, EmitF16BitsToF32(state, data));
}
}
if (info.type == Format::ComponentType::Sint && bits < 32u) {
const auto maximum = (1u << (bits - 1u)) - 1u;
data = EmitSMax32(state, data, ConstantU32(state, ~maximum));
data = EmitSMin32(state, data, ConstantU32(state, maximum));
return Binary(state, spv::OpBitwiseAnd, TypeU32(state), data,
ConstantU32(state, (1u << bits) - 1u));
}
const bool normalized = info.type == Format::ComponentType::Unorm ||
info.type == Format::ComponentType::Snorm;
const bool scaled = info.type == Format::ComponentType::Uscaled ||
info.type == Format::ComponentType::Sscaled;
if (normalized || scaled) {
// Two-bit packed channels are unsigned even in signed formats.
const bool is_signed = bits != 2u && (info.type == Format::ComponentType::Snorm ||
info.type == Format::ComponentType::Sscaled);
const auto max_value = static_cast<float>((1u << (bits - (is_signed ? 1u : 0u))) - 1u);
const auto lower = normalized ? (is_signed ? -1.0f : 0.0f)
: (is_signed ? -max_value - 1.0f : 0.0f);
const auto upper = normalized ? 1.0f : max_value;
auto value = Select(state, TypeF32(state), EmitClassifyF32Bits(state, data).nan,
ConstantF32Value(state, 0.0f), EmitBitCastF32U32(state, data));
value = EmitGlsl<GLSLstd450FClamp, IR::Type::F32>(
state, value, ConstantF32Value(state, lower), ConstantF32Value(state, upper));
if (normalized) {
value = EmitFPMul32(state, value, ConstantF32Value(state, max_value));
if (rounding == UnormRounding::RoundEven) {
value = EmitFPRoundEven32(state, value);
} else if (rounding == UnormRounding::HalfUp) {
value = EmitFPAdd32(state, value, ConstantF32Value(state, 0.5f));
}
}
// Float-to-integer conversion truncates scaled values toward zero.
value = Unary(state, is_signed ? spv::OpConvertFToS : spv::OpConvertFToU,
is_signed ? TypeI32(state) : TypeU32(state), value);
return is_signed ? Unary(state, spv::OpBitcast, TypeU32(state), value) : value;
}
if (bits == 16u && info.type == Format::ComponentType::Float) {
const auto pair = EmitCompositeConstructF32x2(
state, EmitBitCastF32U32(state, data), ConstantF32Value(state, 0.0f));
return EmitPackHalf2x16(state, pair);
}
return data;
}
uint32_t EncodePackedColorComponent(EmitterState& state, const Format::BufferFormatInfo& info,
uint32_t component, uint32_t data, uint32_t data_bits,
UnormRounding rounding) {
auto value = EncodeFormattedStoreComponent(state, info, component, data, data_bits, rounding);
if (info.type == Format::ComponentType::Uint) {
value = EmitUMin32(state, value,
ConstantU32(state, (1u << info.component_bits[component]) - 1u));
}
if (info.component_bit_offset[component] != 0u) {
value = Binary(state, spv::OpShiftLeftLogical, TypeU32(state), value,
ConstantU32(state, info.component_bit_offset[component]));
}
return value;
}
spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
switch (opcode) {
case IR::ValueOpcode::SharedAtomicCmpSwap64:
case IR::ValueOpcode::ImageAtomicCompareSwap32:
case IR::ValueOpcode::BufferAtomicCmpSwap32: return spv::OpAtomicCompareExchange;
case IR::ValueOpcode::ImageAtomicSwap32:
@@ -391,7 +532,8 @@ spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
case IR::ValueOpcode::ImageAtomicUMax32:
case IR::ValueOpcode::ImageAtomicUMax64:
case IR::ValueOpcode::BufferAtomicUMax32:
case IR::ValueOpcode::SharedAtomicUMax32: return spv::OpAtomicUMax;
case IR::ValueOpcode::SharedAtomicUMax32:
case IR::ValueOpcode::SharedAtomicUMax64: return spv::OpAtomicUMax;
case IR::ValueOpcode::ImageAtomicAnd32:
case IR::ValueOpcode::ImageAtomicAnd64:
case IR::ValueOpcode::BufferAtomicAnd32:
@@ -212,9 +212,8 @@ void DefineDescriptors(EmitterState& state) {
auto& bindings = state.program.bindings;
auto& counts = bindings.descriptor_counts;
auto& buffer_count = counts[static_cast<size_t>(Kind::Buffers)];
for (uint32_t index = 0; index < info.buffers.size(); ++index) {
info.buffers[index].descriptor_index = (info.live_buffers & (uint64_t{1} << index)) != 0
? buffer_count++ : UINT32_MAX;
for (auto& buffer: info.buffers) {
buffer.descriptor_index = buffer.live ? buffer_count++ : UINT32_MAX;
}
std::array<const IR::ImageResource*, IR::ImageBindingCount> image_types {};
for (auto& image: info.images) {
@@ -244,7 +243,8 @@ void DefineDescriptors(EmitterState& state) {
IR::PushData::StartFor(bindings.push_data_start_dword, bindings.ShaderDataDwords());
counts[static_cast<size_t>(Kind::ShaderData)] =
bindings.ShaderDataDwords() != 0 && !bindings.UsesPushData();
if (state.program.bindings.UsesPushData() || state.program.stage == ShaderType::Mesh) {
if (state.program.bindings.UsesPushData() || state.program.stage == ShaderType::Mesh ||
state.object_primitive_id_variable != 0) {
const auto type = PushConstantBlockType(state);
state.push_constant_variable = state.builder.DefineGlobalVariable(
TypePointer(state, spv::StorageClassPushConstant, type), spv::StorageClassPushConstant);
@@ -547,6 +547,11 @@ void DefineInputs(EmitterState& state) {
}
input.variable_id =
DefineInterfaceVariable(state, type, spv::StorageClassInput, input.debug_name.c_str());
if (state.program.stage == ShaderType::Pixel && input.kind == IR::StageInputKind::PrimitiveId) {
// Fragment PrimitiveId accepts Tessellation, which the device already enables.
state.builder.RequireCapability(spv::CapabilityTessellation);
state.builder.AddAnnotation(spv::OpDecorate, input.variable_id, spv::DecorationFlat);
}
if (input.kind == IR::StageInputKind::Layer || input.kind == IR::StageInputKind::SampleId) {
state.builder.AddAnnotation(spv::OpDecorate, input.variable_id, spv::DecorationFlat);
}
@@ -658,13 +663,21 @@ void DefineOutputs(EmitterState& state) {
BuiltIn(state.sample_mask_variable, SampleMaskArrayType(state), "gl_SampleMask",
spv::BuiltInSampleMask);
break;
case IR::StageOutputKind::ObjectPrimitiveId:
binding.variable_id = DefineInterfaceVariable(
state, TypeU32(state), spv::StorageClassOutput, binding.debug_name.c_str());
state.object_primitive_id_variable = binding.variable_id;
state.builder.AddAnnotation(spv::OpDecorate, binding.variable_id,
spv::DecorationLocation, binding.location);
break;
case IR::StageOutputKind::Parameter:
case IR::StageOutputKind::Mrt: {
const bool uint_output =
binding.kind == IR::StageOutputKind::Mrt &&
state.program.stage == ShaderType::Pixel &&
binding.index < std::size(state.input_info.pixel->target_output_mode) &&
state.input_info.pixel->target_output_mode[binding.index] == 7u;
(state.input_info.pixel->target_output_mode[binding.index] == 7u ||
(state.input_info.pixel->target_packed_unorm_mask & (1u << binding.index)) != 0u);
const auto type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput,
binding.debug_name.c_str());
@@ -765,16 +778,23 @@ void DefineModule(EmitterState& state) {
state.builder.RequireCapability(spv::CapabilityImageGatherExtended);
}
if (state.lane_count == 2 || state.program.info.subgroup_barrier ||
state.program.info.subgroup_ballot || state.program.info.subgroup_shuffle ||
state.program.info.subgroup_local_invocation_id) {
state.program.info.subgroup_ballot || state.program.info.subgroup_vote ||
state.program.info.subgroup_shuffle ||
state.program.info.subgroup_arithmetic || state.program.info.subgroup_local_invocation_id) {
state.builder.RequireCapability(spv::CapabilityGroupNonUniform);
}
if (state.lane_count == 2 || state.program.info.subgroup_ballot) {
state.builder.RequireCapability(spv::CapabilityGroupNonUniformBallot);
}
if (state.program.info.subgroup_vote) {
state.builder.RequireCapability(spv::CapabilityGroupNonUniformVote);
}
if (state.program.info.subgroup_shuffle) {
state.builder.RequireCapability(spv::CapabilityGroupNonUniformShuffle);
}
if (state.program.info.subgroup_arithmetic) {
state.builder.RequireCapability(spv::CapabilityGroupNonUniformArithmetic);
}
if (state.program.info.compute_derivatives && state.program.stage == ShaderType::Compute) {
state.builder.RequireCapability(spv::CapabilityComputeDerivativeGroupQuadsKHR);
state.builder.RequireExtension("SPV_KHR_compute_shader_derivatives");
@@ -85,6 +85,14 @@ void StoreDispatcherPhiEdge(ValueEmitContext& ctx, const DispatcherFunctionState
void EmitReturn(ValueEmitContext& ctx) {
EmitKillIfPixelValidMaskInactive(ctx.state);
if (ctx.state.object_primitive_id_variable != 0) {
auto& state = ctx.state;
const auto primitive = EmitGetBuiltin(
ctx, IR::Value {static_cast<uint32_t>(IR::StageInputKind::PrimitiveId)}, IR::Value {0u});
const auto object = EmitPushDataLoad(state, ConstantU32(state, 0));
state.builder.AddFunction(spv::OpStore, state.object_primitive_id_variable,
EmitAddU32(state, object, primitive));
}
ctx.state.builder.AddFunction(spv::OpReturn);
}
@@ -226,8 +234,9 @@ void Invoke(Return (*emit)(Context&, Args...), ValueEmitContext& ctx, const IR::
void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
if (ctx.half != 0 && (inst.GetOpcode() == IR::ValueOpcode::Ballot ||
inst.GetOpcode() == IR::ValueOpcode::ReadFirstLane)) {
// Both operations already combine both emulated halves into one whole-wave result.
inst.GetOpcode() == IR::ValueOpcode::ReadFirstLane ||
inst.GetOpcode() == IR::ValueOpcode::SubgroupUMinU32)) {
// These operations combine both emulated halves into one whole-wave result.
ctx.Define(inst, ctx.other_half->Result(inst));
return;
}
@@ -347,6 +356,7 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
const auto initial_parent = state.current_label;
state.builder.AddFunction(spv::OpBranch, dispatcher.header_label);
state.current_block = nullptr;
EmitLabel(state, dispatcher.header_label);
const auto pc = state.builder.AllocateId();
const auto next_pc = state.builder.AllocateId();
@@ -382,6 +392,7 @@ void EmitDispatcherFunction(ValueEmitContext& ctx, const DispatcherFunctionState
next_pc_words.push_back(state.current_label);
state.builder.AddFunction(spv::OpBranch, dispatcher.after_switch_label);
}
state.current_block = nullptr;
EmitLabel(state, dispatcher.after_switch_label);
state.builder.AddFunction(next_pc_words);
state.builder.AddFunction(spv::OpBranch, dispatcher.continue_label);
@@ -665,6 +676,8 @@ void EmitProgram(EmitterState& state) {
}
}
DefineGetBdaPointer(state);
DefineIndirectBufferLoads(ctx);
DefineIndirectBufferFormats(ctx);
DefineBvhIntersect(state);
if (program.info.uses_swizzle) {
ctx.scratch_u32_variable = state.builder.AllocateId();
@@ -744,6 +757,7 @@ void EmitProgram(EmitterState& state) {
EmitStructuredFunction(ctx);
}
state.builder.AddFunction(spv::OpFunctionEnd);
state.current_block = nullptr;
if (state.program.stage == ShaderType::Mesh) {
EmitMeshEntryPoint(state);
}
@@ -185,6 +185,7 @@ constexpr MimgGatherInfo MIMG_GATHER_OPCODE_LIST[] = {
{0x44u, Opcode::IMAGE_GATHER4_L, ImageSampleFlagLod},
{0x47u, Opcode::IMAGE_GATHER4_LZ, ImageSampleFlagLevelZero},
{0x48u, Opcode::IMAGE_GATHER4_C, ImageSampleFlagCompare},
{0x4cu, Opcode::IMAGE_GATHER4_C_L, ImageSampleFlagCompare | ImageSampleFlagLod},
{0x4fu, Opcode::IMAGE_GATHER4_C_LZ,
ImageSampleFlagCompare | ImageSampleFlagLevelZero},
{0x57u, Opcode::IMAGE_GATHER4_LZ_O,
@@ -351,6 +352,9 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
if (a16) {
inst.image_sample_flags |= ImageSampleFlagA16;
}
if ((sample != nullptr || gather != nullptr) && (word0 & (1u << 12u)) != 0u) {
inst.image_sample_flags |= ImageSampleFlagUnnormalized;
}
inst.image_dimension = dimension;
inst.image_r128 = r128;
inst.image_nsa_dwords = nsa_dwords;
@@ -42,13 +42,17 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
{0x0eu, Opcode::BUFFER_LOAD_DWORDX4, 4, 32},
{0x0fu, Opcode::BUFFER_LOAD_DWORDX3, 3, 32},
{0x18u, Opcode::BUFFER_STORE_BYTE, 1, 8},
{0x19u, Opcode::BUFFER_STORE_BYTE_D16_HI, 1, 8},
{0x1au, Opcode::BUFFER_STORE_SHORT, 1, 16},
{0x1bu, Opcode::BUFFER_STORE_SHORT_D16_HI, 1, 16},
{0x1cu, Opcode::BUFFER_STORE_DWORD, 1, 32},
{0x1du, Opcode::BUFFER_STORE_DWORDX2, 2, 32},
{0x1eu, Opcode::BUFFER_STORE_DWORDX4, 4, 32},
{0x1fu, Opcode::BUFFER_STORE_DWORDX3, 3, 32},
{0x20u, Opcode::BUFFER_LOAD_UBYTE_D16, 1, 8},
{0x24u, Opcode::BUFFER_LOAD_SHORT_D16, 1, 16},
{0x25u, Opcode::BUFFER_LOAD_SHORT_D16_HI, 1, 16},
{0x27u, Opcode::BUFFER_STORE_FORMAT_D16_HI_X, 1, 16, false, false, true},
{0x30u, Opcode::BUFFER_ATOMIC_SWAP, 1, 32},
{0x31u, Opcode::BUFFER_ATOMIC_CMPSWAP, 1, 32},
{0x32u, Opcode::BUFFER_ATOMIC_ADD, 1, 32},
@@ -66,7 +70,9 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
{0x59u, Opcode::BUFFER_ATOMIC_AND_X2, 2, 32},
{0x5au, Opcode::BUFFER_ATOMIC_OR_X2, 2, 32},
{0x80u, Opcode::BUFFER_LOAD_FORMAT_D16_X, 1, 16, false, false, true},
{0x83u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZW, 2, 16, false, false, true},
{0x84u, Opcode::BUFFER_STORE_FORMAT_D16_X, 1, 16, false, false, true},
{0x87u, Opcode::BUFFER_STORE_FORMAT_D16_XYZW, 2, 16, false, false, true},
};
constexpr MemoryOpcodeInfo MTBUF_OPCODE_LIST[] = {
@@ -116,7 +122,8 @@ constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
{0x3fu, Opcode::DS_ORDERED_COUNT, 1, 32},
{0x40u, Opcode::DS_ADD_U64, 2, 32}, {0x4au, Opcode::DS_OR_B64, 2, 32},
{0x4du, Opcode::DS_WRITE_B64, 2, 32}, {0x4eu, Opcode::DS_WRITE2_B64, 4, 32},
{0x4fu, Opcode::DS_WRITE2ST64_B64, 4, 32}, {0x76u, Opcode::DS_READ_B64, 2, 32},
{0x4fu, Opcode::DS_WRITE2ST64_B64, 4, 32}, {0x68u, Opcode::DS_MAX_RTN_U64, 2, 32},
{0x70u, Opcode::DS_CMPST_RTN_B64, 2, 32}, {0x76u, Opcode::DS_READ_B64, 2, 32},
{0x77u, Opcode::DS_READ2_B64, 4, 32}, {0x78u, Opcode::DS_READ2ST64_B64, 4, 32},
{0xa0u, Opcode::DS_WRITE_B8_D16_HI, 1, 8},
{0xa1u, Opcode::DS_WRITE_B16_D16_HI, 1, 16},
@@ -192,6 +199,8 @@ bool IsDsAtomicOpcode(Opcode opcode) {
case Opcode::DS_MIN_RTN_U32:
case Opcode::DS_MAX_U32:
case Opcode::DS_MAX_RTN_U32:
case Opcode::DS_MAX_RTN_U64:
case Opcode::DS_CMPST_RTN_B64:
case Opcode::DS_AND_B32:
case Opcode::DS_AND_RTN_B32:
case Opcode::DS_OR_B32:
@@ -211,6 +220,7 @@ uint32_t DsSourceCount(Opcode opcode) {
case Opcode::DS_WRITE2ST64_B32:
case Opcode::DS_WRITE2_B64:
case Opcode::DS_WRITE2ST64_B64:
case Opcode::DS_CMPST_RTN_B64:
case Opcode::DS_MSKOR_B32: return 3u;
case Opcode::DS_MIN_F32:
case Opcode::DS_MAX_F32:
@@ -300,10 +310,20 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
}
DecodeVectorGpr(vdata, inst.dst);
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_XYZW ||
inst.opcode == Opcode::BUFFER_STORE_FORMAT_D16_XYZW) {
inst.data_components = 4u;
}
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_X ||
inst.opcode == Opcode::BUFFER_LOAD_UBYTE_D16 ||
inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16 ||
inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16_HI) {
inst.dst.sdwa_sel = inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16_HI ? 5u : 4u;
} else if (inst.opcode == Opcode::BUFFER_STORE_BYTE_D16_HI) {
inst.dst.sdwa_sel = 2u;
} else if (inst.opcode == Opcode::BUFFER_STORE_SHORT_D16_HI ||
inst.opcode == Opcode::BUFFER_STORE_FORMAT_D16_HI_X) {
inst.dst.sdwa_sel = 5u;
}
DecodeVectorGpr(vaddr, inst.src0);
DecodeScalarSource(srsrc * 4u, pc, inst.src1);
@@ -143,6 +143,7 @@ std::string FormatMimg(const Instruction& inst) {
case Opcode::IMAGE_GATHER4_L:
case Opcode::IMAGE_GATHER4_LZ:
case Opcode::IMAGE_GATHER4_C:
case Opcode::IMAGE_GATHER4_C_L:
case Opcode::IMAGE_GATHER4_C_LZ:
case Opcode::IMAGE_GATHER4_LZ_O:
case Opcode::IMAGE_GATHER4_C_O:
@@ -611,6 +612,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::IMAGE_GATHER4_L:
case Opcode::IMAGE_GATHER4_LZ:
case Opcode::IMAGE_GATHER4_C:
case Opcode::IMAGE_GATHER4_C_L:
case Opcode::IMAGE_GATHER4_C_LZ:
case Opcode::IMAGE_GATHER4_LZ_O:
case Opcode::IMAGE_GATHER4_C_O:
@@ -631,12 +633,16 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::BUFFER_LOAD_FORMAT_XYZ:
case Opcode::BUFFER_LOAD_FORMAT_XYZW:
case Opcode::BUFFER_LOAD_FORMAT_D16_X:
case Opcode::BUFFER_LOAD_FORMAT_D16_XYZW:
case Opcode::BUFFER_STORE_FORMAT_X:
case Opcode::BUFFER_STORE_FORMAT_XY:
case Opcode::BUFFER_STORE_FORMAT_XYZ:
case Opcode::BUFFER_STORE_FORMAT_XYZW:
case Opcode::BUFFER_STORE_FORMAT_D16_X:
case Opcode::BUFFER_STORE_FORMAT_D16_HI_X:
case Opcode::BUFFER_STORE_FORMAT_D16_XYZW:
case Opcode::BUFFER_LOAD_UBYTE:
case Opcode::BUFFER_LOAD_UBYTE_D16:
case Opcode::BUFFER_LOAD_USHORT:
case Opcode::BUFFER_LOAD_SHORT_D16:
case Opcode::BUFFER_LOAD_SHORT_D16_HI:
@@ -645,7 +651,9 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::BUFFER_LOAD_DWORDX3:
case Opcode::BUFFER_LOAD_DWORDX4:
case Opcode::BUFFER_STORE_BYTE:
case Opcode::BUFFER_STORE_BYTE_D16_HI:
case Opcode::BUFFER_STORE_SHORT:
case Opcode::BUFFER_STORE_SHORT_D16_HI:
case Opcode::BUFFER_STORE_DWORD:
case Opcode::BUFFER_STORE_DWORDX2:
case Opcode::BUFFER_STORE_DWORDX3:
@@ -708,6 +716,8 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::DS_MIN_RTN_U32:
case Opcode::DS_MAX_U32:
case Opcode::DS_MAX_RTN_U32:
case Opcode::DS_MAX_RTN_U64:
case Opcode::DS_CMPST_RTN_B64:
case Opcode::DS_AND_B32:
case Opcode::DS_AND_RTN_B32:
case Opcode::DS_OR_B32:
@@ -547,12 +547,16 @@ enum class Opcode {
BUFFER_LOAD_FORMAT_XYZ,
BUFFER_LOAD_FORMAT_XYZW,
BUFFER_LOAD_FORMAT_D16_X,
BUFFER_LOAD_FORMAT_D16_XYZW,
BUFFER_STORE_FORMAT_X,
BUFFER_STORE_FORMAT_XY,
BUFFER_STORE_FORMAT_XYZ,
BUFFER_STORE_FORMAT_XYZW,
BUFFER_STORE_FORMAT_D16_X,
BUFFER_STORE_FORMAT_D16_HI_X,
BUFFER_STORE_FORMAT_D16_XYZW,
BUFFER_LOAD_UBYTE,
BUFFER_LOAD_UBYTE_D16,
BUFFER_LOAD_SBYTE,
BUFFER_LOAD_USHORT,
BUFFER_LOAD_SSHORT,
@@ -563,7 +567,9 @@ enum class Opcode {
BUFFER_LOAD_DWORDX3,
BUFFER_LOAD_DWORDX4,
BUFFER_STORE_BYTE,
BUFFER_STORE_BYTE_D16_HI,
BUFFER_STORE_SHORT,
BUFFER_STORE_SHORT_D16_HI,
BUFFER_STORE_DWORD,
BUFFER_STORE_DWORDX2,
BUFFER_STORE_DWORDX3,
@@ -624,6 +630,8 @@ enum class Opcode {
DS_MIN_RTN_U32,
DS_MAX_U32,
DS_MAX_RTN_U32,
DS_MAX_RTN_U64,
DS_CMPST_RTN_B64,
DS_AND_B32,
DS_AND_RTN_B32,
DS_OR_B32,
@@ -692,6 +700,7 @@ enum class Opcode {
IMAGE_GATHER4_L,
IMAGE_GATHER4_LZ,
IMAGE_GATHER4_C,
IMAGE_GATHER4_C_L,
IMAGE_GATHER4_C_LZ,
IMAGE_GATHER4_LZ_O,
IMAGE_GATHER4_C_O,
@@ -762,6 +771,7 @@ enum ImageSampleFlag : uint32_t {
ImageSampleFlagCd = 1u << 8u,
ImageSampleFlagGatherHorizontal = 1u << 9u,
ImageSampleFlagAdjust = 1u << 10u,
ImageSampleFlagUnnormalized = 1u << 11u,
};
enum class ImageDimension : uint32_t {
@@ -17,7 +17,6 @@ enum class Vop2SdwaProfile {
IntegerFullDestination,
IntegerPartialDestination,
ReverseRightShift,
ReverseLogicalLeft,
Bitwise,
Count,
};
@@ -48,7 +47,7 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
{0x17u, Opcode::V_ASHR_I32},
{0x18u, Opcode::V_ASHRREV_I32, Vop2SdwaProfile::ReverseRightShift},
{0x19u, Opcode::V_LSHL_B32},
{0x1au, Opcode::V_LSHLREV_B32, Vop2SdwaProfile::ReverseLogicalLeft},
{0x1au, Opcode::V_LSHLREV_B32, Vop2SdwaProfile::IntegerPartialDestination},
{0x1bu, Opcode::V_AND_B32, Vop2SdwaProfile::Bitwise},
{0x1cu, Opcode::V_OR_B32, Vop2SdwaProfile::Bitwise},
{0x1du, Opcode::V_XOR_B32, Vop2SdwaProfile::Bitwise},
@@ -1041,7 +1040,6 @@ constexpr Vop2SdwaRule VOP2_SDWA_RULES[] = {
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false},
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
// Bitwise: WriteRawU32 inserts a byte or word destination like any other partial write.
{SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false},
};
@@ -187,7 +187,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
if (decoded.family == Decoder::Family::MUBUF || decoded.family == Decoder::Family::MTBUF) {
const bool store_or_atomic =
(decoded.opcode >= Decoder::Opcode::BUFFER_STORE_FORMAT_X &&
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X) ||
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XYZW) ||
(decoded.opcode >= Decoder::Opcode::BUFFER_STORE_BYTE &&
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_DWORDX4) ||
(decoded.opcode >= Decoder::Opcode::TBUFFER_STORE_FORMAT_X &&
@@ -578,11 +578,13 @@ void Translator::DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opc
bool returns_value) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto address = ReadU32(inst.src0);
const IR::Value value =
memory.data_dwords == 2u ? IR::Value(ReadU64(inst.src1)) : IR::Value(ReadU32(inst.src1));
const auto read = [&](const Decoder::Operand& operand) -> IR::Value {
return memory.data_dwords == 2u ? IR::Value(ReadU64(operand)) : IR::Value(ReadU32(operand));
};
const auto value = read(inst.src1);
const auto flags = AddMemoryInfo(memory, inst.pc);
const auto result = inst.src_count == 3u
? ir.Emit(opcode, {address, value, ReadU32(inst.src2), ir.GetExec()}, flags)
? ir.Emit(opcode, {address, value, read(inst.src2), ir.GetExec()}, flags)
: ir.Emit(opcode, {address, value, ir.GetExec()}, flags);
if (returns_value) {
WriteOperand(inst.dst, result);
@@ -971,6 +973,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
}
case Decoder::Opcode::BUFFER_LOAD_UBYTE:
case Decoder::Opcode::BUFFER_LOAD_UBYTE_D16:
case Decoder::Opcode::BUFFER_LOAD_SBYTE:
case Decoder::Opcode::BUFFER_LOAD_USHORT:
case Decoder::Opcode::BUFFER_LOAD_SSHORT:
@@ -985,6 +988,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZ:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZW:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_X:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XYZW:
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_X:
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XY:
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XYZ:
@@ -995,12 +999,16 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::BUFFER_STORE_DWORDX3:
case Decoder::Opcode::BUFFER_STORE_DWORDX4:
case Decoder::Opcode::BUFFER_STORE_BYTE:
case Decoder::Opcode::BUFFER_STORE_BYTE_D16_HI:
case Decoder::Opcode::BUFFER_STORE_SHORT:
case Decoder::Opcode::BUFFER_STORE_SHORT_D16_HI:
case Decoder::Opcode::BUFFER_STORE_FORMAT_X:
case Decoder::Opcode::BUFFER_STORE_FORMAT_XY:
case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZ:
case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZW:
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X:
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_HI_X:
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XYZW:
case Decoder::Opcode::TBUFFER_STORE_FORMAT_X:
case Decoder::Opcode::TBUFFER_STORE_FORMAT_XY:
case Decoder::Opcode::TBUFFER_STORE_FORMAT_XYZ:
@@ -1073,6 +1081,10 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicUMax32, false);
case Decoder::Opcode::DS_MAX_RTN_U32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicUMax32, true);
case Decoder::Opcode::DS_MAX_RTN_U64:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicUMax64, true);
case Decoder::Opcode::DS_CMPST_RTN_B64:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicCmpSwap64, true);
case Decoder::Opcode::DS_AND_B32:
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicAnd32, false);
case Decoder::Opcode::DS_AND_RTN_B32:
@@ -1151,6 +1163,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::IMAGE_GATHER4_L:
case Decoder::Opcode::IMAGE_GATHER4_LZ:
case Decoder::Opcode::IMAGE_GATHER4_C:
case Decoder::Opcode::IMAGE_GATHER4_C_L:
case Decoder::Opcode::IMAGE_GATHER4_C_LZ:
case Decoder::Opcode::IMAGE_GATHER4_LZ_O:
case Decoder::Opcode::IMAGE_GATHER4_C_O:
@@ -319,7 +319,7 @@ void Translator::WriteRawU32(const Decoder::Operand& operand, IR::U32 value) {
case Decoder::OperandKind::Sgpr: {
const auto reg = static_cast<IR::ScalarReg>(operand.reg);
ir.SetScalarReg(reg, value);
program.scalar_writes.push_back({current_pc, reg});
program.scalar_writes.push_back({current_pc, reg, value});
ir.SetScalarMaskTag(reg, IR::U1(IR::Value(false)));
if (IR::RegIndex(reg) > 0u) {
ir.SetScalarMaskTag(static_cast<IR::ScalarReg>(IR::RegIndex(reg) - 1u),
@@ -685,13 +685,13 @@ std::array<IR::U32, 2> Translator::WriteMask(const Decoder::Operand& operand, IR
IR::U1(IR::Value(false)));
}
ir.SetScalarReg(reg, mask[0]);
program.scalar_writes.push_back({current_pc, reg});
program.scalar_writes.push_back({current_pc, reg, mask[0]});
// A wave32 VALU mask destination must not overwrite the neighboring SGPR.
if ((write_64 || program.wave_size == 64u) &&
IR::RegIndex(reg) + 1u < IR::NumScalarRegs) {
const auto high = static_cast<IR::ScalarReg>(IR::RegIndex(reg) + 1u);
ir.SetScalarReg(high, mask[1]);
program.scalar_writes.push_back({current_pc, high});
program.scalar_writes.push_back({current_pc, high, mask[1]});
ir.SetThreadBitScalarReg(high, IR::U1(IR::Value(false)));
ir.SetScalarMaskTag(high, IR::U1(IR::Value(false)));
}
@@ -948,9 +948,14 @@ void IncludeInstructionVectorRegisters(const Decoder::Instruction& inst, uint32_
switch (inst.opcode) {
case Decoder::Opcode::DS_ADD_U64:
case Decoder::Opcode::DS_OR_B64:
case Decoder::Opcode::DS_MAX_RTN_U64:
case Decoder::Opcode::DS_WRITE_B64:
case Decoder::Opcode::DS_WRITE_B96:
case Decoder::Opcode::DS_WRITE_B128: include_vector(inst.src1, inst.data_dwords); break;
case Decoder::Opcode::DS_CMPST_RTN_B64:
include_vector(inst.src1, 2u);
include_vector(inst.src2, 2u);
break;
case Decoder::Opcode::DS_WRITE2_B32:
case Decoder::Opcode::DS_WRITE2ST64_B32:
case Decoder::Opcode::DS_WRITE2_B64:
+17 -9
View File
@@ -164,6 +164,16 @@ bool EquivalentValue(const ResourcePlan& program, Value left, Value right) {
return EquivalentValue(program, left, right, visited);
}
bool EquivalentSelectorDomain(const ResourcePlan& program,
const DescriptorSource::IndirectDescriptor& left,
const DescriptorSource::IndirectDescriptor& right) {
return left.selector == right.selector && left.signed_count == right.signed_count &&
left.workgroup_axis == right.workgroup_axis &&
EquivalentValue(program, left.key_count, right.key_count) &&
EquivalentValue(program, left.selector_first, right.selector_first) &&
EquivalentValue(program, left.selector_mask, right.selector_mask);
}
Value ResolveInvariantPhi(const ResourcePlan& program, Value value) {
value = value.Resolve();
const auto* root = value.TryInstruction();
@@ -471,10 +481,15 @@ void ValidateProgram(const Program& program, bool require_ssa) {
}
if (memory.kind == ResourceKind::IndirectBuffer &&
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) {
return Fail("indirect buffer requires a scalar, raw DWORD x1/x2/x3/x4, or formatted X load");
return Fail("indirect buffer requires a scalar, raw 8/16-bit, raw DWORD x1/x2/x3/x4, "
"or formatted X load");
}
const auto access = BufferAccessOf(inst.GetOpcode());
const bool packed_formatted =
(access == BufferAccess::Read || access == BufferAccess::Write) && memory.formatted &&
memory.data_bits == 16u && memory.component_count == 4u && buffer_components == 2u;
if (buffer_components > 1u &&
(!vector_buffer || memory.data_bits != 32u ||
(!vector_buffer || (memory.data_bits != 32u && !packed_formatted) ||
memory.data_dwords != buffer_components || memory.component_index != 0u)) {
return Fail(fmt::format("{} has inconsistent native-wide metadata",
ValueOpcodeName(inst.GetOpcode())));
@@ -664,13 +679,6 @@ void ValidateProgram(const Program& program, bool require_ssa) {
}
}
void ResolveControlFlowIdentities(Program& program) {
for (auto* block: program.blocks) {
block->condition = block->condition.Resolve();
block->indirect_target = block->indirect_target.Resolve();
}
}
std::string ProgramToString(const Program& program) {
std::map<const Inst*, size_t> ids;
size_t next_id = 1;
+80 -6
View File
@@ -19,6 +19,7 @@
#include <memory>
#include <optional>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace Xbyak {
@@ -27,6 +28,11 @@ class CodeGenerator;
namespace Libs::Graphics::ShaderRecompiler::IR {
class SrtWalker;
// Finite descriptor domains also bound the arithmetic used to prove their indices.
constexpr uint32_t MaxIndirectDescriptorProbes = 65536u;
enum class ResourceKind {
None,
ScalarBuffer,
@@ -77,7 +83,10 @@ struct MemoryInfo {
bool planning_only = false;
[[nodiscard]] bool SupportsIndirectBufferLoad(ValueOpcode opcode) const {
return !typed && data_bits == 32u &&
if (typed) return false;
if (data_bits == 8u) return !formatted && opcode == ValueOpcode::LoadBufferU8;
if (data_bits == 16u) return !formatted && opcode == ValueOpcode::LoadBufferU16;
return data_bits == 32u &&
(formatted ? opcode == ValueOpcode::LoadBufferU32
: opcode == ValueOpcode::ReadConstBuffer ||
opcode == ValueOpcode::LoadBufferU32 ||
@@ -120,6 +129,7 @@ struct BufferResource {
bool atomic = false;
bool formatted = false;
bool scalar = false;
bool live = false;
uint32_t indirect_root = NoIndirectBuffer;
uint32_t indirect_mapping_offset = 0;
uint32_t indirect_search_iterations = 0;
@@ -149,6 +159,7 @@ struct ImageResource {
bool written = false;
bool atomic = false;
bool atomic64 = false;
bool unfiltered_access_only = true;
bool depth_compare = false;
bool cube = false;
bool r128 = false;
@@ -160,11 +171,27 @@ struct ImageResource {
bool operator==(const ImageResource& other) const = default;
};
[[nodiscard]] constexpr Prospero::BufferFormat
RawSignedStorageFormat(const ImageResource& image, Prospero::BufferFormat format) {
if (image.resource_class != ImageResourceClass::Storage || !image.written)
return Prospero::BufferFormat::kInvalid;
if (format == Prospero::BufferFormat::k32SInt && (image.atomic ? !image.atomic64 : !image.read))
return Prospero::BufferFormat::k32UInt;
if (format == Prospero::BufferFormat::k16SInt && !image.read && !image.atomic)
return Prospero::BufferFormat::k16UInt;
return Prospero::BufferFormat::kInvalid;
}
struct SamplerResource {
static constexpr uint32_t NoIndirectSampler = UINT32_MAX;
uint32_t source = 0;
uint32_t first_use_pc = 0;
// Native filtering/border variants share the original sampler's runtime descriptor.
uint32_t snapshot_index = 0;
uint32_t indirect_root = NoIndirectSampler;
uint32_t indirect_mapping_offset = 0;
uint32_t indirect_search_iterations = 0;
std::vector<uint32_t> indirect_resources;
bool force_point_filtering = false;
bool depth_compare = false;
@@ -218,6 +245,7 @@ enum class StageOutputKind {
Position,
Parameter,
Mrt,
ObjectPrimitiveId,
Depth,
SampleMask,
PointSize,
@@ -323,6 +351,10 @@ struct PushData {
static constexpr uint32_t NoStart = UINT32_MAX;
std::array<uint32_t, DwordCount> dwords {};
[[nodiscard]] static constexpr uint32_t GraphicsDrawDwords(bool mesh, bool object_id) {
return mesh ? MeshDrawDwordCount : static_cast<uint32_t>(object_id);
}
[[nodiscard]] static constexpr bool CanFit(uint32_t start, uint32_t size) {
return size != 0 && start <= DwordCount && size <= DwordCount - start;
}
@@ -455,7 +487,7 @@ struct Bindings {
};
struct ShaderInfo {
static constexpr uint32_t MaxBuffers = 64;
static constexpr uint32_t MaxBufferRoots = 64;
static constexpr uint32_t MaxImages = 64;
static constexpr uint32_t MaxSamplers = 32;
static constexpr uint32_t MaxSampledPairs = 64;
@@ -467,7 +499,6 @@ struct ShaderInfo {
std::vector<StageInput> inputs;
std::vector<StageOutput> outputs;
std::vector<uint32_t> user_data_registers;
uint64_t live_buffers = 0;
std::array<uint8_t, 32> vertex_fetch_components {};
int32_t vertex_offset_sgpr = -1;
int32_t instance_offset_sgpr = -1;
@@ -480,8 +511,10 @@ struct ShaderInfo {
bool uses_dispatch_threads = false;
bool bvh = false;
bool subgroup_ballot = false;
bool subgroup_vote = false;
bool subgroup_barrier = false;
bool subgroup_shuffle = false;
bool subgroup_arithmetic = false;
bool subgroup_local_invocation_id = false;
bool compute_derivatives = false;
bool image_gather_extended = false;
@@ -501,21 +534,48 @@ struct ShaderInfo {
struct DescriptorSource {
struct IndirectDescriptor {
struct SelectorRead {
struct CountedRows {
uint32_t source = UINT32_MAX;
uint32_t stride = 0; // Byte spacing between selector rows, not their length.
bool operator==(const CountedRows& other) const = default;
};
uint32_t source = UINT32_MAX;
uint32_t stride = 0;
uint32_t offset = 0;
uint32_t maximum = UINT32_MAX;
uint32_t bit_offset = 0;
uint32_t bit_mask = UINT32_MAX;
bool global = false;
// Four DWORD bitmap words per record; set-bit ranks subtract from the last key.
bool bitset = false;
std::optional<CountedRows> rows;
std::vector<std::pair<uint32_t, uint32_t>> byte_ranges;
bool operator==(const SelectorRead& other) const = default;
};
struct PointerTable {
uint32_t offset = 0;
uint32_t stride = 0;
uint32_t count = 1;
bool indexed = false; // Parent pointers use raw MUBUF record addressing.
bool operator==(const PointerTable& other) const = default;
};
std::optional<SelectorRead> selector;
std::optional<uint32_t> table_maximum;
std::optional<PointerTable> pointer_table;
uint32_t table_source = 0;
uint32_t table_offset = 0;
uint32_t table_immediate = 0;
uint32_t table_stride = 0;
uint32_t table_record_bytes = 0;
bool table_scalar = false;
bool indexed = false;
bool signed_count = false;
uint32_t workgroup_axis = UINT32_MAX;
Value key_count;
// First selector index, or first descriptor key for a direct or bitmap range.
Value selector_first;
Value selector_mask;
std::vector<uint32_t> sources;
@@ -567,6 +627,12 @@ struct UniformFillPlan {
// Resource analysis retained by the shader cache. It owns immutable descriptor/SRT,
// condition and fill values without translated blocks, plus reusable evaluation scratch.
struct ResourcePlan {
struct SelectorKeys {
uint32_t source = UINT32_MAX;
std::vector<uint32_t> values;
bool resolved = false;
};
struct EvaluationContext {
struct Entry {
uint64_t value = 0;
@@ -575,6 +641,9 @@ struct ResourcePlan {
std::vector<Entry> values;
uint64_t generation = 0;
const SrtWalker* session = nullptr;
Value active_mask;
uint8_t read_policy = 0;
};
ResourcePlan();
@@ -592,7 +661,9 @@ struct ResourcePlan {
std::list<Inst> value_storage;
std::vector<MemoryInfo> memory_info;
std::vector<DescriptorSource> descriptor_sources;
std::vector<uint32_t> selector_groups;
std::vector<ResourceBlock> control_flow;
std::unordered_map<const Inst*, std::vector<uint32_t>> control_phi_predecessors;
std::vector<SrtRead> srt_reads;
bool requires_specialization_memory = false;
std::vector<std::pair<uint64_t, uint64_t>> source_reads;
@@ -605,11 +676,12 @@ struct ResourcePlan {
// GPU-thread scratch for nested clean/EXEC memos, activity and material keys.
mutable std::deque<EvaluationContext> evaluation_contexts;
mutable uint32_t evaluation_value_count = 0;
mutable uint32_t evaluation_depth = 0;
mutable std::vector<uint8_t> active_sources;
mutable std::vector<uint8_t> visited_blocks;
mutable std::vector<uint8_t> reachable_blocks;
mutable std::vector<uint32_t> pending_blocks;
mutable std::vector<uint32_t> material_keys;
mutable std::vector<SelectorKeys> selector_keys;
};
struct Program: ResourcePlan {
@@ -629,7 +701,7 @@ struct Program: ResourcePlan {
bool dispatcher_fallback = false;
CFG::FailureKind cfg_failure_kind = CFG::FailureKind::None;
std::string fallback_reason;
struct ScalarWrite { uint32_t pc; ScalarReg reg; };
struct ScalarWrite { uint32_t pc; ScalarReg reg; Value value; };
std::vector<ScalarWrite> scalar_writes;
// Typed memory and export instructions reference shader-local metadata by dense index.
// Decoder-only details (such as NSA register numbers) have already become IR operands.
@@ -646,8 +718,10 @@ std::string ProgramToString(const Program& program);
bool HasShaderMemoryWrites(const Program& program);
void ValidateProgram(const Program& program, bool require_ssa);
void ResolveControlFlowIdentities(Program& program);
bool EquivalentValue(const ResourcePlan& program, Value left, Value right);
bool EquivalentSelectorDomain(const ResourcePlan& program,
const DescriptorSource::IndirectDescriptor& left,
const DescriptorSource::IndirectDescriptor& right);
Value ResolveInvariantPhi(const ResourcePlan& program, Value value);
Value ResolveActiveU32(Value value, Value active);
@@ -21,6 +21,7 @@ class Value {
public:
Value() = default;
explicit Value(Inst* value);
explicit Value(const Inst* value) = delete;
explicit Value(ScalarReg value);
explicit Value(VectorReg value);
explicit Value(bool value);
@@ -175,6 +175,8 @@ SharedAccess SharedAccessOf(ValueOpcode opcode) {
case ValueOpcode::SharedAtomicUMin32:
case ValueOpcode::SharedAtomicSMax32:
case ValueOpcode::SharedAtomicUMax32:
case ValueOpcode::SharedAtomicUMax64:
case ValueOpcode::SharedAtomicCmpSwap64:
case ValueOpcode::SharedAtomicAnd32:
case ValueOpcode::SharedAtomicOr32:
case ValueOpcode::SharedAtomicOr64:
@@ -190,6 +192,8 @@ uint32_t SharedComponentCount(ValueOpcode opcode) {
switch (opcode) {
case ValueOpcode::SharedAtomicIAdd64:
case ValueOpcode::SharedAtomicOr64:
case ValueOpcode::SharedAtomicUMax64:
case ValueOpcode::SharedAtomicCmpSwap64:
case ValueOpcode::LoadSharedU32x2:
case ValueOpcode::WriteSharedU32x2: return 2u;
case ValueOpcode::LoadSharedU32x3:
@@ -226,6 +226,7 @@ VALUE_OPCODE(LaneId, U32)
VALUE_OPCODE(Ballot, U32x4, U1)
VALUE_OPCODE(ReadFirstLane, U32, U32, U1)
VALUE_OPCODE(ReadLane, U32, U32, U32)
VALUE_OPCODE(SubgroupUMinU32, U32, U32)
VALUE_OPCODE(WriteLane, U32, U32, U32, U32)
VALUE_OPCODE(Permlane16U32, U32, U32, U32, U32, U1)
VALUE_OPCODE(PermuteU32, U32, U32, U32, U1)
@@ -235,6 +236,7 @@ VALUE_OPCODE(BpermuteU32, U32, U32, U32, U1)
// dense bindings are assigned by resource tracking.
VALUE_OPCODE(GetSrtResource, SrtResource)
VALUE_OPCODE(GetBufferResource, BufferResource, U32, U32, U32, U32)
VALUE_OPCODE(GetBufferResourceIndex, U32, U32)
VALUE_OPCODE(GetAddressResource, AddressResource, U32, U32)
VALUE_OPCODE(GetScratchResource, AddressResource)
VALUE_OPCODE(GetImageResource, ImageResource, U32, U32, U32, U32, U32, U32, U32, U32)
@@ -303,6 +305,8 @@ VALUE_OPCODE(SharedAtomicSMin32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicUMin32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicSMax32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicUMax32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicUMax64, U64, U32, U64, U1)
VALUE_OPCODE(SharedAtomicCmpSwap64, U64, U32, U64, U64, U1)
VALUE_OPCODE(SharedAtomicAnd32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicOr32, U32, U32, U32, U1)
VALUE_OPCODE(SharedAtomicOr64, Void, U32, U64, U1)
@@ -22,6 +22,66 @@ void Replace(Inst& inst, Value value) {
inst.ReplaceUsesWith(value.Resolve());
}
bool HasOneUse(const Inst& inst) {
// Replacement aliases preserve external Values but emit no additional operation.
return std::ranges::count_if(inst.Uses(), [](const Use& use) {
return use.user->GetOpcode() != ValueOpcode::Identity;
}) == 1;
}
class FoldWorklist {
public:
struct Location {
Block::iterator instruction;
uint64_t visited = 0;
bool queued = false;
};
void Register(Block::iterator instruction) {
locations.emplace(&*instruction, Location {instruction});
Queue(&*instruction);
}
void Queue(Inst* inst) {
const auto found = locations.find(inst);
if (found != locations.end() && !found->second.queued) {
found->second.queued = true;
pending.push_back(inst);
}
}
void QueueUsers(const std::vector<Use>& users) {
++generation;
affected.clear();
for (const auto& use: users) affected.push_back(use.user);
// Pattern folds inspect nested operands even when intermediate operations
// stay dynamic. Visit consumers once per change, including cyclic Phi edges.
for (size_t i = 0; i < affected.size(); ++i) {
auto* inst = affected[i];
const auto found = locations.find(inst);
if (found == locations.end() || found->second.visited == generation) continue;
found->second.visited = generation;
Queue(inst);
for (const auto& use: inst->Uses()) affected.push_back(use.user);
}
}
Block::iterator Prepend(Block& block, Block::iterator before, ValueOpcode opcode,
std::initializer_list<Value> args = {}) {
const auto result = block.PrependNewInst(before, opcode, args);
Register(result);
return result;
}
std::unordered_map<Inst*, Location> locations;
std::vector<Inst*> pending;
std::unordered_set<Inst*> lowered_ancillary;
private:
std::vector<Inst*> affected;
uint64_t generation = 0;
};
template <typename Function>
bool FoldU32(Inst& inst, Function function) {
const auto lhs = Arg(inst, 0);
@@ -195,8 +255,42 @@ bool FoldCompositeExtract(Inst& inst, ValueOpcode construct, size_t components)
return false;
}
void FoldZeroComparison(Block& block, Block::iterator instruction, FoldWorklist& worklist) {
auto& inst = *instruction;
Value value;
for (uint32_t arg = 0; arg < 2u; ++arg)
if (Arg(inst, arg) == Value(0u)) value = Arg(inst, arg ^ 1u);
const auto* source = value.TryInstruction();
if (source == nullptr) return;
const bool equal = inst.GetOpcode() == ValueOpcode::IEqual32;
const auto boolean_select = [](const Inst* select) {
return select != nullptr && select->GetOpcode() == ValueOpcode::SelectU32 &&
IsImmediate(Arg(*select, 1), Type::U32) &&
IsImmediate(Arg(*select, 2), Type::U32);
};
if (boolean_select(source)) {
const bool when_true = (Arg(*source, 1).U32() == 0u) == equal;
const bool when_false = (Arg(*source, 2).U32() == 0u) == equal;
if (when_true == when_false) Replace(inst, Value(when_true));
else if (when_true) Replace(inst, Arg(*source, 0));
else Replace(inst, Value(&*worklist.Prepend(block, instruction, ValueOpcode::LogicalNot,
{Arg(*source, 0)})));
} else if (source->GetOpcode() == ValueOpcode::BitwiseOr32 &&
(boolean_select(Arg(*source, 0).TryInstruction()) ||
boolean_select(Arg(*source, 1).TryInstruction()))) {
// SCC encoded as a DWORD needs no select or bitwise operation when only
// its zero predicate is consumed. Keep arbitrary DWORDs as zero comparisons.
const auto lhs = worklist.Prepend(block, instruction, inst.GetOpcode(),
{Arg(*source, 0), Value(0u)});
const auto rhs = worklist.Prepend(block, instruction, inst.GetOpcode(),
{Arg(*source, 1), Value(0u)});
Replace(inst, Value(&*worklist.Prepend(block, instruction,
equal ? ValueOpcode::LogicalAnd : ValueOpcode::LogicalOr, {Value(&*lhs), Value(&*rhs)})));
}
}
void FoldInstruction(Block& block, Block::iterator instruction,
std::unordered_set<Inst*>& lowered_ancillary) {
FoldWorklist& worklist) {
auto& inst = *instruction;
switch (inst.GetOpcode()) {
case ValueOpcode::StoreBufferU32: {
@@ -208,7 +302,7 @@ void FoldInstruction(Block& block, Block::iterator instruction,
case ValueOpcode::SelectU1:
if (!FoldSelect(inst) && IsImmediate(Arg(inst, 2), Type::U1) &&
!Arg(inst, 2).U1()) {
auto result = block.PrependNewInst(instruction, ValueOpcode::LogicalAnd,
auto result = worklist.Prepend(block, instruction, ValueOpcode::LogicalAnd,
{Arg(inst, 0), Arg(inst, 1)});
Replace(inst, Value(&*result));
}
@@ -224,8 +318,8 @@ void FoldInstruction(Block& block, Block::iterator instruction,
case 0u: Replace(inst, Value(false)); break;
case 0x3ffu: Replace(inst, Value(true)); break;
case 3u: {
const auto result = block.PrependNewInst(
instruction, ValueOpcode::FPIsNan32, {Arg(inst, 0)});
const auto result = worklist.Prepend(
block, instruction, ValueOpcode::FPIsNan32, {Arg(inst, 0)});
Replace(inst, Value(&*result));
break;
}
@@ -280,12 +374,12 @@ void FoldInstruction(Block& block, Block::iterator instruction,
if (offset.U32() >= field.start && offset.U32() < field.end &&
count.U32() <= field.end - offset.U32()) {
// Preserve extraction and sign extension while exposing only the used field.
const auto input = block.PrependNewInst(
instruction, ValueOpcode::GetBuiltin,
const auto input = worklist.Prepend(
block, instruction, ValueOpcode::GetBuiltin,
{Value(static_cast<uint32_t>(field.kind)), Value(0u)});
inst.SetArg(0, Value(&*input));
inst.SetArg(1, Value(offset.U32() - field.start));
lowered_ancillary.insert(source);
worklist.lowered_ancillary.insert(source);
return;
}
}
@@ -510,8 +604,20 @@ void FoldInstruction(Block& block, Block::iterator instruction,
});
return;
case ValueOpcode::BitwiseAnd32:
if (!FoldU32(inst, [](uint32_t a, uint32_t b) { return a & b; })) {
ReplaceBinaryIdentity(inst, Type::U32, 0xffffffffu);
if (FoldU32(inst, [](uint32_t a, uint32_t b) { return a & b; }) ||
ReplaceBinaryIdentity(inst, Type::U32, UINT32_MAX)) return;
for (uint32_t arm = 0; arm < 2u; ++arm) {
const auto mask = Arg(inst, arm);
const auto* shifted = Arg(inst, arm ^ 1u).TryInstruction();
if (!IsImmediate(mask, Type::U32) || shifted == nullptr ||
shifted->GetOpcode() != ValueOpcode::ShiftLeftLogical32 || !HasOneUse(*shifted)) continue;
const auto shift = Arg(*shifted, 1);
if (!IsImmediate(shift, Type::U32) || shift.U32() >= 32u) continue;
const auto masked = worklist.Prepend(block, instruction, ValueOpcode::BitwiseAnd32,
{Arg(*shifted, 0), Value(mask.U32() >> shift.U32())});
Replace(inst, Value(&*worklist.Prepend(block, instruction, ValueOpcode::ShiftLeftLogical32,
{Value(&*masked), shift})));
return;
}
return;
case ValueOpcode::BitwiseAnd64:
@@ -577,10 +683,12 @@ void FoldInstruction(Block& block, Block::iterator instruction,
FoldU32(inst, [](uint32_t a, uint32_t b) { return std::max(a, b); });
return;
case ValueOpcode::IEqual32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a == b; });
if (!FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a == b; }))
FoldZeroComparison(block, instruction, worklist);
return;
case ValueOpcode::INotEqual32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a != b; });
if (!FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a != b; }))
FoldZeroComparison(block, instruction, worklist);
return;
case ValueOpcode::ULessThan32:
FoldU32Compare(inst, [](uint32_t a, uint32_t b) { return a < b; });
@@ -642,19 +750,43 @@ void FoldInstruction(Block& block, Block::iterator instruction,
const auto lhs = Arg(inst, 0);
const auto rhs = Arg(inst, 1);
const auto simplify = [&](Value assumption, Value expression) {
const auto* disjunction = expression.TryInstruction();
if (disjunction == nullptr || disjunction->GetOpcode() != ValueOpcode::LogicalOr) {
return false;
const auto* test = expression.TryInstruction();
if (test == nullptr) return false;
const auto replace = [&](Value value) {
inst.SetArg(assumption == lhs ? 1u : 0u, value);
return true;
};
const auto negate = [&](Value value) {
return Value(&*worklist.Prepend(block, instruction, ValueOpcode::LogicalNot, {value}));
};
const bool inverse = test->GetOpcode() == ValueOpcode::LogicalNot;
if (inverse) {
test = Arg(*test, 0).TryInstruction();
if (test == nullptr) return false;
if (test->GetOpcode() == ValueOpcode::LogicalAnd && HasOneUse(*test))
for (uint32_t arg = 0; arg < 2u; ++arg)
if (Arg(*test, arg) == assumption)
return replace(negate(Arg(*test, arg ^ 1u)));
}
for (uint32_t i = 0; i < 2u; ++i) {
const auto* inverse = disjunction->Arg(i).Resolve().TryInstruction();
if (inverse != nullptr && inverse->GetOpcode() == ValueOpcode::LogicalNot &&
inverse->Arg(0).Resolve() == assumption) {
inst.SetArg(assumption == lhs ? 1u : 0u,
disjunction->Arg(i ^ 1u));
return true;
if (test->GetOpcode() == ValueOpcode::IEqual32 ||
test->GetOpcode() == ValueOpcode::INotEqual32) {
for (uint32_t arg = 0; arg < 2u; ++arg) {
if (Arg(*test, arg) != Value(0u)) continue;
const auto original = Arg(*test, arg ^ 1u);
const auto active = ResolveActiveU32(original, assumption);
if (!active.IsEmpty() && active != original) {
const auto comparison = Value(&*worklist.Prepend(block, instruction,
test->GetOpcode(), {active, Value(0u)}));
return replace(inverse ? negate(comparison) : comparison);
}
}
}
if (inverse || test->GetOpcode() != ValueOpcode::LogicalOr) return false;
for (uint32_t arg = 0; arg < 2u; ++arg) {
const auto* negation = Arg(*test, arg).TryInstruction();
if (negation != nullptr && negation->GetOpcode() == ValueOpcode::LogicalNot &&
Arg(*negation, 0) == assumption) return replace(Arg(*test, arg ^ 1u));
}
return false;
};
if (simplify(lhs, rhs) || simplify(rhs, lhs)) return;
@@ -673,6 +805,25 @@ void FoldInstruction(Block& block, Block::iterator instruction,
Replace(inst, lhs.U1() ? lhs : rhs);
} else if (IsImmediate(rhs, Type::U1)) {
Replace(inst, rhs.U1() ? rhs : lhs);
} else {
const auto* left = lhs.TryInstruction();
const auto* right = rhs.TryInstruction();
if (left == nullptr || right == nullptr ||
left->GetOpcode() != ValueOpcode::LogicalAnd ||
right->GetOpcode() != ValueOpcode::LogicalAnd ||
(!HasOneUse(*left) && !HasOneUse(*right))) return;
// Factor a shared execution predicate without retaining more operations.
for (uint32_t a = 0; a < 2u; ++a) {
for (uint32_t b = 0; b < 2u; ++b) {
const auto common = Arg(*left, a);
if (common != Arg(*right, b)) continue;
const auto either = Value(&*worklist.Prepend(block, instruction,
ValueOpcode::LogicalOr, {Arg(*left, a ^ 1u), Arg(*right, b ^ 1u)}));
Replace(inst, Value(&*worklist.Prepend(block, instruction,
ValueOpcode::LogicalAnd, {common, either})));
return;
}
}
}
}
return;
@@ -706,14 +857,13 @@ void FoldInstruction(Block& block, Block::iterator instruction,
}
}
// In wave32, extracting this invocation's bit from a ballot recovers its predicate.
// Keep that identity through scalar EXEC operations and their loop-carried mask Phis.
// Full and empty masks have a constant predicate in either wave size. In wave32,
// ballot projection also survives scalar EXEC operations and loop-carried mask Phis.
class LaneMaskProjection {
public:
explicit LaneMaskProjection(std::unordered_set<Inst*>& lowered_ancillary)
: m_lowered_ancillary(lowered_ancillary) {}
explicit LaneMaskProjection(FoldWorklist& worklist): m_worklist(worklist) {}
void Fold(Inst& inst) {
void Fold(Inst& inst, uint32_t wave_size) {
if (inst.GetOpcode() != ValueOpcode::INotEqual32 || !Immediate(Arg(inst, 1), 0u)) return;
const auto* bit = Arg(inst, 0).TryInstruction();
if (bit == nullptr || bit->GetOpcode() != ValueOpcode::BitwiseAnd32 ||
@@ -725,9 +875,15 @@ public:
!Immediate(Arg(*index, 1), 31u)) return;
const auto* lane = Arg(*index, 0).TryInstruction();
if (lane == nullptr || lane->GetOpcode() != ValueOpcode::LaneId) return;
const auto mask = Arg(*shift, 0);
if (Immediate(mask, 0u) || Immediate(mask, UINT32_MAX)) {
Replace(inst, Value(mask.U32() != 0u));
return;
}
if (wave_size != 32u) return;
m_visited.clear();
m_grounded = false;
if (CanProject(Arg(*shift, 0)) && m_grounded) Replace(inst, Project(Arg(*shift, 0)));
if (CanProject(mask) && m_grounded) Replace(inst, Project(mask));
}
private:
@@ -772,19 +928,18 @@ private:
if (const auto found = m_values.find(source); found != m_values.end()) return found->second.Resolve();
if (const auto predicate = BallotPredicate(*source); !predicate.IsEmpty()) return predicate;
auto* block = source->Parent();
const auto where = std::find_if(block->begin(), block->end(),
[source](const Inst& candidate) { return &candidate == source; });
const auto where = m_worklist.locations.at(source).instruction;
const auto op = source->GetOpcode() == ValueOpcode::BitwiseAnd32 ? ValueOpcode::LogicalAnd
: source->GetOpcode() == ValueOpcode::BitwiseOr32 ? ValueOpcode::LogicalOr
: source->GetOpcode() == ValueOpcode::BitwiseNot32 ? ValueOpcode::LogicalNot
: source->GetOpcode() == ValueOpcode::SelectU32 ? ValueOpcode::SelectU1
: ValueOpcode::Phi;
auto result = op == ValueOpcode::Phi ? block->PrependNewInst(where, op)
auto result = op == ValueOpcode::Phi ? m_worklist.Prepend(*block, where, op)
: op == ValueOpcode::LogicalNot
? block->PrependNewInst(where, op, {Value(false)})
? m_worklist.Prepend(*block, where, op, {Value(false)})
: op == ValueOpcode::SelectU1
? block->PrependNewInst(where, op, {Arg(*source, 0), Value(false), Value(false)})
: block->PrependNewInst(where, op, {Value(false), Value(false)});
? m_worklist.Prepend(*block, where, op, {Arg(*source, 0), Value(false), Value(false)})
: m_worklist.Prepend(*block, where, op, {Value(false), Value(false)});
m_values.emplace(source, Value(&*result));
if (op == ValueOpcode::Phi) result->SetFlags(Type::U1);
for (size_t arg = op == ValueOpcode::SelectU1 ? 1u : 0u; arg < source->NumArgs(); ++arg) {
@@ -792,11 +947,11 @@ private:
if (op == ValueOpcode::Phi) result->AddPhiOperand(source->PhiBlock(arg), projected);
else result->SetArg(arg, projected);
}
FoldInstruction(*block, result, m_lowered_ancillary);
FoldInstruction(*block, result, m_worklist);
return Value(&*result).Resolve();
}
std::unordered_set<Inst*>& m_lowered_ancillary;
FoldWorklist& m_worklist;
std::unordered_set<const Inst*> m_visited;
std::unordered_map<const Inst*, Value> m_values;
bool m_grounded = false;
@@ -805,29 +960,59 @@ private:
} // namespace
void ConstantPropagationPass(const BlockList& blocks, uint32_t wave_size) {
std::unordered_set<Inst*> lowered_ancillary;
LaneMaskProjection mask_projection(lowered_ancillary);
for (auto* block: blocks) {
for (auto inst = block->begin(); inst != block->end(); ++inst) {
if (wave_size == 32u) mask_projection.Fold(*inst);
FoldInstruction(*block, inst, lowered_ancillary);
FoldWorklist worklist;
LaneMaskProjection mask_projection(worklist);
for (auto* block: blocks)
for (auto inst = block->begin(); inst != block->end(); ++inst) worklist.Register(inst);
std::vector<Use> users;
std::vector<Value> operands;
const auto drain = [&] {
for (size_t i = 0; i < worklist.pending.size(); ++i) {
auto* inst = worklist.pending[i];
auto& location = worklist.locations.at(inst);
location.queued = false;
const auto opcode = inst->GetOpcode();
if (opcode == ValueOpcode::Identity || opcode == ValueOpcode::Void) continue;
const auto where = location.instruction;
users.assign(inst->Uses().begin(), inst->Uses().end());
operands.clear();
for (size_t arg = 0; arg < inst->NumArgs(); ++arg) operands.push_back(inst->Arg(arg));
mask_projection.Fold(*inst, wave_size);
FoldInstruction(*inst->Parent(), where, worklist);
bool changed = opcode != inst->GetOpcode() || operands.size() != inst->NumArgs();
for (size_t arg = 0; !changed && arg < operands.size(); ++arg)
changed = operands[arg] != inst->Arg(arg);
if (changed) {
worklist.QueueUsers(users);
worklist.Queue(inst);
}
}
}
worklist.pending.clear();
};
drain();
// Normalize retained PHI/select values only after every supported field read has
// been lowered; direct raw consumers remain unsupported.
for (auto* source: lowered_ancillary) {
const bool retained_only = std::ranges::all_of(source->Uses(), [](const Use& use) {
const auto& user = *use.user;
if (!user.HasUses() && !user.MayHaveSideEffects()) {
return true;
bool normalized;
do {
normalized = false;
for (auto* source: worklist.lowered_ancillary) {
if (source->GetOpcode() == ValueOpcode::Identity) continue;
const bool retained_only = std::ranges::all_of(source->Uses(), [](const Use& use) {
const auto& user = *use.user;
if (!user.HasUses() && !user.MayHaveSideEffects()) {
return true;
}
return user.GetOpcode() == ValueOpcode::Phi ||
(user.GetOpcode() == ValueOpcode::SelectU32 && use.operand == 2u);
});
if (retained_only) {
worklist.QueueUsers(source->Uses());
Replace(*source, Value(0u));
normalized = true;
}
return user.GetOpcode() == ValueOpcode::Phi ||
(user.GetOpcode() == ValueOpcode::SelectU32 && use.operand == 2u);
});
if (retained_only) {
Replace(*source, Value(0u));
}
}
drain();
} while (normalized);
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -1,11 +1,21 @@
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include <algorithm>
namespace Libs::Graphics::ShaderRecompiler::IR {
void RemoveIdentities(const BlockList& blocks) {
for (auto* block: blocks) {
void RemoveIdentities(Program& program) {
// Resolve metadata before deleting its identity nodes; retain only folded native constants.
for (auto& write: program.scalar_writes) {
const auto value = write.value.Resolve();
write.value = value.IsImmediate() && value.GetType() == Type::U32 ? value : Value {};
}
for (auto* block: program.blocks) {
block->condition = block->condition.Resolve();
block->indirect_target = block->indirect_target.Resolve();
}
for (auto* block: program.blocks) {
auto& instructions = block->Instructions();
for (auto inst = instructions.begin(); inst != instructions.end();) {
if (inst->GetOpcode() != ValueOpcode::Identity) {
@@ -4,7 +4,9 @@
namespace Libs::Graphics::ShaderRecompiler::IR {
void RemoveIdentities(const BlockList& blocks);
struct Program;
void RemoveIdentities(Program& program);
void EliminateDeadCode(const BlockList& blocks);
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -99,6 +99,114 @@ Value GetRealValue(PhiMap& phi_map, Value source, uint32_t lane, uint32_t wave_s
return Value(&phi);
}
// A full-EXEC prefix scan with both half-wave endpoint reads is a reduction.
// Fragment subgroups may have no physical invocation at either endpoint.
class UMinReductionMatcher {
public:
explicit UMinReductionMatcher(Block* block): block(block) {}
Value Match(const Inst& minimum) {
const auto* low = Get(minimum.Arg(0), ValueOpcode::ReadLane);
const auto* high = Get(minimum.Arg(1), ValueOpcode::ReadLane);
if (low == nullptr || high == nullptr || low->Arg(0).Resolve() != high->Arg(0).Resolve()) {
return {};
}
const auto lane = [](Value value, uint32_t expected) {
value = value.Resolve();
return value.IsImmediate() && value.GetType() == Type::U32 &&
value.U32() % 64u == expected;
};
if (!((lane(low->Arg(1), 31) && lane(high->Arg(1), 63)) ||
(lane(low->Arg(1), 63) && lane(high->Arg(1), 31)))) {
return {};
}
const auto* joined = Get(Unwrap(low->Arg(0)), ValueOpcode::UMin32);
if (joined == nullptr) return {};
for (size_t index = 0; index < 2; index++) {
auto attempt = *this;
const auto* cross = attempt.Get(attempt.Unwrap(joined->Arg(index)), ValueOpcode::Permlane16U32);
if (cross == nullptr) continue;
const auto flags = cross->Flags<PermlaneFlags>();
auto prefix = attempt.Unwrap(joined->Arg(1 - index));
if (!flags.x16 || flags.fetch_inactive || !flags.bound_control ||
cross->Arg(1).Resolve() != Value(UINT32_MAX) ||
cross->Arg(2).Resolve() != Value(UINT32_MAX) ||
!attempt.FullExec(cross->Arg(3)) || attempt.Unwrap(cross->Arg(0)) != prefix) {
continue;
}
for (const uint32_t shift: {8u, 4u, 2u, 1u}) {
prefix = attempt.MatchRow(prefix, shift);
}
prefix = attempt.Unwrap(prefix);
if (prefix.IsEmpty() || attempt.saved_exec.IsEmpty()) return prefix;
const auto* seed = attempt.Get(prefix, ValueOpcode::SelectU32);
if (seed != nullptr && seed->Arg(2).Resolve() == Value(UINT32_MAX)) {
const auto predicate = seed->Arg(0).Resolve();
const auto* subset = attempt.Get(predicate, ValueOpcode::LogicalAnd);
if (predicate == attempt.saved_exec ||
(subset != nullptr && (subset->Arg(0).Resolve() == attempt.saved_exec ||
subset->Arg(1).Resolve() == attempt.saved_exec))) {
return prefix;
}
}
}
return {};
}
private:
Inst* Get(Value value, ValueOpcode opcode) const {
auto* inst = value.Resolve().TryInstruction();
return inst != nullptr && inst->Parent() == block && inst->GetOpcode() == opcode
? inst : nullptr;
}
bool FullExec(Value predicate) {
predicate = predicate.Resolve();
if (predicate == Value(true)) return true;
const auto* either = Get(predicate, ValueOpcode::LogicalOr);
if (either == nullptr) return false;
for (size_t index = 0; index < 2; index++) {
const auto* negate = Get(either->Arg(index), ValueOpcode::LogicalNot);
if (negate == nullptr || negate->Arg(0).Resolve() != either->Arg(1 - index).Resolve()) continue;
const auto original = negate->Arg(0).Resolve();
if (saved_exec.IsEmpty()) saved_exec = original;
return saved_exec == original;
}
return false;
}
Value Unwrap(Value value) {
while (const auto* select = Get(value, ValueOpcode::SelectU32)) {
if (!FullExec(select->Arg(0))) break;
value = select->Arg(1);
}
return value.Resolve();
}
static bool IsRowShift(const Inst& inst, uint32_t shift) {
const auto flags = inst.Flags<DppMoveFlags>();
return flags.control == 0x110u + shift && flags.row_mask == 0xf &&
flags.bank_mask == 0xf && !flags.fetch_inactive && !flags.bound_control && !flags.dpp8;
}
Value MatchRow(Value value, uint32_t shift) {
const auto* update = Get(Unwrap(value), ValueOpcode::DppUpdateU32);
if (update == nullptr || !IsRowShift(*update, shift) || !FullExec(update->Arg(2))) return {};
const auto* minimum = Get(update->Arg(0), ValueOpcode::UMin32);
if (minimum == nullptr) return {};
for (size_t index = 0; index < 2; index++) {
const auto* move = Get(minimum->Arg(index), ValueOpcode::DppMoveU32);
if (move == nullptr || !IsRowShift(*move, shift) || !FullExec(move->Arg(1))) continue;
const auto previous = Unwrap(minimum->Arg(1 - index));
if (Unwrap(move->Arg(0)) == previous && Unwrap(update->Arg(1)) == previous) return previous;
}
return {};
}
Block* block;
Value saved_exec;
};
} // namespace
ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size) {
@@ -109,6 +217,17 @@ ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size) {
for (auto* block: program.blocks) {
for (auto& inst: *block) {
if (wave_size == 64u && inst.GetOpcode() == ValueOpcode::UMin32) {
const auto seed = UMinReductionMatcher(block).Match(inst);
if (!seed.IsEmpty()) {
const auto position = std::find_if(block->begin(), block->end(),
[&](const Inst& candidate) { return &candidate == &inst; });
auto& reduction = *block->PrependNewInst(position, ValueOpcode::SubgroupUMinU32, {seed});
inst.ReplaceUsesWith(Value(&reduction));
stats.rewritten_reductions++;
}
continue;
}
if (inst.GetOpcode() != ValueOpcode::ReadLane) {
continue;
}
@@ -5,7 +5,8 @@
namespace Libs::Graphics::ShaderRecompiler::IR {
struct ReadLaneStats {
uint32_t rewritten_reads = 0;
uint32_t rewritten_reads = 0;
uint32_t rewritten_reductions = 0;
};
[[nodiscard]] ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size);
File diff suppressed because it is too large. Load diff
@@ -13,6 +13,7 @@ struct ResourceSpecialization {
Prospero::BufferFormat descriptor_format = Prospero::BufferFormat::kInvalid;
uint32_t descriptor_swizzle = DstSel(4, 5, 6, 7);
bool zero_stride_oob = false;
bool scalar_empty = false;
uint32_t indirect_root = BufferResource::NoIndirectBuffer;
uint32_t indirect_mapping_offset = 0;
uint32_t indirect_search_iterations = 0;
@@ -33,8 +34,16 @@ struct ResourceSpecialization {
bool operator==(const Image&) const = default;
};
struct Sampler {
uint32_t indirect_root = SamplerResource::NoIndirectSampler;
uint32_t indirect_mapping_offset = 0;
uint32_t indirect_search_iterations = 0;
bool operator==(const Sampler&) const = default;
};
std::vector<Buffer> buffers;
std::vector<Image> images;
std::vector<Sampler> samplers;
bool operator==(const ResourceSpecialization&) const = default;
};
File diff suppressed because it is too large. Load diff
@@ -6,7 +6,8 @@
namespace Libs::Graphics::ShaderRecompiler::IR {
// Resolves native descriptor sources, plans their scalar reads, and assigns dense resource bindings.
void TrackResources(Program& program, const Decoder::Program& decoded, const CFG::Graph& native_cfg);
void TrackResources(Program& program, const Decoder::Program& decoded, const CFG::Graph& native_cfg,
ShaderStageInputInfo input = {});
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -308,15 +308,15 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
info.uses_lds |= kind == ResourceKind::Lds;
info.uses_gds |= kind == ResourceKind::Gds;
} else if (kind == ResourceKind::Buffer || kind == ResourceKind::ScalarBuffer) {
if (memory.resource >= info.buffers.size() || memory.resource >= ShaderInfo::MaxBuffers) {
if (memory.resource >= info.buffers.size()) {
return Fail("buffer operation has invalid resource metadata");
}
info.live_buffers |= uint64_t{1} << memory.resource;
info.buffers[memory.resource].live = true;
for (const auto child: info.buffers[memory.resource].indirect_resources) {
if (child >= info.buffers.size()) {
return Fail("indirect buffer operation has invalid resource metadata");
}
info.live_buffers |= uint64_t{1} << child;
info.buffers[child].live = true;
}
}
}
@@ -343,9 +343,16 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
if (memory.resource >= info.buffers.size()) {
return Fail("buffer operation has invalid resource metadata");
}
const auto bits = StorageBufferElementBits(program, memory);
info.buffer_u8 |= bits == 8u;
info.buffer_u16 |= bits == 16u;
const auto collect_element = [&](uint32_t resource) {
auto selected = memory;
selected.resource = resource;
const auto bits = StorageBufferElementBits(program, selected);
info.buffer_u8 |= bits == 8u;
info.buffer_u16 |= bits == 16u;
};
collect_element(memory.resource);
for (const auto child: info.buffers[memory.resource].indirect_resources)
collect_element(child);
if ((info.buffers[memory.resource].packed_stride & (1u << 20u)) != 0u) {
if (program.stage != ShaderType::Compute) {
return Fail("buffer ADD_TID is only valid for compute shaders");
@@ -503,10 +510,11 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
case ValueOpcode::StoreCompletion: info.subgroup_barrier = true; break;
case ValueOpcode::BvhIntersect: info.bvh = true; break;
case ValueOpcode::ConditionRef:
info.subgroup_ballot |=
info.subgroup_vote |=
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
break;
case ValueOpcode::Ballot: info.subgroup_ballot = true; break;
case ValueOpcode::SubgroupUMinU32: info.subgroup_arithmetic = true; break;
case ValueOpcode::DppMoveU32:
case ValueOpcode::ReadFirstLane:
case ValueOpcode::ReadLane: {
@@ -558,16 +566,14 @@ void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info) {
}
ValidateOptions(program, input_info);
auto& info = program.info;
if (info.buffers.size() > ShaderInfo::MaxBuffers) {
return Fail("buffer resource limit exceeded");
}
info.inputs.clear();
info.outputs.clear();
info.user_data_registers.clear();
info.live_buffers = 0;
for (auto& buffer: info.buffers) buffer.live = false;
info.has_bitwise_xor = info.uses_lds = info.uses_gds = info.uses_flattened_srt =
info.uses_dispatch_threads = info.uses_swizzle = false;
info.bvh = info.subgroup_ballot = info.subgroup_barrier = info.subgroup_shuffle =
info.bvh = info.subgroup_ballot = info.subgroup_vote = info.subgroup_barrier =
info.subgroup_shuffle = info.subgroup_arithmetic =
info.subgroup_local_invocation_id = info.compute_derivatives = info.image_gather_extended =
info.function_lds = info.function_scratch = info.pixel_valid_mask = info.buffer_int64_atomics =
info.buffer_u8 = info.buffer_u16 = info.shared_int64_atomics = info.coherent_buffers = info.float64 = false;
@@ -593,6 +599,15 @@ void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info) {
case ShaderType::Mesh: break;
case ShaderType::Pixel:
CollectPixelInputs(input_info.pixel, info, inputs.per_vertex, inputs.components);
if (input_info.pixel->object_primitive_id) {
if (std::ranges::any_of(info.outputs, [](const StageOutput& output) {
return output.kind == StageOutputKind::Mrt && output.location == 7u;
})) {
return Fail("explicit MRT7 export overlaps automatic object/primitive ID");
}
AddBuiltinInput(info, StageInputKind::PrimitiveId);
AddOutput(info, StageOutputKind::ObjectPrimitiveId, 0, 7, "out_object_primitive_id");
}
break;
case ShaderType::Compute: CollectComputeInputs(input_info.compute, info); break;
default: return Fail("unsupported shader stage for info collection");
@@ -141,6 +141,7 @@ bool IsRuntimeUniformOp(ValueOpcode op) {
case ValueOpcode::IMul64:
case ValueOpcode::UMulHi:
case ValueOpcode::UMin32:
case ValueOpcode::UMax32:
case ValueOpcode::ShiftLeftLogical32:
case ValueOpcode::ShiftLeftLogical64:
case ValueOpcode::ShiftRightLogical32:
@@ -177,8 +178,9 @@ bool IsRuntimeUniformOp(ValueOpcode op) {
class RuntimeValidator {
public:
explicit RuntimeValidator(const ResourcePlan& program, RuntimeValueType type)
: m_program(program), m_type(type) {}
explicit RuntimeValidator(const ResourcePlan& program, RuntimeValueType type,
bool wave_uniform = false)
: m_program(program), m_type(type), m_wave_uniform(wave_uniform) {}
bool Run(Value value) { return Validate(value); }
@@ -277,6 +279,12 @@ private:
}
return finish(true);
}
if (m_wave_uniform && op == ValueOpcode::GetBuiltin) {
const auto axis = inst->NumArgs() == 2u ? inst->Arg(1).Resolve() : Value {};
return finish(m_program.stage == ShaderType::Compute && inst->NumArgs() == 2u &&
inst->Arg(0) == Value(static_cast<uint32_t>(StageInputKind::WorkgroupId)) &&
axis.IsImmediate() && axis.GetType() == Type::U32 && axis.U32() < 3u);
}
if (op == ValueOpcode::Phi) {
if (m_type != RuntimeValueType::Any && !ValidateArguments(*inst, false)) {
return finish(false);
@@ -356,6 +364,7 @@ private:
const ResourcePlan& m_program;
RuntimeValueType m_type;
bool m_wave_uniform;
Value m_active_mask;
std::unordered_set<const Inst*> m_visiting;
std::unordered_set<const Inst*> m_validated_dependencies;
@@ -490,12 +499,24 @@ void CompileSrtWalker(ResourcePlan& program) {
}
SrtWalker::SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
SrtWalker* clean_evaluator, Value active_mask)
SrtWalker* clean_evaluator)
: m_program(program), m_runtime(runtime), m_clean_evaluator(clean_evaluator),
m_active_mask(active_mask.Resolve()),
m_context(AcquireContext(program)) {}
m_session(this),
m_read_policy(clean_evaluator != nullptr ? 2u : 0u), m_context(AcquireContext()) {}
SrtWalker::~SrtWalker() { --m_program.evaluation_depth; }
SrtWalker::SrtWalker(SrtWalker& parent, Value active_mask, bool clean,
SrtWalker* clean_evaluator)
: m_program(parent.m_program), m_runtime(clean ? CleanRuntime(parent.m_runtime) : parent.m_runtime),
m_clean_evaluator(clean_evaluator), m_active_mask(active_mask.Resolve()),
m_reachable_blocks(parent.m_reachable_blocks), m_session(parent.m_session),
m_read_policy((parent.m_read_policy & 1u) | (clean ? 1u : 0u) |
(clean_evaluator != nullptr ? 2u : 0u)), m_context(AcquireContext()) {}
SrtWalker::~SrtWalker() {
if (m_session != this) return;
for (auto& context: m_program.evaluation_contexts)
if (context.session == this) context.session = nullptr;
}
bool SrtWalker::Evaluate(Value value, uint32_t& result) {
uint64_t wide = 0;
@@ -506,15 +527,29 @@ bool SrtWalker::Evaluate(Value value, uint32_t& result) {
return true;
}
ResourcePlan::EvaluationContext& SrtWalker::AcquireContext(const ResourcePlan& program) {
if (program.evaluation_depth == program.evaluation_contexts.size()) {
program.evaluation_contexts.emplace_back();
ResourcePlan::EvaluationContext& SrtWalker::AcquireContext() {
ResourcePlan::EvaluationContext* unused = nullptr;
for (auto& context: m_program.evaluation_contexts) {
if (context.session == m_session && context.active_mask == m_active_mask &&
context.read_policy == m_read_policy) return context;
if (context.session == nullptr && unused == nullptr) unused = &context;
}
auto& context = program.evaluation_contexts[program.evaluation_depth++];
context.generation += 2;
auto& context = unused != nullptr ? *unused : m_program.evaluation_contexts.emplace_back();
context.session = m_session;
context.active_mask = m_active_mask;
context.read_policy = m_read_policy;
context.generation += 4;
return context;
}
void SrtWalker::ResetUnknowns() {
for (auto& context: m_program.evaluation_contexts) {
if (context.session != m_session) continue;
for (auto& entry: context.values)
if (entry.generation == (context.generation | 2u)) entry.generation = 0;
}
}
float SrtWalker::Float32(uint64_t bits) {
return std::bit_cast<float>(static_cast<uint32_t>(bits));
}
@@ -552,8 +587,10 @@ bool SrtWalker::EvaluateWide(Value value, uint64_t& result) {
result = m_context.values[index].value;
return true;
}
// The low generation bit marks an instruction that is still being evaluated.
if (m_context.values[index].generation == (m_context.generation | 1u)) {
// Visiting and completed-unknown entries both stop repeated evaluation in this
// context. Only completed unknowns are retried when reachability becomes tighter.
if (m_context.values[index].generation == (m_context.generation | 1u) ||
m_context.values[index].generation == (m_context.generation | 2u)) {
return false;
}
m_context.values[index].generation = m_context.generation | 1u;
@@ -562,7 +599,7 @@ bool SrtWalker::EvaluateWide(Value value, uint64_t& result) {
// Recursive evaluation may grow the dense memo vector.
auto& memo = m_context.values[index];
if (!evaluated) {
memo.generation = 0;
memo.generation = m_context.generation | 2u;
return false;
}
memo.value = out;
@@ -576,6 +613,19 @@ bool SrtWalker::Arg(const Inst& inst, size_t index, uint64_t& result) {
}
bool SrtWalker::EvaluatePhi(const Inst& inst, uint64_t& result) {
if (const auto control = m_program.control_phi_predecessors.find(&inst);
control != m_program.control_phi_predecessors.end()) {
bool found = false;
for (size_t index = 0; index < inst.NumArgs(); ++index) {
if (m_reachable_blocks != nullptr &&
!m_reachable_blocks->at(control->second[index])) continue;
uint64_t incoming = 0;
if (!Arg(inst, index, incoming) || (found && incoming != result)) return false;
result = incoming;
found = true;
}
return found;
}
const auto value = ResolveInvariantPhi(m_program, Value(const_cast<Inst*>(&inst)));
return !value.IsEmpty() && EvaluateWide(value, result);
}
@@ -673,7 +723,8 @@ bool SrtWalker::EvaluateRawRead(const Inst& inst, uint64_t& result) {
? static_cast<uint64_t>(static_cast<uint32_t>(records))
: static_cast<uint64_t>(stride) * static_cast<uint32_t>(records);
if (byte_offset > size || size - byte_offset < sizeof(uint32_t)) {
return false;
result = 0u;
return true;
}
address = (base & ~uint64_t {3}) + byte_offset;
} else {
@@ -721,9 +772,8 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
if (!m_active_mask.IsEmpty() && inst.Arg(1).Resolve() == m_active_mask) {
return EvaluateWide(inst.Arg(0), result);
}
const auto clean_runtime = CleanRuntime(m_runtime);
SrtWalker clean_active(m_program, clean_runtime, nullptr, inst.Arg(1));
SrtWalker active(m_program, m_runtime, &clean_active, inst.Arg(1));
SrtWalker clean_active(*this, inst.Arg(1), true);
SrtWalker active(*this, inst.Arg(1), false, &clean_active);
return active.EvaluateWide(inst.Arg(0), result);
}
case ValueOpcode::BitCastU32F32:
@@ -793,8 +843,11 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
}
return false;
case ValueOpcode::UMin32:
case ValueOpcode::UMax32:
if (binary()) {
result = std::min(static_cast<uint32_t>(a), static_cast<uint32_t>(b));
result = inst.GetOpcode() == ValueOpcode::UMin32
? std::min(static_cast<uint32_t>(a), static_cast<uint32_t>(b))
: std::max(static_cast<uint32_t>(a), static_cast<uint32_t>(b));
return true;
}
return false;
@@ -1007,7 +1060,13 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
}
return false;
case ValueOpcode::ULessThanEqual32:
if (binary()) {
// A zero lower bound accepts every U32, including a varying lane value.
if (!Arg(inst, 0, a)) return false;
if (a == 0u) {
result = 1u;
return true;
}
if (Arg(inst, 1, b)) {
result = static_cast<uint32_t>(a) <= static_cast<uint32_t>(b);
return true;
}
@@ -1031,9 +1090,21 @@ bool SrtWalker::EvaluateInst(const Inst& inst, uint64_t& result) {
result = 0u;
return true;
}
if (!Arg(inst, 1, b) || (b != 0u && !left)) return false;
result = b != 0u;
return true;
if (Arg(inst, 1, b) && (b == 0u || left)) {
result = b != 0u;
return true;
}
if (!left) {
// Only lanes satisfying the left EXEC factor can make the conjunction
// true. Use a separate memo so this assumption cannot escape its RHS.
SrtWalker active(m_clean_evaluator != nullptr ? *m_clean_evaluator : *this,
inst.Arg(0));
if (active.EvaluateWide(inst.Arg(1), b) && b == 0u) {
result = 0u;
return true;
}
}
return false;
}
case ValueOpcode::LogicalOr: {
const bool left = Arg(inst, 0, a);
@@ -1112,35 +1183,54 @@ bool SrtWalker::RefreshFlatBuffer(std::vector<uint32_t>& flat) {
}
return true;
}
auto& visited = m_program.visited_blocks;
auto& reachable = m_program.reachable_blocks;
auto& pending = m_program.pending_blocks;
visited.assign(m_program.control_flow.size(), 1u);
ResetUnknowns();
m_reachable_blocks = &visited;
if (m_clean_evaluator != nullptr) {
m_clean_evaluator->ResetUnknowns();
m_clean_evaluator->m_reachable_blocks = &visited;
}
for (;;) {
reachable.assign(m_program.control_flow.size(), 0u);
pending.clear();
pending.push_back(0u);
while (!pending.empty()) {
const auto index = pending.back();
pending.pop_back();
if (reachable.at(index)) continue;
reachable[index] = 1u;
const auto& block = m_program.control_flow[index];
uint32_t condition = 0;
auto& predicate = m_clean_evaluator != nullptr ? *m_clean_evaluator : *this;
if (!block.condition.IsEmpty() &&
(m_runtime.read_specialization_memory != nullptr || !m_program.capture_specialization_reads) &&
predicate.Evaluate(block.condition, condition)) {
pending.push_back(block.successors[condition != 0u ? 0u : 1u]);
} else {
pending.insert(pending.end(), block.successors.begin(), block.successors.end());
}
}
if (reachable == visited) break;
visited.swap(reachable);
if (m_program.control_phi_predecessors.empty()) break;
// Only unknown values need another evaluation: successful Phi facts remain
// true as their possible predecessors shrink. Keep uniform-read memos stable.
ResetUnknowns();
if (m_clean_evaluator != nullptr) m_clean_evaluator->ResetUnknowns();
}
flat.assign(m_program.srt_reads.size(), 0u);
active.assign(m_program.descriptor_sources.size(), 1u);
for (const auto& block: m_program.control_flow) {
for (const auto& block: m_program.control_flow)
for (const auto source: block.sources) active.at(source) = 0u;
}
auto& visited = m_program.visited_blocks;
auto& pending = m_program.pending_blocks;
visited.assign(m_program.control_flow.size(), 0u);
pending.clear();
pending.push_back(0u);
while (!pending.empty()) {
const auto index = pending.back();
pending.pop_back();
if (visited.at(index)) continue;
visited[index] = 1u;
for (uint32_t index = 0; index < visited.size(); ++index) {
if (!visited[index]) continue;
const auto& block = m_program.control_flow[index];
for (const auto source: block.sources) active[source] = 1u;
for (const auto slot: block.srt_reads) {
for (const auto slot: block.srt_reads)
if (!refresh(slot)) return false;
}
uint32_t condition = 0;
auto& predicate = m_clean_evaluator != nullptr ? *m_clean_evaluator : *this;
if (!block.condition.IsEmpty() &&
(m_runtime.read_specialization_memory != nullptr || !m_program.capture_specialization_reads) &&
predicate.Evaluate(block.condition, condition)) {
pending.push_back(block.successors[condition != 0u ? 0u : 1u]);
} else {
pending.insert(pending.end(), block.successors.begin(), block.successors.end());
}
}
return true;
}
@@ -1149,5 +1239,9 @@ bool ValidateRuntimeValue(const ResourcePlan& program, Value value, RuntimeValue
return RuntimeValidator(program, type).Run(value);
}
bool ValidateWaveUniformValue(const ResourcePlan& program, Value value) {
return RuntimeValidator(program, RuntimeValueType::Any, true).Run(value);
}
} // namespace Libs::Graphics::ShaderRecompiler::IR
@@ -3,6 +3,7 @@
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include <algorithm>
#include <span>
namespace Libs::Graphics::ShaderRecompiler::IR {
@@ -18,6 +19,7 @@ struct SrtRuntime {
void* userdata = nullptr;
SrtMemoryReader read_specialization_memory = nullptr;
std::span<const uint32_t> workgroup_counts;
uint64_t indirect_args_address = 0;
};
class SrtReadCapture {
@@ -45,15 +47,60 @@ enum class RuntimeValueType { Any, Integer, ImmutableInteger };
bool ValidateRuntimeValue(const ResourcePlan& program, Value value,
RuntimeValueType type = RuntimeValueType::Any);
// Uniform across a compute wave, including values derived from its workgroup ID.
// This does not imply that the host can evaluate the value.
bool ValidateWaveUniformValue(const ResourcePlan& program, Value value);
void CompileSrtWalker(ResourcePlan& program);
// Uses the strict reader for values that affect shader specialization.
SrtRuntime CleanRuntime(SrtRuntime runtime);
// Nonnegative affine coefficients: element zero is constant, remaining elements are
// caller-selected leaves. Runtime range proofs must additionally exclude U32 wrap.
template <size_t Size>
std::optional<std::array<uint64_t, Size>> AffineIndex(
Value value, const auto& resolve, const auto& leaf, uint32_t depth = 0) {
value = resolve(value);
if (depth > 32u || value.GetType() != Type::U32) return {};
std::array<uint64_t, Size> result {};
if (value.IsImmediate()) {
result[0] = value.U32();
return result;
}
if (const auto index = leaf(value)) {
result[*index] = 1u;
return result;
}
const auto* inst = value.TryInstruction();
if (inst == nullptr) return {};
const auto op = inst->GetOpcode();
if (op != ValueOpcode::IAdd32 && op != ValueOpcode::IMul32 &&
op != ValueOpcode::ShiftLeftLogical32) return {};
auto left = AffineIndex<Size>(inst->Arg(0), resolve, leaf, depth + 1u);
auto right = AffineIndex<Size>(inst->Arg(1), resolve, leaf, depth + 1u);
if (!left || !right) return {};
const auto constant = [](const auto& coefficients) {
return std::ranges::all_of(std::span(coefficients).subspan(1u),
[](uint64_t coefficient) { return coefficient == 0u; });
};
if (op == ValueOpcode::IMul32 && !constant(*right)) std::swap(left, right);
if (op != ValueOpcode::IAdd32 && !constant(*right)) return {};
if (op == ValueOpcode::ShiftLeftLogical32) {
if ((*right)[0] >= 32u) return {};
(*right)[0] = uint64_t {1} << (*right)[0];
}
for (size_t index = 0; index < Size; ++index) {
(*left)[index] = op == ValueOpcode::IAdd32
? (*left)[index] + (*right)[index] : (*left)[index] * (*right)[0];
if ((*left)[index] > UINT32_MAX) return {};
}
return left;
}
// One memoized evaluation session shared by the entire shader resource refresh.
class SrtWalker {
public:
SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
SrtWalker* clean_evaluator = nullptr, Value active_mask = {});
SrtWalker* clean_evaluator = nullptr);
~SrtWalker();
SrtWalker(const SrtWalker&) = delete;
SrtWalker& operator=(const SrtWalker&) = delete;
@@ -64,7 +111,10 @@ public:
bool RefreshFlatBuffer(std::vector<uint32_t>& flat);
private:
static ResourcePlan::EvaluationContext& AcquireContext(const ResourcePlan& program);
SrtWalker(SrtWalker& parent, Value active_mask, bool clean = false,
SrtWalker* clean_evaluator = nullptr);
ResourcePlan::EvaluationContext& AcquireContext();
void ResetUnknowns();
static float Float32(uint64_t bits);
bool EvaluateWide(Value value, uint64_t& result);
bool Arg(const Inst& inst, size_t index, uint64_t& result);
@@ -77,6 +127,9 @@ private:
SrtRuntime m_runtime;
SrtWalker* m_clean_evaluator = nullptr;
Value m_active_mask;
const std::vector<uint8_t>* m_reachable_blocks = nullptr;
const SrtWalker* m_session;
uint8_t m_read_policy;
ResourcePlan::EvaluationContext& m_context;
};
+4 -201
View File
@@ -18,7 +18,6 @@
#include "libs/errno.h"
#include <algorithm>
#include <atomic>
#include <bit>
#include <cstdio>
#include <memory>
@@ -95,206 +94,6 @@ static ShaderParams GetShaderParams(uint64_t shader_addr, uint64_t hash,
return params;
}
#if 0
// Kept as disabled debugging guards for investigating unusual stage register state.
static void vs_check(const HW::VertexShaderInfo& vs, const HW::ShaderRegisters& sh) {
const auto is_zero_or_wave64_subgroup = [](uint32_t value) {
return value == 0 || value <= 0x40;
};
const auto is_known_gs_out_prim_type = [](uint32_t value) {
switch (static_cast<Prospero::GsOutputPrimitiveType>(value)) {
case Prospero::GsOutputPrimitiveType::kPoints:
case Prospero::GsOutputPrimitiveType::kLines:
case Prospero::GsOutputPrimitiveType::kTriangles:
case Prospero::GsOutputPrimitiveType::k2dRectangle:
case Prospero::GsOutputPrimitiveType::kRectList: return true;
}
return false;
};
const bool ps5_ngg_passthrough_triangle_path =
vs.es_regs.data_addr != 0 && vs.gs_regs.data_addr == vs.es_regs.data_addr &&
sh.m_geNggSubgrpCntl == 0x00000001 && sh.m_vgtGsMaxVertOut == 0x00000003 &&
sh.m_vgtGsOutPrimType == 0x00000002 && sh.m_geMaxOutputPerSubgroup <= 0x000000c0;
if (vs.es_regs.data_addr != 0 || vs.gs_regs.data_addr != 0) {
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.priority != 0);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.float_mode != 192);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.dx10_clamp != true);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.debug_mode != false);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.ieee_mode != false);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.cu_group_enable != false);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.require_forward_progress != false);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.threadgroup_configuration != false);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.gs_vgpr_component_count != 3);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.fp16_overflow != false);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc2.scratch_en != false);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc2.offchip_lds != false);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc2.es_vgpr_component_count != 3);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc2.lds_size != 0);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc2.shared_vgprs != 0);
}
for (uint32_t value = sh.m_spiShaderPosFormat; value != 0; value >>= 4u) {
EXIT_NOT_IMPLEMENTED((value & 0xfu) != 0 && (value & 0xfu) != 0x4u);
}
if (sh.m_paClVsOutCntl != 0x00000000) {
static bool logged = false;
if (!logged) {
LOGF("\t temporary: accepting PA_CL_VS_OUT_CNTL = 0x%08" PRIx32 "\n",
sh.m_paClVsOutCntl);
logged = true;
}
}
EXIT_NOT_IMPLEMENTED(sh.m_spiShaderIdxFormat != 0x00000000 &&
sh.m_spiShaderIdxFormat != 0x00000001);
EXIT_NOT_IMPLEMENTED(sh.m_geNggSubgrpCntl != 0x00000000 && sh.m_geNggSubgrpCntl != 0x00000001);
EXIT_NOT_IMPLEMENTED(sh.m_vgtGsInstanceCnt != 0x00000000);
EXIT_NOT_IMPLEMENTED(!is_zero_or_wave64_subgroup(sh.GetEsVertsPerSubgrp()));
EXIT_NOT_IMPLEMENTED(!is_zero_or_wave64_subgroup(sh.GetGsPrimsPerSubgrp()));
EXIT_NOT_IMPLEMENTED(!is_zero_or_wave64_subgroup(sh.GetGsInstPrimsInSubgrp()));
EXIT_NOT_IMPLEMENTED(!is_zero_or_wave64_subgroup(sh.m_geMaxOutputPerSubgroup) &&
!ps5_ngg_passthrough_triangle_path);
EXIT_NOT_IMPLEMENTED(sh.m_vgtEsgsRingItemsize != 0x00000000 &&
sh.m_vgtEsgsRingItemsize != 0x00000004);
EXIT_NOT_IMPLEMENTED(sh.m_vgtGsMaxVertOut != 0x00000000 && !ps5_ngg_passthrough_triangle_path);
EXIT_NOT_IMPLEMENTED(!is_known_gs_out_prim_type(sh.m_vgtGsOutPrimType));
}
static void ps_check(const HW::PsStageRegisters& ps, const HW::ShaderRegisters& sh) {
if (sh.target_output_mode[0] != 0 && sh.target_output_mode[0] != 2 &&
sh.target_output_mode[0] != 4 && sh.target_output_mode[0] != 5 &&
sh.target_output_mode[0] != 7 && sh.target_output_mode[0] != 9) {
EXIT("Not implemented (sh.target_output_mode[0] != 0 && sh.target_output_mode[0] != 2 && "
"sh.target_output_mode[0] != 4 && sh.target_output_mode[0] != 5 && "
"sh.target_output_mode[0] != 7 && sh.target_output_mode[0] != 9)\n");
}
EXIT_NOT_IMPLEMENTED(sh.db_shader_control.conservative_z_export_value != 0x00000000);
EXIT_NOT_IMPLEMENTED(sh.db_shader_control.shader_z_behavior != 0x00000001 &&
sh.db_shader_control.shader_z_behavior != 0x00000000);
// EXIT_NOT_IMPLEMENTED(ps.shader_kill_enable != false);
// EXIT_NOT_IMPLEMENTED(ps.shader_execute_on_noop != false);
// EXIT_NOT_IMPLEMENTED(ps.m_spiShaderPgmRsrc1Ps != 0x002c0000);
// EXIT_NOT_IMPLEMENTED(ps.m_spiShaderPgmRsrc2Ps != 0x00000000);
// EXIT_NOT_IMPLEMENTED(ps.vgprs != 0x00 && ps.vgprs != 0x01);
EXIT_NOT_IMPLEMENTED(ps.rsrc1.priority != 0);
EXIT_NOT_IMPLEMENTED(ps.rsrc1.float_mode != 192);
EXIT_NOT_IMPLEMENTED(ps.rsrc1.dx10_clamp != true);
EXIT_NOT_IMPLEMENTED(ps.rsrc1.debug_mode != false);
EXIT_NOT_IMPLEMENTED(ps.rsrc1.ieee_mode != false);
EXIT_NOT_IMPLEMENTED(ps.rsrc1.cu_group_disable != false);
EXIT_NOT_IMPLEMENTED(ps.rsrc1.require_forward_progress != false);
EXIT_NOT_IMPLEMENTED(ps.rsrc1.fp16_overflow != false);
EXIT_NOT_IMPLEMENTED(ps.rsrc2.scratch_en != false);
// EXIT_NOT_IMPLEMENTED(ps.user_sgpr != 0 && ps.user_sgpr != 4 && ps.user_sgpr != 12);
EXIT_NOT_IMPLEMENTED(ps.rsrc2.wave_cnt_en != false);
if (ps.rsrc2.extra_lds_size != 0) {
static std::atomic_uint log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("\t PS extra LDS reservation = 0x%02" PRIx8 ", continuing\n",
ps.rsrc2.extra_lds_size);
}
}
EXIT_NOT_IMPLEMENTED(ps.rsrc2.raster_ordered_shading != 0);
EXIT_NOT_IMPLEMENTED(ps.rsrc2.shared_vgprs != 0);
if (sh.shader_z_format != 0x00000000 && sh.shader_z_format != 0x00000001 &&
!sh.db_shader_control.shader_z_export_enable) {
static std::atomic_uint log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("\t shader_z_format = 0x%08" PRIx32
" with z export disabled, ignoring depth export format\n",
sh.shader_z_format);
}
}
EXIT_NOT_IMPLEMENTED(sh.db_shader_control.shader_z_export_enable &&
sh.shader_z_format != 0x00000000 && sh.shader_z_format != 0x00000001);
constexpr uint32_t ps_input_linear_center = 0x00000020u;
constexpr uint32_t ps_input_pos_w = 0x00000800u;
constexpr uint32_t ps_input_front_face = 0x00001000u;
constexpr uint32_t supported_ps_input_bits =
0x00000702u | ps_input_linear_center | ps_input_pos_w | ps_input_front_face;
EXIT_NOT_IMPLEMENTED((sh.ps_input_ena & ~supported_ps_input_bits) != 0);
EXIT_NOT_IMPLEMENTED((sh.ps_input_addr & ~supported_ps_input_bits) != 0);
EXIT_NOT_IMPLEMENTED(sh.ps_input_ena != sh.ps_input_addr);
// EXIT_NOT_IMPLEMENTED(ps.m_spiPsInControl != 0x00000000);
constexpr uint32_t baryc_persp_mask =
0x00000003u | 0x00000030u | 0x00000300u | 0x00003000u;
constexpr uint32_t baryc_linear_mask = 0x00030000u | 0x00300000u | 0x03000000u;
constexpr uint32_t baryc_known_mask = baryc_persp_mask | baryc_linear_mask;
EXIT_NOT_IMPLEMENTED((sh.baryc_cntl & ~baryc_known_mask) != 0);
EXIT_NOT_IMPLEMENTED((sh.baryc_cntl & baryc_persp_mask) != 0);
if ((sh.ps_input_ena & ps_input_linear_center) == 0 && (sh.baryc_cntl & baryc_linear_mask) != 0) {
static std::atomic_uint log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("\t ignoring inactive linear SPI_BARYC_CNTL bits: 0x%08" PRIx32 "\n",
sh.baryc_cntl & baryc_linear_mask);
}
} else {
EXIT_NOT_IMPLEMENTED((sh.baryc_cntl & baryc_linear_mask) != 0x00000000 &&
(sh.baryc_cntl & baryc_linear_mask) != 0x01000000);
}
if ((sh.m_cbShaderMask & 0x0000000f) != 0x0000000f) {
static bool logged = false;
if (!logged) {
LOGF("\t temporary: accepting partial CB_SHADER_MASK = 0x%08" PRIx32 "\n",
sh.m_cbShaderMask);
logged = true;
}
}
if ((sh.m_cbShaderMask & ~0x0000000fu) != 0) {
static bool logged = false;
if (!logged) {
LOGF("\t temporary: ignoring extra CB_SHADER_MASK MRT bits: 0x%08" PRIx32 "\n",
sh.m_cbShaderMask);
logged = true;
}
}
if (sh.db_shader_control.other_bits != 0x00000000) {
static std::atomic_uint log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("\t temporary: ignoring unsupported DB_SHADER_CONTROL bits 0x%08" PRIx32 "\n",
sh.db_shader_control.other_bits);
}
}
EXIT_NOT_IMPLEMENTED(sh.m_paScShaderControl != 0x00000000);
}
static void cs_check(const HW::CsStageRegisters& cs, const HW::ShaderRegisters& /*sh*/) {
// EXIT_NOT_IMPLEMENTED(cs.num_thread_x != 0x00000040);
// EXIT_NOT_IMPLEMENTED(cs.num_thread_y != 0x00000001);
// EXIT_NOT_IMPLEMENTED(cs.num_thread_z != 0x00000001);
// EXIT_NOT_IMPLEMENTED(cs.vgprs != 0x00 && cs.vgprs != 0x01);
EXIT_NOT_IMPLEMENTED(cs.priority != 0x00);
EXIT_NOT_IMPLEMENTED(cs.debug_mode != false);
EXIT_NOT_IMPLEMENTED(cs.require_forward_progress != false);
EXIT_NOT_IMPLEMENTED(cs.shared_vgprs != 0x00);
EXIT_NOT_IMPLEMENTED(cs.scratch_en != 0x00);
// EXIT_NOT_IMPLEMENTED(cs.user_sgpr != 0x0c);
if (cs.tgid_x_en == 0x00) {
static bool logged = false;
if (!logged) {
LOGF("\t temporary: compute shader has TGID X disabled\n");
logged = true;
}
} else {
EXIT_NOT_IMPLEMENTED(cs.tgid_x_en != 0x01);
}
// EXIT_NOT_IMPLEMENTED(cs.tgid_y_en != 0x00);
// EXIT_NOT_IMPLEMENTED(cs.tgid_z_en != 0x00);
EXIT_NOT_IMPLEMENTED(cs.tg_size_en != 0x00);
EXIT_NOT_IMPLEMENTED(cs.tidig_comp_cnt > 2);
// EXIT_NOT_IMPLEMENTED(cs.m_computePgmRsrc1 != 0x002c0040);
// EXIT_NOT_IMPLEMENTED(cs.m_computePgmRsrc2 != 0x00000098);
// EXIT_NOT_IMPLEMENTED(cs.m_computeNumThreadX != 0x00000040);
// EXIT_NOT_IMPLEMENTED(cs.m_computeNumThreadY != 0x00000001);
// EXIT_NOT_IMPLEMENTED(cs.m_computeNumThreadZ != 0x00000001);
}
#endif
static void ShaderDetectBuffers(ShaderVertexInputInfo& info) {
KYTY_PROFILER_FUNCTION();
@@ -578,11 +377,13 @@ static void ShaderGetStaticInputInfoCS(const HW::ComputeShaderInfo& regs,
const bool dispatch_thread_dimensions = info.dispatch_thread_dimensions;
const auto host_subgroup_size = info.host_subgroup_size;
const auto workgroup_counts = std::to_array(info.workgroup_counts);
const auto indirect_args_address = info.indirect_args_address;
info = {};
info.async_compute = async_compute;
info.dispatch_thread_dimensions = dispatch_thread_dimensions;
info.host_subgroup_size = host_subgroup_size;
std::ranges::copy(workgroup_counts, info.workgroup_counts);
info.indirect_args_address = indirect_args_address;
info.threads_num[0] = regs.cs_regs.num_thread_x;
info.threads_num[1] = regs.cs_regs.num_thread_y;
info.threads_num[2] = regs.cs_regs.num_thread_z;
@@ -671,8 +472,10 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
key.push_back(static_cast<uint32_t>(info.ps_sample_mask_export_enable));
key.push_back(static_cast<uint32_t>(info.ps_early_z));
key.push_back(static_cast<uint32_t>(info.dual_source_blending));
key.push_back(static_cast<uint32_t>(info.object_primitive_id));
key.push_back(static_cast<uint32_t>(info.alpha_blend_source));
key.insert(key.end(), std::begin(info.target_output_mode), std::end(info.target_output_mode));
key.push_back(info.target_packed_unorm_mask | (uint32_t {info.target_round_mode} << 8u));
const auto mask = info.target_shader_mask;
key.push_back((mask | (mask >> 1u) | (mask >> 2u) | (mask >> 3u)) & 0x11111111u);
for (uint32_t base = 0; base < info.target_export_mapping.size(); base += 4u) {
+17
View File
@@ -66,6 +66,18 @@ inline uint8_t GetDstSel(uint32_t swizzle, uint32_t channel) {
return (swizzle >> (channel * 3u)) & 0x7u;
}
[[nodiscard]] inline bool IsValidImageSwizzle(uint32_t swizzle,
uint32_t component_count = 4) noexcept {
if ((swizzle & ~0xfffu) != 0) {
return false;
}
for (uint32_t channel = 0; channel < 4; channel++) {
const auto selector = GetDstSel(swizzle, channel);
if (selector > 1 && (selector < 4 || selector >= 4 + component_count)) return false;
}
return true;
}
struct ShaderClipSpaceTransform {
float scale[2] = {};
float offset[2] = {};
@@ -150,6 +162,7 @@ struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo {
uint8_t float_mode = 0xc0;
uint32_t dispatch_threads_num[3] = {0, 0, 0};
uint32_t workgroup_counts[3] = {0, 0, 0};
uint64_t indirect_args_address = 0;
bool group_id[3] = {false, false, false};
bool dispatch_thread_dimensions = false;
bool lds_storage = false;
@@ -174,6 +187,9 @@ struct ShaderPixelInputInfo {
uint8_t target_output_mode[8] = {};
uint32_t target_shader_mask = UINT32_MAX;
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
uint8_t target_packed_unorm_mask = 0;
// CB round mode per converted export: zero adds half an LSB, one truncates.
uint8_t target_round_mode = 0;
uint32_t scratch_size_dwords = 0;
bool ps_pos_x = false;
bool ps_pos_y = false;
@@ -188,6 +204,7 @@ struct ShaderPixelInputInfo {
bool ps_sample_mask_export_enable = false;
bool ps_sample_shading = false;
bool dual_source_blending = false;
bool object_primitive_id = false;
// Export logical alpha or per-channel source factors through MRT1 after channel swizzling.
ShaderAlphaBlendSource alpha_blend_source = ShaderAlphaBlendSource::None;
bool ps_early_z = false;
+84 -9
View File
@@ -21,6 +21,7 @@
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <optional>
#include <random>
#include <system_error>
#include <vector>
@@ -956,12 +957,10 @@ int64_t KYTY_SYSV_ABI KernelPwrite(int d, const void* buf, size_t nbytes, int64_
return bytes_written;
}
int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, int64_t offset) {
PRINT_NAME();
static int64_t Writev(int d, const KernelIovec* iov, int iovcnt, std::optional<int64_t> offset) {
std::vector<KernelIovec> buffers;
size_t total = 0;
const int error = ValidateIovecs(iov, iovcnt, offset, &buffers, &total);
const int error = ValidateIovecs(iov, iovcnt, offset.value_or(0), &buffers, &total);
if (error != OK) {
return error;
}
@@ -991,11 +990,16 @@ int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, i
}
const auto position = file->f.Tell();
const auto target = (file->append ? file->f.Size() : static_cast<uint64_t>(offset));
auto target = position;
if (file->append) {
target = file->f.Size();
} else if (offset) {
target = static_cast<uint64_t>(*offset);
}
if (target > static_cast<uint64_t>(INT64_MAX) - total) {
return KERNEL_ERROR_EFBIG;
}
if (!file->f.Seek(target)) {
if ((offset || file->append) && !file->f.Seek(target)) {
return KERNEL_ERROR_EIO;
}
@@ -1012,16 +1016,26 @@ int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, i
}
}
const bool flushed = !file->sync_writes || file->f.Flush();
const bool restored = file->f.Seek(position);
const bool restored = !offset || file->f.Seek(position);
if (!flushed || !restored) {
return KERNEL_ERROR_EIO;
}
LOGF("\tWritev %" PRId64 " bytes (pos = %" PRId64 ", iovcnt = %d) to: %s\n", bytes_written,
offset, iovcnt, Common::PathToString(file->real_name).c_str());
LOGF("\tWritev %" PRId64 " bytes (pos = %" PRIu64 ", iovcnt = %d) to: %s\n", bytes_written,
target, iovcnt, Common::PathToString(file->real_name).c_str());
return bytes_written;
}
int64_t KYTY_SYSV_ABI KernelWritev(int d, const KernelIovec* iov, int iovcnt) {
PRINT_NAME();
return Writev(d, iov, iovcnt, std::nullopt);
}
int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, int64_t offset) {
PRINT_NAME();
return Writev(d, iov, iovcnt, offset);
}
int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
PRINT_NAME();
@@ -1197,6 +1211,67 @@ int KYTY_SYSV_ABI KernelFstat(int d, FileStat* sb) {
return OK;
}
int KYTY_SYSV_ABI KernelTruncate(const char* path, int64_t length) {
PRINT_NAME();
if (path == nullptr) {
return KERNEL_ERROR_EFAULT;
}
if (path[0] != '/' || length < 0) {
return KERNEL_ERROR_EINVAL;
}
size_t component_length = 0;
for (size_t i = 0; path[i] != '\0'; ++i) {
if (i >= 1023) {
return KERNEL_ERROR_ENAMETOOLONG;
}
if (path[i] == '/') {
component_length = 0;
} else if (++component_length > 255) {
return KERNEL_ERROR_ENAMETOOLONG;
}
}
if (IsRandomDevice(path)) {
return OK;
}
const auto real_name = g_mount_points->ResolvePath(path);
if (real_name.empty()) {
return KERNEL_ERROR_ENOENT;
}
if (Common::IsArchivePath(real_name)) {
return KERNEL_ERROR_EROFS;
}
std::error_code error;
const auto status = std::filesystem::status(real_name, error);
if (!error) {
if (!std::filesystem::exists(status)) {
return KERNEL_ERROR_ENOENT;
}
if (std::filesystem::is_directory(status)) {
return KERNEL_ERROR_EISDIR;
}
if (std::filesystem::is_regular_file(status)) {
std::filesystem::resize_file(real_name, static_cast<uint64_t>(length), error);
}
}
if (error) {
switch (static_cast<std::errc>(error.default_error_condition().value())) {
case std::errc::no_such_file_or_directory: return KERNEL_ERROR_ENOENT;
case std::errc::not_a_directory: return KERNEL_ERROR_ENOTDIR;
case std::errc::filename_too_long: return KERNEL_ERROR_ENAMETOOLONG;
case std::errc::permission_denied: return KERNEL_ERROR_EACCES;
case std::errc::read_only_file_system: return KERNEL_ERROR_EROFS;
case std::errc::file_too_large: return KERNEL_ERROR_EFBIG;
case std::errc::invalid_argument: return KERNEL_ERROR_EINVAL;
case std::errc::no_space_on_device: return KERNEL_ERROR_ENOSPC;
default: return KERNEL_ERROR_EIO;
}
}
LOGF("\tTruncate (size = %" PRId64 ") file: %s\n", length,
Common::PathToString(real_name).c_str());
return OK;
}
int KYTY_SYSV_ABI KernelFtruncate(int d, int64_t length) {
PRINT_NAME();
+2
View File
@@ -64,11 +64,13 @@ int64_t KYTY_SYSV_ABI KernelRead(int d, void* buf, size_t nbytes);
int64_t KYTY_SYSV_ABI KernelPread(int d, void* buf, size_t nbytes, int64_t offset);
int64_t KYTY_SYSV_ABI KernelPreadv(int d, const KernelIovec* iov, int iovcnt, int64_t offset);
int64_t KYTY_SYSV_ABI KernelWrite(int d, const void* buf, size_t nbytes);
int64_t KYTY_SYSV_ABI KernelWritev(int d, const KernelIovec* iov, int iovcnt);
int64_t KYTY_SYSV_ABI KernelPwrite(int d, const void* buf, size_t nbytes, int64_t offset);
int64_t KYTY_SYSV_ABI KernelPwritev(int d, const KernelIovec* iov, int iovcnt, int64_t offset);
int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence);
int KYTY_SYSV_ABI KernelStat(const char* path, FileStat* sb);
int KYTY_SYSV_ABI KernelFstat(int d, FileStat* sb);
int KYTY_SYSV_ABI KernelTruncate(const char* path, int64_t length);
int KYTY_SYSV_ABI KernelFtruncate(int d, int64_t length);
int KYTY_SYSV_ABI KernelUnlink(const char* path);
int KYTY_SYSV_ABI KernelRename(const char* from, const char* to);
+226 -218
View File
@@ -72,6 +72,9 @@ constexpr uint64_t DEFAULT_FLEXIBLE_MEMORY_SIZE = 1ull * 1024ull * 1024ull * 102
static uint64_t g_flexible_memory_size = DEFAULT_FLEXIBLE_MEMORY_SIZE;
static bool g_flexible_memory_size_frozen = false;
static Graphics::RenderContext* g_gpu_resources = nullptr;
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
static void (*g_test_gpu_range)(bool, uint64_t, size_t) = nullptr;
#endif
static Graphics::RenderContext& GetGpuResources() {
EXIT_IF(g_gpu_resources == nullptr);
@@ -83,6 +86,9 @@ static bool IsGpuAddressRange(uint64_t vaddr, uint64_t size) {
}
static void MapGpuRange(uint64_t vaddr, uint64_t size) {
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
if (g_test_gpu_range != nullptr) g_test_gpu_range(true, vaddr, size);
#endif
if (g_gpu_resources == nullptr || !IsGpuAddressRange(vaddr, size)) {
return;
}
@@ -90,6 +96,9 @@ static void MapGpuRange(uint64_t vaddr, uint64_t size) {
}
static void UnmapGpuRange(uint64_t vaddr, uint64_t size) {
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
if (g_test_gpu_range != nullptr) g_test_gpu_range(false, vaddr, size);
#endif
if (g_gpu_resources == nullptr || !IsGpuAddressRange(vaddr, size)) {
return;
}
@@ -223,15 +232,15 @@ public:
VirtualRangeType type, const char* name, bool disallow_merge = false) {
Common::LockGuard lock(m_mutex);
if (start == 0 || size == 0) {
if (start == 0 || size == 0 || size > UINT64_MAX - start) {
return false;
}
auto position = LowerBound(start);
if ((position != m_ranges.end() &&
VirtualRangesOverlap(start, size, position->start, position->size)) ||
VirtualRangesOverlap(start, size, position->second.start, position->second.size)) ||
(position != m_ranges.begin() &&
VirtualRangesOverlap(start, size, std::prev(position)->start,
std::prev(position)->size))) {
VirtualRangesOverlap(start, size, std::prev(position)->second.start,
std::prev(position)->second.size))) {
return false;
}
@@ -244,23 +253,21 @@ public:
r.type = type;
r.disallow_merge = disallow_merge;
CopyVirtualRangeName(r.name, name);
const auto index = static_cast<size_t>(position - m_ranges.begin());
m_ranges.insert(position, r);
MergeAroundUnlocked(index);
auto inserted = m_ranges.emplace_hint(position, start, r);
UpdatePageTableEntries(inserted->second, true);
MergeAroundUnlocked(inserted);
return true;
}
bool Remove(uint64_t start, uint64_t size) {
Common::LockGuard lock(m_mutex);
auto position = LowerBound(start);
if (position != m_ranges.end() && position->start == start && position->size == size) {
m_ranges.erase(position);
auto position = m_ranges.find(start);
if (position != m_ranges.end() && position->second.size == size) {
EraseUnlocked(position);
return true;
}
auto removed = RemoveUnlocked(start, size);
MergeUnlocked();
return removed;
return RemoveUnlocked(start, size);
}
bool HasOverlap(uint64_t start, uint64_t size) {
@@ -284,40 +291,31 @@ public:
bool ReleaseReserved(uint64_t start, uint64_t size) {
Common::LockGuard lock(m_mutex);
for (size_t index = 0; index < m_ranges.size(); index++) {
auto& r = m_ranges[index];
if (r.start == start && r.size == size && IsReservedRangeType(r.type)) {
m_ranges.erase(m_ranges.begin() + static_cast<std::ptrdiff_t>(index));
return true;
}
auto position = m_ranges.find(start);
if (position != m_ranges.end() && position->second.size == size &&
IsReservedRangeType(position->second.type)) {
EraseUnlocked(position);
}
return true;
}
bool ReplaceSpan(uint64_t start, uint64_t size, VirtualRangeType expected_type,
uint64_t offset, int protection, int memory_type, VirtualRangeType type,
const char* name, bool disallow_merge = false) {
bool ReplaceSpan(uint64_t start, uint64_t size, VirtualRangeType expected_type, uint64_t offset,
int protection, int memory_type, VirtualRangeType type, const char* name,
bool disallow_merge = false) {
Common::LockGuard lock(m_mutex);
if (start == 0 || size == 0 || size > UINT64_MAX - start) {
return false;
}
auto current = start;
const auto end = start + size;
while (current < end) {
const Range* candidate = nullptr;
for (const auto& r: m_ranges) {
if (r.type == expected_type && current >= r.start &&
current < End(r.start, r.size)) {
candidate = &r;
break;
}
}
if (candidate == nullptr) {
auto current = start;
const auto end = start + size;
for (auto candidate = FindContaining(start); current < end; ++candidate) {
if (candidate == m_ranges.end() || candidate->second.start > current ||
candidate->second.type != expected_type) {
return false;
}
current = std::min(end, End(candidate->start, candidate->size));
current = std::min(end, End(candidate->second.start, candidate->second.size));
}
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
@@ -335,21 +333,27 @@ public:
replacement.type = type;
replacement.disallow_merge = disallow_merge;
CopyVirtualRangeName(replacement.name, name);
RemoveUnlocked(start, size);
auto position = LowerBound(start);
const auto index = static_cast<size_t>(position - m_ranges.begin());
m_ranges.insert(position, replacement);
MergeAroundUnlocked(index);
// Validation precedes all mutations. Keep the first node for the replacement so
// replacing an existing arena does not allocate another tree node.
auto first = SplitAtUnlocked(start);
auto last = SplitAtUnlocked(end);
for (auto it = std::next(first); it != last;) {
it = EraseUnlocked(it);
}
UpdatePageTableEntries(first->second, false);
first->second = replacement;
UpdatePageTableEntries(first->second, true);
MergeAroundUnlocked(first);
return true;
}
void Rename(uint64_t start, uint64_t size, const char* name) {
Common::LockGuard lock(m_mutex);
auto position = LowerBound(start);
if (position != m_ranges.end() && position->start == start && position->size == size) {
CopyVirtualRangeName(position->name, name);
MergeAroundUnlocked(static_cast<size_t>(position - m_ranges.begin()));
auto position = m_ranges.find(start);
if (position != m_ranges.end() && position->second.size == size) {
CopyVirtualRangeName(position->second.name, name);
MergeAroundUnlocked(position);
return;
}
EditUnlocked(start, size, [name](Range* r) { CopyVirtualRangeName(r->name, name); });
@@ -366,13 +370,11 @@ public:
Common::LockGuard lock(m_mutex);
auto next = std::upper_bound(
m_ranges.begin(), m_ranges.end(), addr,
[](uint64_t value, const Range& range) { return value < range.start; });
auto next = m_ranges.upper_bound(addr);
if (next != m_ranges.begin()) {
auto current = std::prev(next);
if (addr < End(current->start, current->size)) {
*out = *current;
if (addr < End(current->second.start, current->second.size)) {
*out = current->second;
return true;
}
}
@@ -380,7 +382,7 @@ public:
return false;
}
*out = *next;
*out = next->second;
return true;
}
@@ -395,11 +397,9 @@ public:
const auto end = start + size;
auto current = start;
for (const auto& range: m_ranges) {
const auto range_end = End(range.start, range.size);
if (range_end <= current) {
continue;
}
for (auto it = FindContaining(start); it != m_ranges.end(); ++it) {
const auto& range = it->second;
const auto range_end = End(range.start, range.size);
if (range.start > current) {
break;
}
@@ -428,27 +428,19 @@ public:
return 0;
}
auto vma = std::upper_bound(
m_ranges.begin(), m_ranges.end(), virtual_addr,
[](uint64_t value, const Range& range) { return value < range.start; });
if (vma == m_ranges.begin()) {
return 0;
}
--vma;
const auto vma_end = End(vma->start, vma->size);
if (virtual_addr < vma->start || virtual_addr >= vma_end ||
!IsCommittedRangeType(vma->type)) {
auto vma = FindContaining(virtual_addr);
if (vma == m_ranges.end() || !IsCommittedRangeType(vma->second.type)) {
return 0;
}
const auto vma_end = End(vma->second.start, vma->second.size);
uint64_t clamped_size = std::min(size, vma_end - virtual_addr);
uint64_t expected = virtual_addr + clamped_size;
++vma;
while (vma != m_ranges.end() && vma->start == expected && IsCommittedRangeType(vma->type) &&
clamped_size < size) {
const auto chunk = std::min(size - clamped_size, vma->size);
while (vma != m_ranges.end() && vma->second.start == expected &&
IsCommittedRangeType(vma->second.type) && clamped_size < size) {
const auto chunk = std::min(size - clamped_size, vma->second.size);
clamped_size += chunk;
expected += chunk;
++vma;
@@ -459,27 +451,7 @@ public:
uint64_t CountPageTableEntries(bool gpu) {
Common::LockGuard lock(m_mutex);
uint64_t used = 0;
for (const auto& r: m_ranges) {
if (!IsCommittedRangeType(r.type) || r.size == 0) {
continue;
}
const bool has_cpu_access =
(r.protection & (PROT_CPU_READ | PROT_CPU_WRITE | PROT_CPU_EXEC)) != 0;
const bool has_gpu_access = (r.protection & (PROT_GPU_READ | PROT_GPU_WRITE)) != 0;
if (gpu ? !has_gpu_access : !has_cpu_access) {
continue;
}
const auto end = End(r.start, r.size);
const auto first_entry = r.start / PAGE_TABLE_GRANULARITY;
const auto last_entry = (end - 1) / PAGE_TABLE_GRANULARITY;
used += last_entry - first_entry + 1;
}
return used;
return gpu ? m_gpu_page_table_entries : m_cpu_page_table_entries;
}
private:
@@ -498,51 +470,112 @@ private:
std::strncmp(left.name, right.name, KERNEL_MAXIMUM_NAME_LENGTH) == 0;
}
static void AddPiece(std::vector<Range>* ranges, const Range& source, uint64_t start,
uint64_t end) {
EXIT_IF(ranges == nullptr);
// Keys are range starts; ranges are nonempty, non-overlapping and locally coalesced.
// A tree avoids shifting unrelated ranges when streaming edits split or remove an arena.
using RangeMap = std::map<uint64_t, Range>;
using Iterator = RangeMap::iterator;
if (end <= start) {
return;
}
Iterator LowerBound(uint64_t start) { return m_ranges.lower_bound(start); }
Range piece = source;
piece.start = start;
piece.size = end - start;
if (piece.type == VirtualRangeType::Direct) {
piece.offset += start - source.start;
Iterator FindContaining(uint64_t address) {
auto next = m_ranges.upper_bound(address);
if (next == m_ranges.begin()) {
return m_ranges.end();
}
ranges->push_back(piece);
auto current = std::prev(next);
return address < End(current->second.start, current->second.size) ? current
: m_ranges.end();
}
std::vector<Range>::iterator LowerBound(uint64_t start) {
return std::lower_bound(
m_ranges.begin(), m_ranges.end(), start,
[](const Range& range, uint64_t value) { return range.start < value; });
static bool CanMerge(const Range& left, const Range& right) {
return End(left.start, left.size) == right.start && SameMergeKey(left, right);
}
void MergeAroundUnlocked(size_t index) {
if (index >= m_ranges.size()) {
void UpdatePageTableEntries(const Range& range, bool add) {
if (!IsCommittedRangeType(range.type)) {
return;
}
if (index != 0) {
auto& previous = m_ranges[index - 1];
auto& current = m_ranges[index];
if (End(previous.start, previous.size) == current.start &&
SameMergeKey(previous, current)) {
previous.size += current.size;
m_ranges.erase(m_ranges.begin() + static_cast<std::ptrdiff_t>(index));
index--;
// Preserve the statistics API's per-range accounting: splitting within a page-table
// entry counts it for each fragment, and merging removes that duplicate contribution.
const auto entries = (End(range.start, range.size) - 1) / PAGE_TABLE_GRANULARITY -
range.start / PAGE_TABLE_GRANULARITY + 1;
if ((range.protection & (PROT_CPU_READ | PROT_CPU_WRITE | PROT_CPU_EXEC)) != 0) {
m_cpu_page_table_entries =
add ? m_cpu_page_table_entries + entries : m_cpu_page_table_entries - entries;
}
if ((range.protection & (PROT_GPU_READ | PROT_GPU_WRITE)) != 0) {
m_gpu_page_table_entries =
add ? m_gpu_page_table_entries + entries : m_gpu_page_table_entries - entries;
}
}
Iterator EraseUnlocked(Iterator position) {
UpdatePageTableEntries(position->second, false);
return m_ranges.erase(position);
}
Iterator MergeNextUnlocked(Iterator left, Iterator right) {
UpdatePageTableEntries(left->second, false);
left->second.size += right->second.size;
auto next = EraseUnlocked(right);
UpdatePageTableEntries(left->second, true);
return next;
}
void MergeAroundUnlocked(Iterator current) {
if (current != m_ranges.begin()) {
auto previous = std::prev(current);
if (CanMerge(previous->second, current->second)) {
MergeNextUnlocked(previous, current);
current = previous;
}
}
while (index + 1 < m_ranges.size()) {
auto& current = m_ranges[index];
auto& next = m_ranges[index + 1];
if (End(current.start, current.size) != next.start || !SameMergeKey(current, next)) {
for (auto next = std::next(current); next != m_ranges.end();) {
if (!CanMerge(current->second, next->second)) {
break;
}
current.size += next.size;
m_ranges.erase(m_ranges.begin() + static_cast<std::ptrdiff_t>(index + 1));
next = MergeNextUnlocked(current, next);
}
}
Iterator SplitAtUnlocked(uint64_t address) {
auto position = LowerBound(address);
if (position != m_ranges.begin()) {
auto& previous = std::prev(position)->second;
const auto previous_end = End(previous.start, previous.size);
if (previous.start < address && address < previous_end) {
Range right = previous;
right.start = address;
right.size = previous_end - address;
if (right.type == VirtualRangeType::Direct) {
right.offset += address - previous.start;
}
position = m_ranges.emplace_hint(position, address, right);
UpdatePageTableEntries(previous, false);
previous.size = address - previous.start;
UpdatePageTableEntries(previous, true);
UpdatePageTableEntries(right, true);
}
}
return position;
}
void MergeSpanUnlocked(Iterator first, Iterator last) {
if (first == last) {
return;
}
// Include both neighbors. The right neighbor itself can be erased by a merge,
// so the sentinel must be the node after it, which this loop never touches.
const auto stop = last == m_ranges.end() ? last : std::next(last);
if (first != m_ranges.begin()) {
--first;
}
for (auto next = std::next(first); next != stop;) {
if (CanMerge(first->second, next->second)) {
next = MergeNextUnlocked(first, next);
} else {
first = next++;
}
}
}
@@ -551,102 +584,51 @@ private:
if (size == 0) {
return;
}
std::vector<Range> out;
auto edit_end = End(start, size);
for (const auto& r: m_ranges) {
auto r_end = End(r.start, r.size);
if (!VirtualRangesOverlap(start, size, r.start, r.size)) {
out.push_back(r);
continue;
}
auto mid_start = std::max(start, r.start);
auto mid_end = std::min(edit_end, r_end);
AddPiece(&out, r, r.start, mid_start);
Range mid = r;
mid.start = mid_start;
mid.size = mid_end - mid_start;
if (mid.type == VirtualRangeType::Direct) {
mid.offset += mid_start - r.start;
}
edit(&mid);
out.push_back(mid);
AddPiece(&out, r, mid_end, r_end);
auto first = SplitAtUnlocked(start);
auto last = SplitAtUnlocked(End(start, size));
for (auto it = first; it != last; ++it) {
UpdatePageTableEntries(it->second, false);
edit(&it->second);
UpdatePageTableEntries(it->second, true);
}
m_ranges = out;
MergeUnlocked();
MergeSpanUnlocked(first, last);
}
bool RemoveUnlocked(uint64_t start, uint64_t size) {
if (size == 0) {
return false;
}
std::vector<Range> out;
bool removed = false;
auto rem_end = End(start, size);
for (const auto& r: m_ranges) {
auto r_end = End(r.start, r.size);
if (!VirtualRangesOverlap(start, size, r.start, r.size)) {
out.push_back(r);
continue;
}
removed = true;
AddPiece(&out, r, r.start, std::max(start, r.start));
AddPiece(&out, r, std::min(rem_end, r_end), r_end);
auto first = SplitAtUnlocked(start);
auto last = SplitAtUnlocked(End(start, size));
if (first == last) {
return false;
}
m_ranges = out;
return removed;
}
void MergeUnlocked() {
if (m_ranges.size() < 2) {
return;
while (first != last) {
first = EraseUnlocked(first);
}
std::sort(m_ranges.begin(), m_ranges.end(),
[](const Range& left, const Range& right) { return left.start < right.start; });
std::vector<Range> merged;
for (const auto& r: m_ranges) {
if (!merged.empty()) {
auto& last = merged[merged.size() - 1];
if (End(last.start, last.size) == r.start && SameMergeKey(last, r)) {
last.size += r.size;
continue;
}
}
merged.push_back(r);
}
m_ranges = merged;
// Removal leaves an address gap, so it cannot make surviving ranges mergeable.
return true;
}
Range* FindOverlap(uint64_t start, uint64_t size) {
auto position = LowerBound(start);
if (position != m_ranges.end() &&
VirtualRangesOverlap(start, size, position->start, position->size)) {
return &*position;
VirtualRangesOverlap(start, size, position->second.start, position->second.size)) {
return &position->second;
}
if (position != m_ranges.begin()) {
auto previous = std::prev(position);
if (VirtualRangesOverlap(start, size, previous->start, previous->size)) {
return &*previous;
if (VirtualRangesOverlap(start, size, previous->second.start, previous->second.size)) {
return &previous->second;
}
}
return nullptr;
}
std::vector<Range> m_ranges;
Common::Mutex m_mutex;
RangeMap m_ranges;
Common::Mutex m_mutex;
uint64_t m_cpu_page_table_entries = 0;
uint64_t m_gpu_page_table_entries = 0;
};
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
@@ -962,7 +944,11 @@ static bool IsInPrtAperture(uint64_t address, uint64_t size = 1) {
}
bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size) {
return g_guest_address_space->TryReadSparseBacking(vaddr, data, size);
return g_guest_address_space->CopySparseMemory<false>(vaddr, data, size);
}
bool CopySparseMemory(uint64_t vaddr, void* data, uint64_t size) {
return g_guest_address_space->CopySparseMemory<true>(vaddr, data, size);
}
static bool SelfTestSub64SharedPlaceholderAlias() {
@@ -2524,20 +2510,7 @@ static bool FreeGuestMemoryOwner(uint64_t vaddr, uint64_t size) {
g_virtual_ranges->Remove(vaddr, size);
}
static int UnmapMemoryRange(uint64_t vaddr, size_t len) {
if (len == 0 || UINT64_MAX - vaddr < len) {
return KERNEL_ERROR_EINVAL;
}
VirtualRanges::Range range {};
if (!g_virtual_ranges->Query(vaddr, 0, &range)) {
return KERNEL_ERROR_EACCES;
}
const auto chunk_len = std::min<uint64_t>(len, range.size - (vaddr - range.start));
if (chunk_len < len) {
const int ret = UnmapMemoryRange(vaddr, chunk_len);
return ret == OK ? UnmapMemoryRange(vaddr + chunk_len, len - chunk_len) : ret;
}
static int UnmapMemoryOwner(const VirtualRanges::Range& range, uint64_t vaddr, size_t len) {
if (IsReservedRangeType(range.type)) {
if (!g_guest_address_space->ReleaseFree(vaddr, len)) {
return KERNEL_ERROR_EACCES;
@@ -2601,6 +2574,13 @@ static int UnmapMemoryRange(uint64_t vaddr, size_t len) {
return OK;
}
static int UnmapMemoryRange(const VirtualRanges::Range& range, uint64_t vaddr, size_t len) {
UnmapGpuRange(vaddr, len);
const int result = UnmapMemoryOwner(range, vaddr, len);
if (result != OK && IsCommittedRangeType(range.type)) MapGpuRange(vaddr, len);
return result;
}
// The ABI entry point and batch operations hold g_memory_operation_mutex.
static int UnmapMemory(uint64_t vaddr, size_t len) {
LOGF("\t start = 0x%016" PRIx64 "\n"
@@ -2614,8 +2594,11 @@ static int UnmapMemory(uint64_t vaddr, size_t len) {
if (!g_virtual_ranges->QuerySpan(vaddr, len, &ranges)) {
return KERNEL_ERROR_EACCES;
}
UnmapGpuRange(vaddr, len);
return UnmapMemoryRange(vaddr, len);
for (const auto& range: ranges) {
const int result = UnmapMemoryRange(range, range.start, range.size);
if (result != OK) return result;
}
return OK;
}
int KYTY_SYSV_ABI KernelMunmap(uint64_t vaddr, size_t len) {
@@ -2624,6 +2607,31 @@ int KYTY_SYSV_ABI KernelMunmap(uint64_t vaddr, size_t len) {
return UnmapMemory(vaddr, len);
}
static bool IsAmmMemoryRange(uint64_t vaddr, size_t size) {
constexpr uint64_t page_size = 0x4000;
return size != 0 && ((vaddr | size) & (page_size - 1)) == 0 &&
vaddr >= kExtendedMemoryBase && vaddr < kExtendedMemoryBase + kExtendedMemorySize &&
size <= kExtendedMemoryBase + kExtendedMemorySize - vaddr;
}
int UnmapAmmMemory(uint64_t vaddr, size_t size) {
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
if (!IsAmmMemoryRange(vaddr, size)) return KERNEL_ERROR_EINVAL;
const auto end = vaddr + size;
while (vaddr < end) {
VirtualRanges::Range range {};
// AMM unmap accepts holes, including a completely unmapped range.
if (!g_virtual_ranges->Query(vaddr, 1, &range) || range.start >= end) break;
vaddr = std::max(vaddr, range.start);
const auto chunk = std::min(end, range.start + range.size) - vaddr;
const int result = UnmapMemoryRange(range, vaddr, chunk);
if (result != OK) return result;
vaddr += chunk;
}
return OK;
}
size_t KYTY_SYSV_ABI KernelGetDirectMemorySize() {
PRINT_NAME();
@@ -3225,11 +3233,7 @@ int KYTY_SYSV_ABI KernelMapDirectMemory2(void** addr, size_t len, int type, int
int MapAutomaticMemory(uint64_t vaddr, size_t size, int type, int prot) {
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
constexpr uint64_t page_size = 0x4000;
if (size == 0 || (vaddr & (page_size - 1)) != 0 || (size & (page_size - 1)) != 0 ||
vaddr < kExtendedMemoryBase || vaddr >= kExtendedMemoryBase + kExtendedMemorySize ||
size > kExtendedMemoryBase + kExtendedMemorySize - vaddr) {
return KERNEL_ERROR_EINVAL;
}
if (!IsAmmMemoryRange(vaddr, size)) return KERNEL_ERROR_EINVAL;
PhysicalMemory::PhysicalRanges ranges;
if (!g_physical_memory->ReserveAutomatic(size, &ranges)) {
@@ -3567,6 +3571,10 @@ int KYTY_SYSV_ABI KernelReserveVirtualRange(void** addr, size_t len, int flags,
}
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
void TestSetGpuRangeCallback(void (*callback)(bool, uint64_t, size_t)) {
g_test_gpu_range = callback;
}
void TestBeforeNextBackingMap(callback_func_t callback) {
g_test_before_backing_map = callback;
}
+3
View File
@@ -112,10 +112,12 @@ void SetFlexibleMemorySize(uint64_t size);
int AllocateDirectMemory(int64_t search_start, int64_t search_end, size_t size, size_t alignment,
int memory_type, int64_t* phys_addr_out, bool automatic = false);
int MapAutomaticMemory(uint64_t vaddr, size_t size, int type, int prot);
int UnmapAmmMemory(uint64_t vaddr, size_t size);
bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size);
bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size);
bool TryReadBufferBacking(uint64_t vaddr, void* data, uint64_t size);
bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size);
bool CopySparseMemory(uint64_t vaddr, void* data, uint64_t size);
[[nodiscard]] uint64_t ClampRangeSize(uint64_t vaddr, uint64_t size);
void WriteBacking(uint64_t vaddr, const void* data, uint64_t size) noexcept;
void InvalidateMemory(uint64_t vaddr, uint64_t size);
@@ -191,6 +193,7 @@ bool ProtectGuestHostMemory(uint64_t vaddr, uint64_t size, Common::VirtualMemory
bool FreeGuestMemory(uint64_t vaddr, uint64_t size);
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
void TestSetGpuRangeCallback(void (*callback)(bool mapped, uint64_t addr, size_t size));
void TestBeforeNextBackingMap(callback_func_t callback);
void TestSetBackingReadCallback(callback_func_t callback);
void TestFailNextPhysicalMemoryUnmap();
+66 -37
View File
@@ -28,6 +28,35 @@ static bool CoalescePlaceholders(uint64_t vaddr, uint64_t size) {
}
#endif
template <typename Regions, typename Source, typename Gap>
static void CopySparseRange(const Regions& regions, uint64_t vaddr, void* data, uint64_t size,
Source&& source, Gap&& gap) {
const auto end = vaddr + size;
auto mapping = regions.lower_bound(vaddr);
if (mapping != regions.begin()) {
auto previous = std::prev(mapping);
if (previous->second.vaddr + previous->second.size > vaddr) {
mapping = previous;
}
}
auto* destination = static_cast<uint8_t*>(data);
auto cursor = vaddr;
for (; mapping != regions.end() && mapping->second.vaddr < end; ++mapping) {
const auto& entry = mapping->second;
const auto start = std::max(vaddr, entry.vaddr);
const auto bytes = std::min(end, entry.vaddr + entry.size) - start;
if (cursor < start) {
gap(cursor, destination + (cursor - vaddr), start - cursor);
}
std::memcpy(destination + (start - vaddr), source(entry, start), bytes);
cursor = start + bytes;
}
if (cursor < end) {
gap(cursor, destination + (cursor - vaddr), end - cursor);
}
}
class GuestBackingStore {
public:
enum class FailureReason {
@@ -247,7 +276,8 @@ public:
return true;
}
bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size) {
template <typename Gap>
bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size, Gap&& gap) {
if (!IsAvailable() || data == nullptr || size == 0 || UINT64_MAX - vaddr < size) {
return false;
}
@@ -258,31 +288,11 @@ public:
g_test_backing_read(vaddr, size);
}
#endif
const auto end = vaddr + size;
auto mapping = m_maps.lower_bound(vaddr);
if (mapping != m_maps.begin()) {
auto previous = std::prev(mapping);
if (previous->second.vaddr + previous->second.size > vaddr) {
mapping = previous;
}
}
auto* destination = static_cast<uint8_t*>(data);
auto cursor = vaddr;
for (; mapping != m_maps.end() && mapping->second.vaddr < end; ++mapping) {
const auto& entry = mapping->second;
const auto start = std::max(vaddr, entry.vaddr);
const auto bytes = std::min(end, entry.vaddr + entry.size) - start;
const auto offset = entry.backing_offset + start - entry.vaddr;
if (cursor < start) {
std::memset(destination + (cursor - vaddr), 0, start - cursor);
}
std::memcpy(destination + (start - vaddr), m_backing_base + offset, bytes);
cursor = start + bytes;
}
if (cursor < end) {
std::memset(destination + (cursor - vaddr), 0, end - cursor);
}
CopySparseRange(m_maps, vaddr, data, size,
[this](const auto& entry, uint64_t start) {
return m_backing_base + (entry.backing_offset + start - entry.vaddr);
},
gap);
return true;
}
@@ -773,22 +783,41 @@ public:
return m_backing->TryReadBacking(vaddr, data, size);
}
bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size) {
if (!Owns(vaddr, size)) {
template <bool copy_private>
bool CopySparseMemory(uint64_t vaddr, void* data, uint64_t size) {
if (data == nullptr || !Owns(vaddr, size)) {
return false;
}
std::shared_lock lock(m_mutex);
auto mapping = m_mapped.upper_bound(vaddr);
if (mapping != m_mapped.begin()) {
--mapping;
}
for (; mapping != m_mapped.end() && mapping->second.vaddr < vaddr + size; ++mapping) {
const auto& region = mapping->second;
if (region.type == MappingType::Private && region.vaddr + region.size > vaddr) {
return false;
if constexpr (!copy_private) {
auto mapping = m_mapped.upper_bound(vaddr);
if (mapping != m_mapped.begin()) {
--mapping;
}
for (; mapping != m_mapped.end() && mapping->second.vaddr < vaddr + size; ++mapping) {
const auto& region = mapping->second;
if (region.type == MappingType::Private && region.vaddr + region.size > vaddr) {
return false;
}
}
}
return m_backing->TryReadSparseBacking(vaddr, data, size);
return m_backing->TryReadSparseBacking(
vaddr, data, size, [&](uint64_t start, uint8_t* destination, uint64_t bytes) {
const auto zero = [](uint64_t, uint8_t* output, uint64_t length) {
std::memset(output, 0, length);
};
if constexpr (copy_private) {
// Backing gaps can contain private mappings or reservations only.
CopySparseRange(m_mapped, start, destination, bytes,
[](const auto& region, uint64_t address) {
EXIT_IF(region.type != MappingType::Private);
return reinterpret_cast<const void*>(address);
},
zero);
} else {
zero(start, destination, bytes);
}
});
}
bool BackingContains(uint64_t vaddr, uint64_t size) { return m_backing->Contains(vaddr, size); }
@@ -201,6 +201,29 @@
<property name="checked"><bool>true</bool></property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="trophyDurationLayout">
<item>
<widget class="QLabel" name="label_trophy_duration">
<property name="text"><string>Trophy notification duration:</string></property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBox_trophy_duration">
<property name="toolTip"><string>How long each trophy unlock notification stays on screen.</string></property>
<property name="suffix"><string> s</string></property>
<property name="minimum"><number>5</number></property>
<property name="maximum"><number>10</number></property>
<property name="value"><number>6</number></property>
</widget>
</item>
<item>
<spacer name="trophyDurationSpacer">
<property name="orientation"><enum>Qt::Horizontal</enum></property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
@@ -0,0 +1,29 @@
#ifndef ADDITIONAL_CONTENT_DIALOG_H
#define ADDITIONAL_CONTENT_DIALOG_H
#include <QDialog>
#include <QIcon>
#include <QString>
class Configuration;
class QLabel;
class QListWidget;
class QPushButton;
class AdditionalContentDialog final: public QDialog {
public:
AdditionalContentDialog(const Configuration& game, const QString& folder,
QWidget* parent = nullptr);
private:
void Scan();
QString m_title_id;
QString m_folder;
QIcon m_game_icon;
QLabel* m_status = nullptr;
QListWidget* m_list = nullptr;
QPushButton* m_open = nullptr;
};
#endif // ADDITIONAL_CONTENT_DIALOG_H
+3
View File
@@ -103,6 +103,7 @@ public:
QString firmwareVer; /* requiredSystemSoftwareVersion from sce_sys/param.json */
QString basedir; /* Game base directory */
QString game_path; /* Launcher-unique game path */
QString addons_dir; /* Global setting; not saved per game. */
bool custom_settings = false;
GameStatus game_status = GameStatus::Unknown;
QString game_comment;
@@ -123,6 +124,7 @@ public:
bool sync_raw_image_buffers = false;
bool tessellation_enabled = false;
bool trophy_enabled = true;
int trophy_duration_seconds = 6;
bool skip_notice_screen = false;
bool network_connected = false;
int vblank_frequency = 60;
@@ -160,6 +162,7 @@ public:
sync_raw_image_buffers = other.sync_raw_image_buffers;
tessellation_enabled = other.tessellation_enabled;
trophy_enabled = other.trophy_enabled;
trophy_duration_seconds = other.trophy_duration_seconds;
skip_notice_screen = other.skip_notice_screen;
network_connected = other.network_connected;
vblank_frequency = other.vblank_frequency;
@@ -6,6 +6,7 @@
#include <QStringList>
class QByteArray;
class QGroupBox;
class QLineEdit;
class QListWidget;
class QMoveEvent;
class QResizeEvent;
@@ -28,7 +29,8 @@ public:
static void WriteSettings(QSettings& s);
static void ReadSettings(QSettings& s);
void SetGlobalSettings(const QStringList& dirs);
void SetGlobalSettings(const QStringList& dirs, const QString& addons_dir);
[[nodiscard]] QString GetAddonsDirectory() const;
[[nodiscard]] QStringList GetGameDirectories() const;
signals:
@@ -41,6 +43,7 @@ private:
Configuration& m_info;
QGroupBox* m_game_dirs_group = nullptr;
QListWidget* m_game_dirs_list = nullptr;
QLineEdit* m_addons_dir_edit = nullptr;
QToolButton* m_remove_game_dir_button = nullptr;
bool m_global_settings = false;
@@ -85,6 +85,7 @@ private:
QString m_settings_file;
QString m_runtime_directory;
QStringList m_game_dirs;
QString m_addons_dir;
Configuration m_global_info;
QMap<QString, Configuration*> m_custom_infos;
CompatibilityDatabase* m_compatibility = nullptr;
@@ -0,0 +1,90 @@
#include "additionalContentDialog.h"
#include "common/additionalContent.h"
#include "configuration.h"
#include "gameContent.h"
#include <QDesktopServices>
#include <QDialogButtonBox>
#include <QDir>
#include <QLabel>
#include <QListWidget>
#include <QPixmap>
#include <QPushButton>
#include <QStyle>
#include <QUrl>
#include <QVBoxLayout>
AdditionalContentDialog::AdditionalContentDialog(const Configuration& game, const QString& folder,
QWidget* parent)
: QDialog(parent), m_title_id(game.title_id.trimmed().toUpper()), m_folder(folder) {
setAttribute(Qt::WA_DeleteOnClose);
setWindowTitle(tr("Additional Content - %1").arg(game.name));
resize(720, 480);
QPixmap icon;
icon.loadFromData(GameContent::ReadFile(game.basedir, QStringLiteral("sce_sys/icon0.png"),
GameContent::MaxImageSize));
m_game_icon = icon.isNull() ? style()->standardIcon(QStyle::SP_FileIcon) : QIcon(icon);
auto* layout = new QVBoxLayout(this);
auto* name = new QLabel(tr("%1 · %2").arg(game.name, m_title_id), this);
name->setTextFormat(Qt::PlainText);
name->setWordWrap(true);
layout->addWidget(name);
m_list = new QListWidget(this);
m_list->setIconSize(QSize(64, 64));
m_list->setSpacing(4);
m_list->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
layout->addWidget(m_list, 1);
m_status = new QLabel(this);
m_status->setTextFormat(Qt::PlainText);
m_status->setWordWrap(true);
layout->addWidget(m_status);
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, this);
auto* refresh = buttons->addButton(tr("Refresh"), QDialogButtonBox::ActionRole);
m_open = buttons->addButton(tr("Open folder"), QDialogButtonBox::ActionRole);
layout->addWidget(buttons);
connect(refresh, &QPushButton::clicked, this, &AdditionalContentDialog::Scan);
connect(m_open, &QPushButton::clicked, this,
[this]() { QDesktopServices::openUrl(QUrl::fromLocalFile(m_folder)); });
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::close);
Scan();
}
void AdditionalContentDialog::Scan() {
m_list->clear();
const bool folder_exists = !m_folder.isEmpty() && QDir(m_folder).exists();
m_open->setEnabled(folder_exists);
for (const auto& entry:
Common::FindAddContent(GameContent::ToPath(m_folder), m_title_id.toStdString())) {
QPixmap icon;
icon.loadFromData(GameContent::ReadFile(GameContent::FromPath(entry.mount_dir),
QStringLiteral("sce_sys/icon0.png"),
GameContent::MaxImageSize));
const auto title =
QString::fromStdString(entry.title.empty() ? entry.entitlement_label : entry.title);
auto* item = new QListWidgetItem(icon.isNull() ? m_game_icon : QIcon(icon),
tr("%1\nInstalled").arg(title), m_list);
item->setToolTip(QString::fromStdString(entry.content_id));
}
if (m_folder.isEmpty()) {
m_status->setText(tr("No add-ons folder is set. Open Global settings and set the "
"Add-ons folder to the location of your extracted add-on packages."));
} else if (!folder_exists) {
m_status->setText(tr("The add-ons folder does not exist:\n%1\nCheck the path in Global "
"settings.")
.arg(QDir::toNativeSeparators(m_folder)));
} else if (m_list->count() == 0) {
m_status->setText(
tr("No add-ons found for this game in:\n%1").arg(QDir::toNativeSeparators(m_folder)));
} else {
m_status->setText(tr("%n installed add-on(s)", nullptr, m_list->count()));
}
}
+5
View File
@@ -16,6 +16,7 @@ QVariantMap Configuration::GameSettings() const {
KYTY_CFG_SET(sync_raw_image_buffers);
KYTY_CFG_SET(tessellation_enabled);
KYTY_CFG_SET(trophy_enabled);
KYTY_CFG_SET(trophy_duration_seconds);
KYTY_CFG_SET(skip_notice_screen);
KYTY_CFG_SET(network_connected);
KYTY_CFG_SET(vblank_frequency);
@@ -63,6 +64,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
KYTY_CFG_GET(sync_raw_image_buffers);
KYTY_CFG_GET(tessellation_enabled);
trophy_enabled = s.value("trophy_enabled", trophy_enabled).toBool();
KYTY_CFG_GET(trophy_duration_seconds);
KYTY_CFG_GET(skip_notice_screen);
KYTY_CFG_GET(network_connected);
vblank_frequency = s.value("vblank_frequency", vblank_frequency).toInt();
@@ -143,6 +145,9 @@ bool Configuration::SetGameSettings(const QJsonObject& settings, QString& error)
if (vblank_frequency < 30 || vblank_frequency > 360) {
return fail(tr("Vblank frequency must be between 30 and 360."));
}
if (trophy_duration_seconds < 5 || trophy_duration_seconds > 10) {
return fail(tr("Trophy notification duration must be between 5 and 10 seconds."));
}
if ((shader_log_direction == LogDirection::File && shader_log_folder.isEmpty()) ||
(printf_direction == LogDirection::File && printf_output_file.isEmpty()) ||
(command_buffer_dump_enabled && command_buffer_dump_folder.isEmpty())) {
+32 -2
View File
@@ -15,6 +15,7 @@
#include <QFileInfo>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLayout>
#include <QLineEdit>
#include <QListView>
@@ -281,6 +282,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
m_ui->checkBox_network_connected->setChecked(info.network_connected);
m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled);
m_ui->spinBox_trophy_duration->setValue(info.trophy_duration_seconds);
m_ui->spinBox_vblank_frequency->setValue(info.vblank_frequency);
m_ui->comboBox_console_language->clear();
m_ui->comboBox_console_language->addItems(CONSOLE_LANGUAGE_NAMES);
@@ -346,6 +348,28 @@ void ConfigurationEditDialog::InitGameDirectories() {
button_layout->addStretch(1);
group_layout->addLayout(button_layout);
group_layout->addWidget(new QLabel(tr("Add-ons folder"), m_game_dirs_group));
auto* addons_layout = new QHBoxLayout;
addons_layout->setContentsMargins(0, 0, 0, 0);
addons_layout->setSpacing(4);
m_addons_dir_edit = new QLineEdit(m_game_dirs_group);
m_addons_dir_edit->setPlaceholderText(tr("Folder containing extracted add-on (DLC) packages"));
addons_layout->addWidget(m_addons_dir_edit, 1);
auto* addons_button = new QToolButton(m_game_dirs_group);
addons_button->setIcon(style()->standardIcon(QStyle::SP_DirOpenIcon));
addons_button->setToolTip(tr("Select add-ons folder"));
addons_button->setAutoRaise(true);
addons_layout->addWidget(addons_button);
group_layout->addLayout(addons_layout);
connect(addons_button, &QToolButton::clicked, this, [this]() {
const auto dir = QFileDialog::getExistingDirectory(
this, tr("Select add-ons folder"),
m_addons_dir_edit->text().isEmpty() ? QDir::homePath() : m_addons_dir_edit->text());
if (!dir.isEmpty()) {
m_addons_dir_edit->setText(QDir::toNativeSeparators(dir));
}
});
m_ui->gridLayout->addWidget(m_game_dirs_group, 1, 0, 1, 1);
m_game_dirs_group->setVisible(false);
@@ -358,9 +382,10 @@ void ConfigurationEditDialog::InitGameDirectories() {
update_game_directory_buttons();
}
void ConfigurationEditDialog::SetGlobalSettings(const QStringList& dirs) {
void ConfigurationEditDialog::SetGlobalSettings(const QStringList& dirs, const QString& addons_dir) {
m_global_settings = true;
auto* menu = new QMenu(m_ui->settings_button);
m_addons_dir_edit->setText(QDir::toNativeSeparators(addons_dir));
auto* menu = new QMenu(m_ui->settings_button);
menu->addAction(tr("Import..."), this, &ConfigurationEditDialog::ImportGameSettings);
menu->addAction(tr("Export..."), this, &ConfigurationEditDialog::ExportGameSettings);
m_ui->settings_button->setMenu(menu);
@@ -379,6 +404,10 @@ void ConfigurationEditDialog::SetGlobalSettings(const QStringList& dirs) {
resize(size().expandedTo(minimumSizeHint()));
}
QString ConfigurationEditDialog::GetAddonsDirectory() const {
return m_global_settings ? m_addons_dir_edit->text().trimmed() : QString();
}
QStringList ConfigurationEditDialog::GetGameDirectories() const {
QStringList dirs;
if (!m_global_settings) {
@@ -444,6 +473,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked();
info.network_connected = ui.checkBox_network_connected->isChecked();
info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked();
info.trophy_duration_seconds = ui.spinBox_trophy_duration->value();
info.vblank_frequency = ui.spinBox_vblank_frequency->value();
info.console_language = ui.comboBox_console_language->currentIndex();
info.vulkan_validation_enabled = ui.checkBox_vulkan_validation->isChecked();
+24 -2
View File
@@ -1,5 +1,6 @@
#include "configurationListWidget.h"
#include "additionalContentDialog.h"
#include "cheatFile.h"
#include "common/archive.h"
#include "compatibilityDatabase.h"
@@ -73,6 +74,7 @@ constexpr char CONF_SECTION_NAME[] = "GameConfigurations";
constexpr char CONF_LAUNCHER[] = "Launcher";
constexpr char CONF_GAME_DIR[] = "game_dir";
constexpr char CONF_GAME_DIRS[] = "game_dirs";
constexpr char CONF_ADDONS_DIR[] = "addons_dir";
constexpr char CONF_GLOBAL[] = "GlobalConfiguration";
constexpr char SAVE_DATA_DIR[] = "_SaveData";
@@ -395,6 +397,7 @@ void ConfigurationListWidget::WriteSettings() {
s->beginGroup(CONF_LAUNCHER);
m_game_dirs = NormalizeGameDirectories(m_game_dirs);
s->setValue(CONF_GAME_DIRS, m_game_dirs);
s->setValue(CONF_ADDONS_DIR, m_addons_dir);
s->setValue("grid_view", m_ui->grid_view_button->isChecked());
s->setValue("grid_icon_width", m_ui->grid_size_slider->value());
s->remove(CONF_GAME_DIR);
@@ -444,6 +447,7 @@ void ConfigurationListWidget::ReadSettings() {
if (m_game_dirs.isEmpty()) {
m_game_dirs = NormalizeGameDirectories(SettingsStringList(s->value(CONF_GAME_DIR)));
}
m_addons_dir = NormalizeGameDirectory(s->value(CONF_ADDONS_DIR).toString());
s->endGroup();
s->beginGroup(CONF_GLOBAL);
@@ -508,6 +512,7 @@ ConfigurationListWidget::CreateConfiguration(const ConfigurationItem& item) cons
info->elf = custom->elf;
}
info->host_input_mapping = m_global_info.host_input_mapping;
info->addons_dir = m_addons_dir;
return info;
}
@@ -598,7 +603,13 @@ static GameMetadata GetGameMetadata(const QByteArray& param_data, const QString&
ret.title_name = title;
}
ret.title_id = GetJsonString(root, QStringLiteral("titleId"));
ret.title_id = GetJsonString(root, QStringLiteral("titleId"));
if (ret.title_id.isEmpty()) {
const auto content_id = GetJsonString(root, QStringLiteral("contentId"));
if (content_id.size() == 36 && content_id[6] == '-' && content_id[16] == '_') {
ret.title_id = content_id.mid(7, 9);
}
}
ret.gameVersion = GetJsonString(root, QStringLiteral("appVersion"));
if (ret.gameVersion.isEmpty()) {
ret.gameVersion = GetJsonString(root, QStringLiteral("contentVersion"));
@@ -887,7 +898,7 @@ void ConfigurationListWidget::edit_global_settings() {
ConfigurationEditDialog dlg(info, this);
dlg.setWindowTitle(tr("Global settings"));
dlg.SetGlobalSettings(m_game_dirs);
dlg.SetGlobalSettings(m_game_dirs, m_addons_dir);
connect(&dlg, &ConfigurationEditDialog::PreviewControllerColor, this,
&ConfigurationListWidget::PreviewControllerColor);
connect(&dlg, &ConfigurationEditDialog::ImportGameSettings, this,
@@ -901,6 +912,7 @@ void ConfigurationListWidget::edit_global_settings() {
const auto game_dirs = NormalizeGameDirectories(dlg.GetGameDirectories());
const bool game_dirs_changed = game_dirs != m_game_dirs;
m_game_dirs = game_dirs;
m_addons_dir = NormalizeGameDirectory(dlg.GetAddonsDirectory());
WriteSettings();
if (game_dirs_changed) {
ScanGameDirectory();
@@ -1246,6 +1258,16 @@ void ConfigurationListWidget::ShowContextMenu(QTreeWidgetItem* witem) {
});
action_patches->setVisible(item != nullptr &&
Cheats::IsSupportedTitleId(item->GetInfo().title_id));
QAction* action_addcont = menu.addAction(
style()->standardIcon(QStyle::SP_DirIcon), tr("Additional Content..."));
connect(action_addcont, &QAction::triggered, this,
[this, item = QPointer<ConfigurationItem>(item)]() {
if (item != nullptr) {
auto* dialog = new AdditionalContentDialog(item->GetInfo(), m_addons_dir, this);
dialog->show();
}
});
action_addcont->setEnabled(item != nullptr);
QAction* action_remove_save_data =
menu.addAction(style()->standardIcon(QStyle::SP_DialogDiscardButton),
tr("Remove save data..."), this, SLOT(remove_save_data()));
+4
View File
@@ -254,6 +254,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
args << "--readback-linear-images" << BoolArg(info.readback_linear_images);
args << "--sync-raw-image-buffers" << BoolArg(info.sync_raw_image_buffers);
args << "--trophy-notifications" << BoolArg(info.trophy_enabled);
args << "--trophy-duration" << QString::number(info.trophy_duration_seconds);
args << "--skip-notice-screen" << BoolArg(info.skip_notice_screen);
args << "--network-connected" << BoolArg(info.network_connected);
if (info.tessellation_enabled) {
@@ -294,6 +295,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
game = QDir(info.basedir).filePath(info.elf);
}
args << "--game" << game;
if (!info.addons_dir.isEmpty()) {
args << "--addcont-dir" << info.addons_dir;
}
const auto patch_plan = Cheats::PlanPath(info.title_id);
if (QFileInfo::exists(patch_plan)) {
+18 -44
View File
@@ -804,13 +804,13 @@ static int fuse_shader_halves(Shader* fused_result, const Shader* front,
return OK;
}
int KYTY_SYSV_ABI AgcUnknownFuseShaderHalves(Shader* fused_result, const Shader* front,
int KYTY_SYSV_ABI AgcFuseShaderHalves_0200(Shader* fused_result, const Shader* front,
const Shader* back, void* scratch_mem) {
PRINT_NAME();
return fuse_shader_halves(fused_result, front, back, scratch_mem, true);
}
int KYTY_SYSV_ABI AgcUnknownNApJjpKNBl4(Shader* fused_result, const Shader* front,
int KYTY_SYSV_ABI AgcFuseShaderHalves(Shader* fused_result, const Shader* front,
const Shader* back, void* scratch_mem) {
PRINT_NAME();
return fuse_shader_halves(fused_result, front, back, scratch_mem, false);
@@ -2636,7 +2636,7 @@ uint32_t KYTY_SYSV_ABI AgcDcbDrawIndexMultiInstancedGetSize() {
return 9u * 4u;
}
int KYTY_SYSV_ABI AgcUnknownIkfdtRIqCE(uint32_t* cmd, uint64_t arg1,
int KYTY_SYSV_ABI AgcJumpPatchSetTarget_0300(uint32_t* cmd, uint64_t arg1,
const volatile uint32_t* target, uint32_t size_in_dwords,
uint64_t arg4, uint64_t arg5) {
PRINT_NAME();
@@ -3022,39 +3022,8 @@ uint32_t* KYTY_SYSV_ABI AgcDcbAcquireMem(CommandBuffer* buf, uint8_t engine, uin
engine, cb_db_op, gcr_cntl, reinterpret_cast<uint64_t>(base), size_bytes, poll_cycles);
}
bool no_size = (static_cast<int64_t>(size_bytes) == -1);
auto vaddr = reinterpret_cast<uint64_t>(base);
EXIT_NOT_IMPLEMENTED(buf == nullptr);
static std::atomic<uint32_t> warning_log_count {0};
const bool log_warning = (warning_log_count.fetch_add(1) < 64);
if (!no_size && (size_bytes & 0xffu) != 0) {
if (log_warning) {
LOGF_COLOR(Log::Color::Red, "\t warning: size_bytes is not 256-byte aligned\n");
}
}
if (!no_size && (size_bytes >> 40u) != 0) {
if (log_warning) {
LOGF_COLOR(Log::Color::Red, "\t warning: size_bytes is too large\n");
}
}
if ((vaddr & 0xffu) != 0) {
if (log_warning) {
LOGF_COLOR(Log::Color::Red, "\t warning: base is not 256-byte aligned\n");
}
}
if ((vaddr >> 40u) != 0) {
if (log_warning) {
LOGF_COLOR(Log::Color::Red, "\t warning: base is too high\n");
}
}
if (engine > 1) {
if (log_warning) {
LOGF_COLOR(Log::Color::Red, "\t warning: unsupported engine 0x%02" PRIx8 "\n", engine);
}
}
auto* cmd = buf->AllocateDW(8);
if (cmd == nullptr) {
@@ -3062,14 +3031,19 @@ uint32_t* KYTY_SYSV_ABI AgcDcbAcquireMem(CommandBuffer* buf, uint8_t engine, uin
return nullptr;
}
cmd[0] = KYTY_PM4(8, Pm4::IT_NOP, Pm4::R_ACQUIRE_MEM);
cmd[1] = (static_cast<uint32_t>(engine & 1u) << 31u) | cb_db_op;
cmd[2] = (no_size ? 0 : static_cast<uint32_t>(size_bytes >> 8u));
cmd[3] = 0;
constexpr uint64_t address_mask = 0xffffffffffffull;
const auto vaddr = reinterpret_cast<uint64_t>(base);
// Cover the byte range in 256-byte units, bounded by the 48-bit address space.
const auto size = std::min((((vaddr & 0xffu) + size_bytes) & address_mask) + 0xffu,
(address_mask & ~uint64_t {0xff}) - vaddr) >> 8u;
cmd[0] = KYTY_PM4(8, Pm4::IT_ACQUIRE_MEM, 0u);
cmd[1] = (static_cast<uint32_t>(engine) << 31u) | (cb_db_op & 0x06007fc0u);
cmd[2] = static_cast<uint32_t>(size);
cmd[3] = static_cast<uint32_t>((size >> 32u) & 0xffu);
cmd[4] = static_cast<uint32_t>(vaddr >> 8u);
cmd[5] = 0;
cmd[6] = poll_cycles / 40;
cmd[7] = gcr_cntl;
cmd[5] = static_cast<uint32_t>(vaddr >> 40u);
cmd[6] = std::min(poll_cycles >> 4u, 0xffffu);
cmd[7] = gcr_cntl & 0x7ffffu;
return cmd;
}
@@ -3083,7 +3057,7 @@ uint32_t KYTY_SYSV_ABI AgcDcbAcquireMemGetSize() {
uint32_t* KYTY_SYSV_ABI AgcAcbAcquireMem(CommandBuffer* buf, uint32_t gcr_cntl,
const volatile void* base, uint64_t size_bytes,
uint32_t poll_cycles) {
return AgcDcbAcquireMem(buf, 1, 0, gcr_cntl, base, size_bytes, poll_cycles);
return AgcDcbAcquireMem(buf, 0, 0, gcr_cntl, base, size_bytes, poll_cycles);
}
uint32_t KYTY_SYSV_ABI AgcAcbAcquireMemGetSize() {
@@ -3536,7 +3510,7 @@ uint32_t* KYTY_SYSV_ABI AgcDcbSetPredication(CommandBuffer* buf, uint8_t conditi
return cmd;
}
uint32_t* KYTY_SYSV_ABI AgcUnknownKRzWekV120(CommandBuffer* buf, uint32_t arg1, uint32_t arg2,
uint32_t* KYTY_SYSV_ABI AgcDcbSetIndexSize_0600(CommandBuffer* buf, uint32_t arg1, uint32_t arg2,
uint32_t arg3) {
PRINT_NAME();
@@ -4477,7 +4451,7 @@ bool KYTY_SYSV_ABI AgcDriverIsCaptureInProgress() {
return false;
}
int KYTY_SYSV_ABI AgcDriverUnknownU9ueyEhSkF4() {
int KYTY_SYSV_ABI AgcDriverRegisterDefaultOwner() {
PRINT_NAME();
return static_cast<int>(0x8a6c9018u);
+5 -5
View File
@@ -42,9 +42,9 @@ void* KYTY_SYSV_ABI AgcGetRegisterDefaults2Internal(uint32_t ver);
int KYTY_SYSV_ABI AgcCreateShader(Shader** dst, void* header, const volatile void* code);
int KYTY_SYSV_ABI AgcUnknownGetFusedShaderSize(SizeAlign* dst, const Shader* front,
const Shader* back);
int KYTY_SYSV_ABI AgcUnknownFuseShaderHalves(Shader* fused_result, const Shader* front,
int KYTY_SYSV_ABI AgcFuseShaderHalves_0200(Shader* fused_result, const Shader* front,
const Shader* back, void* scratch_mem);
int KYTY_SYSV_ABI AgcUnknownNApJjpKNBl4(Shader* fused_result, const Shader* front,
int KYTY_SYSV_ABI AgcFuseShaderHalves(Shader* fused_result, const Shader* front,
const Shader* back, void* scratch_mem);
int KYTY_SYSV_ABI AgcSetCxRegIndirectPatchSetAddress(uint32_t* cmd,
const volatile ShaderRegister* regs);
@@ -161,7 +161,7 @@ uint32_t* KYTY_SYSV_ABI AgcDcbDrawIndexMultiInstanced(CommandBuffer* buf, uint32
const volatile void* object_ids,
uint32_t instance_count, uint64_t modifier);
uint32_t KYTY_SYSV_ABI AgcDcbDrawIndexMultiInstancedGetSize();
int KYTY_SYSV_ABI AgcUnknownIkfdtRIqCE(uint32_t* cmd, uint64_t arg1,
int KYTY_SYSV_ABI AgcJumpPatchSetTarget_0300(uint32_t* cmd, uint64_t arg1,
const volatile uint32_t* target, uint32_t size_in_dwords,
uint64_t arg4, uint64_t arg5);
uint32_t* KYTY_SYSV_ABI AgcDcbDrawIndexAuto(CommandBuffer* buf, uint32_t index_count,
@@ -260,7 +260,7 @@ uint32_t KYTY_SYSV_ABI AgcDcbCondExecGetSize();
uint32_t* KYTY_SYSV_ABI AgcDcbSetPredication(CommandBuffer* buf, uint8_t condition, uint8_t op,
uint8_t wait_op, const volatile void* address,
uint32_t count_in_dwords);
uint32_t* KYTY_SYSV_ABI AgcUnknownKRzWekV120(CommandBuffer* buf, uint32_t arg1, uint32_t arg2,
uint32_t* KYTY_SYSV_ABI AgcDcbSetIndexSize_0600(CommandBuffer* buf, uint32_t arg1, uint32_t arg2,
uint32_t arg3);
int KYTY_SYSV_ABI AgcDmaDataPatchSetDstAddressOrOffset(uint32_t* cmd,
uint64_t dst_address_or_offset);
@@ -340,7 +340,7 @@ uint32_t KYTY_SYSV_ABI AgcDriverGetEqContextId(const LibKernel::EventQueue::Kern
int KYTY_SYSV_ABI AgcDriverSetTFRing(const volatile void* base, uint32_t size);
int KYTY_SYSV_ABI AgcDriverSetHsOffchipParam(uint64_t value0, uint64_t value1, uint64_t value2);
bool KYTY_SYSV_ABI AgcDriverIsCaptureInProgress();
int KYTY_SYSV_ABI AgcDriverUnknownU9ueyEhSkF4();
int KYTY_SYSV_ABI AgcDriverRegisterDefaultOwner();
} // namespace Gen5Driver
+1 -13
View File
@@ -156,20 +156,8 @@ int KYTY_SYSV_ABI AjmDecAt9ParseConfigData(const void* config_data,
return AJM_ERROR_INVALID_PARAMETER;
}
auto* handle = Atrac9GetHandle();
if (handle == nullptr) {
return AJM_ERROR_INVALID_PARAMETER;
}
Atrac9CodecInfo codec_info {};
const int init_result = AjmAt9InitDecoder(handle, static_cast<const uint8_t*>(config_data));
const int info_result =
init_result == 0 ? Atrac9GetCodecInfo(handle, &codec_info) : init_result;
Atrac9ReleaseHandle(handle);
if (info_result != 0 || codec_info.channels <= 0 || codec_info.samplingRate <= 0 ||
codec_info.frameSamples <= 0 || codec_info.framesInSuperframe <= 0 ||
codec_info.superframeSize <= 0) {
if (!AjmAt9GetCodecInfo(static_cast<const uint8_t*>(config_data), codec_info)) {
return AJM_ERROR_INVALID_PARAMETER;
}
Loaded 100 of 129 files, more files were not shown because too many files have changed in this diff. Show more