Compare commits

..

141 Commits

Author SHA1 Message Date
CamilleLaVey 751dd8546e Smol change 2026-08-03 18:38:53 -04:00
CamilleLaVey 67a54e7357 Some adjustments on overlapped images not recoverted, relaxed due dynamic resolution 2026-08-03 17:29:31 -04:00
CamilleLaVey 2215455617 Another pair of fixes on nvgpu 2026-08-02 03:44:54 -04:00
CamilleLaVey bbb75f8dd4 Some adjustments between buffer history and ZBC Table 2026-08-02 03:07:06 -04:00
CamilleLaVey b8f384f3bd Adjustments on the memory reclamations 2 2026-08-02 01:04:19 -04:00
CamilleLaVey 4b209db82c Request maximum map counts 2026-08-01 23:20:41 -04:00
CamilleLaVey c232624b8b Extend UMA to async path 2026-08-01 22:37:22 -04:00
CamilleLaVey a686b50333 3rd step on UMA implementation 2026-08-01 22:04:31 -04:00
CamilleLaVey 1507074346 Refactor Android CPU affinity/threading 2026-08-01 20:49:41 -04:00
CamilleLaVey 332d0b6bad Revert "debug uma impl" 2026-08-01 19:16:20 -04:00
CamilleLaVey e2984caedc Revert "MEOW" 2026-08-01 19:09:27 -04:00
CamilleLaVey 50763528a2 MEOW 2026-08-01 03:41:13 -04:00
CamilleLaVey 4b3608b58d debug uma impl 2026-08-01 03:07:57 -04:00
CamilleLaVey b76d04e47d ASTC adjustments 2026-08-01 02:41:09 -04:00
CamilleLaVey 71afaff6b3 Another try 2026-08-01 00:03:29 -04:00
CamilleLaVey dcbfa3c2d3 Meow gidolio 2.0 2026-07-31 23:14:58 -04:00
CamilleLaVey 70ff43f2f8 Meow gidolio 2026-07-31 23:10:31 -04:00
CamilleLaVey 76faac9252 shader quad control impl 2026-07-31 23:06:39 -04:00
CamilleLaVey 6346371725 Some memory corrections 2026-07-31 22:52:42 -04:00
CamilleLaVey 4162f86a11 debug overlap images 2 2026-07-31 21:33:49 -04:00
CamilleLaVey 86224e49ef Test on depth bias 2026-07-31 20:46:07 -04:00
CamilleLaVey 6665a89dd2 Fix build 2026-07-31 19:55:28 -04:00
CamilleLaVey 515afa2959 Initial Implementation AHB 2026-07-31 19:46:04 -04:00
CamilleLaVey 8c8c7c6162 Adjustments on UMA 2026-07-31 19:30:31 -04:00
CamilleLaVey fb65aa3c33 2nd step on the bindless implementation 2026-07-31 18:35:17 -04:00
CamilleLaVey a24ad03ff3 Android toggle for uma 2026-07-31 18:10:45 -04:00
CamilleLaVey 4884a0c002 Initial implementation on unified memory access 2026-07-31 18:03:49 -04:00
CamilleLaVey ab1d99d218 some debugs tools 2026-07-31 17:31:43 -04:00
CamilleLaVey e6ee576f80 Quick shenanigan 2026-07-31 05:45:27 -04:00
CamilleLaVey 1682b532da relax memory and draws dispatchs 2026-07-31 05:09:14 -04:00
CamilleLaVey 40fe050954 Quick change for cheats 2026-07-31 04:58:17 -04:00
CamilleLaVey 24ff23f2ea Quick change on pipeline barriers 2026-07-31 04:17:38 -04:00
CamilleLaVey 87ebbfb9b1 Coalescing memory entries on MLP 2026-07-31 04:11:40 -04:00
CamilleLaVey 471778c850 WEML toggle 2026-07-31 03:12:22 -04:00
CamilleLaVey 095f27fd34 Another try with 3D BCn 2026-07-31 03:05:52 -04:00
CamilleLaVey a6c423cafb Smolio jenkins 2026-07-31 02:19:41 -04:00
CamilleLaVey 514507b674 fix build 2026-07-31 02:13:19 -04:00
CamilleLaVey 10935f8d46 playing with isberd 2026-07-31 02:11:28 -04:00
CamilleLaVey a0dae2d490 Compute shader 3D BCn to VK 2026-07-31 01:41:00 -04:00
CamilleLaVey 5e40e3c9c0 GIDOLIO THE KOREAN MENACE 2026-07-31 00:57:05 -04:00
CamilleLaVey f61d05f8da HERE WE GO AGAIN. 2026-07-31 00:45:18 -04:00
CamilleLaVey b04207fdfc Adjustments on the plain data for vertex buffer optimization 2026-07-30 23:54:26 -04:00
CamilleLaVey 91791bd5fe Meow Gidolius Galacticus 2026-07-30 20:36:44 -04:00
CamilleLaVey 9928885a5d DynamicRendering + Pipeline Worker Thread toggles 2026-07-30 20:24:50 -04:00
CamilleLaVey 81fa91b400 Another funny change for pipelines 2026-07-30 20:15:11 -04:00
CamilleLaVey eb8706e366 Funny experiment for pipelines 2026-07-30 19:53:34 -04:00
CamilleLaVey b9d55c9448 2nd step for the buffer address device implementation 2026-07-30 19:29:19 -04:00
CamilleLaVey 1188f285b7 Pipeline Creation Feedback impl 2026-07-30 18:57:15 -04:00
CamilleLaVey 200b81b087 Initial step for the buffer address device implementation 2026-07-30 18:28:24 -04:00
CamilleLaVey 742cadde1d Keep conditional rendering inside a single render pass instance
Begin and end were landing in different scopes: the clear and draw
texture paths opened it with no pass bound, the compare helpers reopened
it after their resolve pass had closed one, and the pass teardown could
return before pausing, leaving it active at submit.
2026-07-30 18:13:56 -04:00
CamilleLaVey c3effdf558 Use per-barrier stage masks on render pass end
Adds a native VkDependencyInfo entry point and splits the end of pass
barrier so color and depth/stencil attachments no longer wait on each
other's stages.
2026-07-30 17:09:21 -04:00
CamilleLaVey b9428acf12 Cut per-draw descriptor set work
Only query the timeline semaphore when no set looks free, and skip the
update plus rebind when the payload matches the previous draw.
2026-07-30 17:09:21 -04:00
CamilleLaVey b027dc42ce Restore conditional rendering workaround for Adreno, Mali and Turnip 2026-07-30 16:54:30 -04:00
CamilleLaVey dc9c99b22d Revert "[TEST] Keep tuning the drain/ memory allocation"
This reverts commit 7f4529b15d.
2026-07-30 16:53:25 -04:00
CamilleLaVey 5346e77c0a Revert "[TEST] Adjust mutable formats usage"
This reverts commit 328a2c1081.
2026-07-30 16:45:08 -04:00
CamilleLaVey 914b78495c Revert "[TEST] Change on storage views per request instead of always granted"
This reverts commit d1eb08c036.
2026-07-30 16:45:03 -04:00
CamilleLaVey a17b4cbb04 Revert "[TEST] New optimal image layouts access"
This reverts commit 236696b0ff.
2026-07-30 16:44:37 -04:00
CamilleLaVey a7b5910eaa Revert "[TEST] Adjustments on storage view + general image layouts"
This reverts commit 88fcb7d338.
2026-07-30 16:44:10 -04:00
CamilleLaVey f98f8b65de Some trivial tests 2026-07-30 15:57:59 -04:00
CamilleLaVey 88fcb7d338 [TEST] Adjustments on storage view + general image layouts 2026-07-30 15:13:04 -04:00
CamilleLaVey 4a4fd056ff enable msaa depth resolve 2026-07-29 20:57:20 -04:00
CamilleLaVey 236696b0ff [TEST] New optimal image layouts access 2026-07-29 20:56:56 -04:00
CamilleLaVey d1eb08c036 [TEST] Change on storage views per request instead of always granted 2026-07-29 20:49:36 -04:00
CamilleLaVey 84a4149db2 Presync fix again 2026-07-29 19:46:51 -04:00
CamilleLaVey de9b1071a5 [TEST] VMA budget fix 2026-07-29 19:41:38 -04:00
CamilleLaVey 85049c2c78 [TEST] Presync + CR workaround removal from QCOM 2026-07-29 19:09:22 -04:00
CamilleLaVey 7fcf88cb10 [TEST] DR Adjustment 2 2026-07-29 17:28:09 -04:00
CamilleLaVey e4f29899d2 [TEST] Adjustment memory reclamation 2 2026-07-29 16:32:54 -04:00
CamilleLaVey 41d2bf4a3e Fix build 2026-07-29 14:52:06 -04:00
CamilleLaVey 328a2c1081 [TEST] Adjust mutable formats usage 2026-07-29 14:41:36 -04:00
CamilleLaVey 6e48eeb09c [TEST] depth stencil resolve implementation + fixes 2026-07-29 14:11:30 -04:00
CamilleLaVey 2cdd7173a9 [TEST] Line/ conservative rasterization adjustment 2026-07-29 13:27:31 -04:00
CamilleLaVey 69342044ef [TEST] Quick corrections 2026-07-29 13:27:31 -04:00
CamilleLaVey 1f41d809b0 [TEST] depth clip enable implementation 2026-07-29 13:27:31 -04:00
CamilleLaVey 250b545d75 [TEST] Debug viewport geometry 2026-07-29 13:27:31 -04:00
CamilleLaVey 5a9322af80 [TEST] Debug depths on viewports 2026-07-29 13:27:31 -04:00
CamilleLaVey bbee229d07 [TEST] Adjustments on memory reclamations 2026-07-29 13:27:31 -04:00
CamilleLaVey cfe1c8b16c [TEST] Rework Android CPU affinity/ threading 2 2026-07-29 13:27:31 -04:00
CamilleLaVey dd5d09e3ae [TEST] Rework Android CPU affinity/ threading 2026-07-29 13:27:25 -04:00
CamilleLaVey 25af655e48 [TEST] Adjustments on TransformFeedback 2026-07-29 13:26:55 -04:00
CamilleLaVey 0cd5dd1e70 [TEST] Adjustments on framepacing submit calls 2026-07-29 13:26:23 -04:00
CamilleLaVey 8c828e78f2 [TEST] Refinements on RAII 2026-07-29 13:26:23 -04:00
CamilleLaVey b1adbd8e6b [TEST] ASTC fixes 2026-07-29 13:26:23 -04:00
CamilleLaVey 0fb0592a5f [TEST] Miscellaneous changes 2026-07-29 13:26:23 -04:00
CamilleLaVey b59dda3af6 Lizzie get meowed, please. 2026-07-29 13:26:23 -04:00
CamilleLaVey aec20586be [TEST] Adjusments on deferred destruction implementation 2026-07-29 13:26:23 -04:00
CamilleLaVey 6918f034e2 [TEST] Overlap image on non implemented path 2026-07-29 13:26:23 -04:00
CamilleLaVey 4b1e2d1d6a [TEST] Debug 3D image copy 2026-07-29 13:26:23 -04:00
CamilleLaVey 9318a508f7 [TEST] Fallback on TICEntry out of bounds 2026-07-29 13:26:23 -04:00
CamilleLaVey f95a9f3901 Meow 2026-07-29 13:26:23 -04:00
CamilleLaVey 1169533a74 [TEST] Debug TICEntry shader pass 2026-07-29 13:26:23 -04:00
CamilleLaVey 991ff44bd6 [TEST] Adjust ternary on pipeline barriers 2026-07-29 13:26:23 -04:00
CamilleLaVey 624f955821 [TEST] Upload on depth MSAACopy pass 2026-07-29 13:26:23 -04:00
CamilleLaVey 5c0fe5da06 [TEST] Adjustments on descriptors payloads 2026-07-29 13:26:23 -04:00
CamilleLaVey 18630fa739 [TEST] Smolios forever 2026-07-29 13:26:23 -04:00
CamilleLaVey 9752908030 [TEST] Test anti-smolios 2026-07-29 13:26:23 -04:00
CamilleLaVey a711fd99a6 [TEST] debug MSAA texture missing handling 2026-07-29 13:26:23 -04:00
CamilleLaVey f6c1404e09 Revert "[TEST] Some miscellaneous changes within buffers queue and RAII deallocations" 2026-07-29 13:26:23 -04:00
CamilleLaVey 77ffefbbbf Revert "[TEST] Another miscellaneous change 2" 2026-07-29 13:26:23 -04:00
CamilleLaVey bf13e7ce1d [TEST] Another miscellaneous change 2 2026-07-29 13:26:23 -04:00
CamilleLaVey 3cd17ec392 [TEST] Some miscellaneous changes within buffers queue and RAII deallocations 2026-07-29 13:26:22 -04:00
CamilleLaVey 7f4529b15d [TEST] Keep tuning the drain/ memory allocation 2026-07-29 13:26:22 -04:00
CamilleLaVey f649730cb1 Fix build 2026-07-29 13:26:22 -04:00
CamilleLaVey 40c91abdad Fix license headers 2026-07-29 13:26:22 -04:00
CamilleLaVey d21bb9a1b7 [TEST] Adjustments on memory collection 2026-07-29 13:26:22 -04:00
CamilleLaVey 94e814ecf1 [TEST] Adjustments on memory collection + removal dead code 2026-07-29 13:26:22 -04:00
CamilleLaVey b8135a3e47 [TEST] Refactor on memory collections 2026-07-29 13:26:22 -04:00
CamilleLaVey 8d7f992792 Enable DR 2026-07-29 13:22:20 -04:00
CamilleLaVey f199597158 [TEST] DR Off 2026-07-17 21:03:17 -04:00
CamilleLaVey 9c313fb787 [TEST] Caching for texture + pages on NCE 2026-07-17 20:15:19 -04:00
CamilleLaVey eec29b83f3 [TEST] Coalesce NCE fault write. 2026-07-17 19:56:16 -04:00
CamilleLaVey a57041d62f [TEST] Discards on MSAA depth/stencil 2026-07-17 17:57:55 -04:00
CamilleLaVey 4956bc86c3 [TEST] Hunting down recursive mutex 7 2026-07-17 17:44:24 -04:00
CamilleLaVey e8b1dc7c0b [TEST] Adjustments on CommandPools + ResetQueryPool 2026-07-17 07:52:45 -04:00
CamilleLaVey e81d170458 [TEST] Remove unnecessary memory upload 2026-07-17 07:28:30 -04:00
CamilleLaVey 7ed7e5e31d [TEST] Remove unnecessary submit 2026-07-17 03:56:02 -04:00
CamilleLaVey eb32b8766a [TEST] Hunting down recursive mutex 6 2026-07-17 03:13:55 -04:00
CamilleLaVey a484e6c34b [TEST] Hunting down recursive mutex 5 2026-07-17 03:00:53 -04:00
CamilleLaVey 84490a7d6f [TEST] Hunting down recursive mutex 4 2026-07-17 02:32:14 -04:00
CamilleLaVey 933f79af95 [TEST] Hunting down recursive mutex 3 2026-07-17 02:15:10 -04:00
CamilleLaVey f532357793 [TEST] Hunting down recursive mutex 2 2026-07-17 01:55:28 -04:00
CamilleLaVey ab92e5fa52 [TEST] Hunting down recursive mutex 1 2026-07-17 01:13:15 -04:00
CamilleLaVey 791880f9bf [TEST] Adjust records/commandBuffer 2026-07-16 22:50:08 -04:00
CamilleLaVey e058a15074 [TEST] Wire MSAA resolve to dynamic rendering native resolve 2026-07-16 20:29:18 -04:00
CamilleLaVey 6313800aee [TEST] Check on color components for masks/blending 2026-07-16 20:02:59 -04:00
CamilleLaVey 5428dbbd14 [TEST] Adjustments on blit/clears per blending object 2026-07-16 17:04:32 -04:00
CamilleLaVey 9694216ad7 [TEST] 2nd stage on dynamic rendering implementation 2026-07-16 16:53:22 -04:00
CamilleLaVey 4fbdc133dd [vulkan] Add resume/supend bits on renderpass 2026-07-16 15:50:36 -04:00
CamilleLaVey f210f16e8c [TEST] Debug on clears made by driver 2026-07-16 15:47:23 -04:00
CamilleLaVey 20bf6bc282 [vulkan] Adjustment on the framebuffer use vs non used attachments 2026-07-16 15:45:49 -04:00
CamilleLaVey 99e95eebb1 [vulkan] Initial dynamic rendering implementation 2026-07-16 15:44:06 -04:00
CamilleLaVey eb9280dedf [vulkan] 4th Vulkan Global Maintenance (#4212)
The approach on this PR moves around refactoring/ updating residual work; main issue was the whole logic behind msaa upload/download images with the constant image copy per pass required; which was gated via shaderStorageImageMultisample, which is not available on all the platforms supported including Android, making them to rely into a more heavier approach to convert msaa image copy into a non msaa image copy, not only losing precision in the conversion, but also creating bugs around the resolve of the image itself, if the supported path had to copy 2 times the same image to be actually presentable, when the fallback was enabled 3 copies were performed, not only decreasing the performance but also increasing race conditions due to the certain capabilities processed in this chain (storage_views). In order to resolve them more naturally, the constant copies were removed from the now "common" path along establishing a proper color resolve for Android and actually making them more direct.

Yet this opened a new pack of outdated handling on our current backend; since we now pass msaa images more directly (whenever a game request a msaa type of image/ which seems to be not common + each fragment resolves images on how it's actually required, adding missing depth/stencil), how we often load and store them becomes a critical issue, since our current configuration renderpass/framebuffer forces to always load and load clear whenever a new attachment it's passing through the renderpass, making this really heavier on memory bandwidth limited devices as SteamDeck and others that doesn't rely on the inmediate rendering mode (Tilers vs iGPU); to prevent over allocating data, I had to re-structure part of the current renderpass and framebuffer to actually reuse part of the data/ attachments called, with flags as DON'T_CARE, simplifying partially on how we actually call attachments including their barriers and conditions (occlusion, etc), the DON'T_CARE it's specifically useful to not only prevent calling new loads (which is a flush and heavier on tiler devices) and not only reusing the actual data store, which works on the future time, this saves memory bandwidth and gives space to actually avoid the constant pressure on SYSMEM/GMEM shader resolver path.

This maintenance also improved the current removal features logic on QCOM drivers, closing gaps between implementations on descriptors (VK_EXT_indexing_descriptor), adjusting the whole WMEL to avoid being so stricter if certain capabilities are missing to actually use this feature, leading to test and play more on how QCOM drivers are actually behaving; meanwhile the main objective is to open path for a robust and bigger implementation in the future, the changes made here also contains small bug fixes on SPIR-V logic, adding missing cases to resolve shaders (FP64 -> FP16), adding more robustness on how narrow features are actually being used and how to wired them whenever they're not available, meanwhile there are still some issues in regards on how Tegra vs Adreno can preserve unorms at FP32 and FP16, leaving better understanding on base for what VK_EXT_shader_float_controls is actually doing most of the times (flushes NaN's and Denorms equally).

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4212
2026-07-16 20:56:00 +02:00
Eden CI 8b8034a2a0 [dist, android] Update translations from Transifex for Jul 14 (#4205)
Automatic translation update for Jul 14

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4205
2026-07-14 16:04:34 +02:00
lizzie 9a0e6b3c28 [msvc] fix error with new <T>bitfields (#4196)
fixes MSVC build errors

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4196
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-07-13 21:12:13 +02:00
MaranBr defb8bf2e2 [video_core] Fix crash using RenderDoc (#4188)
This fixes a crash issue when RenderDoc is attached.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4188
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-07-12 20:25:36 +02:00
lizzie 6295d23581 [qt] remove stale compat list setting (#4202)
if we are REALLY going thru for removing the entire game compat list
subsystem, we ought to remove the setting itself

I kept this PR separate because we MAY want to introduce emuready
compatibility, so keeping the setting will keep compatibility with
older setting files from users...

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4202
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-07-12 20:24:35 +02:00
lizzie 7f85c6e282 [qt] nuke game compat list (again) (#4201)
we have no use for it
it's dead code
most of the time users just complain about how the list isn't updated
ok so what if we update it? great now its more noise added to the git, awesome
even if we constantly update it, we introduce new breakage that makes keeping
track of things extremely difficult

either we integrate with emuready (would violate self containment principles)

or we integrate with emuready in a similar fashion to eden news outlet
(where we cache the list with some TTL of like 60 mins or whatever)
that would be better respecting of "self containment" but would probably
require having a quick way to update the list additively, probably filtered to the user's
device instead of a global thing, and i dont think emuready has that granularity
nor we have the "connect this to that" for the api stuffs

another (which is what i propose here) is throw everything out of the window
until someone can come up with something better

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4201
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-07-12 09:32:11 +02:00
Eden CI 39b2c79985 [dist, android] Update translations from Transifex for Jul 11 (#4200)
Automatic translation update for Jul 11

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4200
2026-07-12 02:50:58 +02:00
MaranBr a27d35463e [video_core] Improve synchronization and refactor buffer cache timing logic (#4182)
This simplifies the GPU accuracy setting by removing the intermediate Balanced mode and setting High as the default on desktop platforms.

It introduces a dedicated setting for GPU fence behavior, allowing the synchronization policy to be configured independently of GPU accuracy.

The Vulkan buffer cache now tracks GPU recording timeline ticks and waits only when necessary, reducing unnecessary synchronization while maintaining correctness for hard-to-trace graphical bugs.

GPU buffer readback has also been refined to synchronize only the affected upload regions when needed, and default DMA behavior has been updated to align with the new GPU accuracy model.

### TL;DR

The fix for particles freezing and unfreezing in mid-air in `Super Mario Odyssey` has been improved, resulting in less of a performance hit.
This game requires the new `Enable GPU Buffer Readback` option to be enabled to fix this issue.

The vertex explosions that occurred in `Super Mario Bros. Wonder`, especially in World 4, have been completely eliminated. You can now enjoy a smooth experience without graphical glitches exploding across the screen.
This game requires the new `GPU Fence Behavior` option to be set to `Strict` to fully fix this issue.

The flickering issue inside certain Shrines in `The Legend of Zelda: Tears of the Kingdom` has also been fixed.
For now, this game requires the new `GPU Fence Behavior` option to be set to `Accurate` to fully fix this issue.

These options are intended to fix graphical bugs in games that require better synchronization behavior between CPU and GPU, so other games may be affected as well.

Co-authored-by: xbzk <xbzk@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4182
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
2026-07-10 05:25:12 +02:00
John 5606edd1a6 [Vulkan] Regression Fix - Return BindVertexBuffer2EXT old path (#4199)
Fixes Triangle SMO Smoke Regression

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4199
2026-07-10 05:00:49 +02:00
crueter cd003e5ec9 [cmake] Fix CPMUtil on Windows by avoiding a temp dir (#4195)
Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4195
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-07-10 01:21:17 +02:00
201 changed files with 23917 additions and 22955 deletions
-5
View File
@@ -112,11 +112,6 @@ Files: src/yuzu/*.ui
Copyright: 2018-2022 yuzu Emulator Project Copyright: 2018-2022 yuzu Emulator Project
License: GPL-2.0-or-later License: GPL-2.0-or-later
Files: src/yuzu/compatdb.ui
src/yuzu/main.ui
Copyright: 2014-2017 Citra Emulator Project
License: GPL-2.0-or-later
Files: src/yuzu/loading_screen.ui Files: src/yuzu/loading_screen.ui
Copyright: 2019 James Rowe <jroweboy@gmail.com> Copyright: 2019 James Rowe <jroweboy@gmail.com>
License: GPL-2.0-or-later License: GPL-2.0-or-later
-19
View File
@@ -281,25 +281,6 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/hooks/pre-commit AND NOT EXISTS ${PROJECT_SOURCE
endif() endif()
endif() endif()
set(compat_base dist/compatibility_list/compatibility_list)
set(compat_qrc ${compat_base}.qrc)
set(compat_json ${compat_base}.json)
configure_file(${PROJECT_SOURCE_DIR}/${compat_qrc}
${PROJECT_BINARY_DIR}/${compat_qrc}
COPYONLY)
if (EXISTS ${PROJECT_SOURCE_DIR}/${compat_json})
configure_file("${PROJECT_SOURCE_DIR}/${compat_json}"
"${PROJECT_BINARY_DIR}/${compat_json}"
COPYONLY)
endif()
# TODO: Compat list download
if (NOT EXISTS ${PROJECT_BINARY_DIR}/${compat_json})
file(WRITE ${PROJECT_BINARY_DIR}/${compat_json} "")
endif()
if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX)) if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX))
set(HAS_NCE 1) set(HAS_NCE 1)
add_compile_definitions(HAS_NCE=1) add_compile_definitions(HAS_NCE=1)
-354
View File
@@ -1,354 +0,0 @@
[
{
"compatibility": 0,
"directory": "the-legend-of-zelda-breath-of-the-wild",
"releases": [
{"id": "01007EF00011E000"}
],
"title": "The Legend of Zelda: Breath of the Wild"
},
{
"compatibility": 1,
"directory": "super-mario-odyssey",
"releases": [
{"id": "0100000000010000"}
],
"title": "Super Mario Odyssey"
},
{
"compatibility": 0,
"directory": "animal-crossing-new-horizons",
"releases": [
{"id": "01006F8002326000"}
],
"title": "Animal Crossing: New Horizons"
},
{
"compatibility": 1,
"directory": "pokemon-legends-z-a",
"releases": [
{"id": "0100F43008C44000"}
],
"title": "Pokémon Legends: Z-A"
},
{
"compatibility": 1,
"directory": "the-legend-of-zelda-tears-of-the-kingdom",
"releases": [
{"id": "0100F2C0115B6000"}
],
"title": "The Legend of Zelda: Tears of the Kingdom"
},
{
"compatibility": 0,
"directory": "super-mario-galaxy",
"releases": [
{"id": "010099C022B96000"}
],
"title": "Super Mario Galaxy"
},
{
"compatibility": 3,
"directory": "star-wars-republic-commando",
"releases": [
{"id": "0100FA10115F8000"}
],
"title": "Star Wars: Republic Commando"
},
{
"compatibility": 0,
"directory": "doki-doki-literature-club-plus",
"releases": [
{"id": "010086901543E000"}
],
"title": "Doki Doki Literature Club Plus"
},
{
"compatibility": 1,
"directory": "pokemon-scarlet",
"releases": [
{"id": "0100A3D008C5C000"}
],
"title": "Pokémon Scarlet"
},
{
"compatibility": 1,
"directory": "pokemon-violet",
"releases": [
{"id": "01008F6008C5E000"}
],
"title": "Pokémon Violet"
},
{
"compatibility": 2,
"directory": "pokemon-legends-arceus",
"releases": [
{"id": "01001E300D162000"}
],
"title": "Pokémon Legends: Arceus"
},
{
"compatibility": 0,
"directory": "splatoon-2",
"releases": [
{"id": "01003BC0000A0000"}
],
"title": "Splatoon 2"
},
{
"compatibility": 1,
"directory": "super-smash-bros-ultimate",
"releases": [
{"id": "01006A800016E000"}
],
"title": "Super Smash Bros. Ultimate"
},
{
"compatibility": 0,
"directory": "mario-kart-8-deluxe",
"releases": [
{"id": "0100152000022000"}
],
"title": "Mario Kart 8 Deluxe"
},
{
"compatibility": 0,
"directory": "splatoon-3",
"releases": [
{"id": "0100C2500FC20000"}
],
"title": "Splatoon 3"
},
{
"compatibility": 0,
"directory": "new-super-mario-bros-u-deluxe",
"releases": [
{"id": "0100EA80032EA000"}
],
"title": "New Super Mario Bros. U Deluxe"
},
{
"compatibility": 0,
"directory": "hyrule-warriors-age-of-calamity",
"releases": [
{"id": "01002B00111A2000"}
],
"title": "Hyrule Warriors: Age of Calamity"
},
{
"compatibility": 2,
"directory": "luigis-mansion-3",
"releases": [
{"id": "0100DCA0064A6000"}
],
"title": "Luigi's Mansion 3"
},
{
"compatibility": 2,
"directory": "pokemon-brilliant-diamond",
"releases": [
{"id": "0100000011D90000"}
],
"title": "Pokémon Brilliant Diamond"
},
{
"compatibility": 2,
"directory": "pokemon-shining-pearl",
"releases": [
{"id": "010018E011D92000"}
],
"title": "Pokémon Shining Pearl"
},
{
"compatibility": 1,
"directory": "super-mario-3d-world-bowsers-fury",
"releases": [
{"id": "010028600EBDA000"}
],
"title": "Super Mario 3D World + Bowser's Fury"
},
{
"compatibility": 0,
"directory": "the-legend-of-zelda-links-awakening",
"releases": [
{"id": "01006BB00C6F0000"}
],
"title": "The Legend of Zelda: Link's Awakening"
},
{
"compatibility": 1,
"directory": "fire-emblem-three-houses",
"releases": [
{"id": "010055D009F78000"}
],
"title": "Fire Emblem: Three Houses"
},
{
"compatibility": 2,
"directory": "metroid-dread",
"releases": [
{"id": "010093801237C000"}
],
"title": "Metroid Dread"
},
{
"compatibility": 0,
"directory": "paper-mario-the-origami-king",
"releases": [
{"id": "0100A3900C3E2000"}
],
"title": "Paper Mario: The Origami King"
},
{
"compatibility": 1,
"directory": "xenoblade-chronicles-definitive-edition",
"releases": [
{"id": "0100FF500E34A000"}
],
"title": "Xenoblade Chronicles: Definitive Edition"
},
{
"compatibility": 2,
"directory": "xenoblade-chronicles-3",
"releases": [
{"id": "010074F013262000"}
],
"title": "Xenoblade Chronicles 3"
},
{
"compatibility": 1,
"directory": "pikmin-3-deluxe",
"releases": [
{"id": "0100F8600D4B0000"}
],
"title": "Pikmin 3 Deluxe"
},
{
"compatibility": 0,
"directory": "donkey-kong-country-tropical-freeze",
"releases": [
{"id": "0100C1F0054B6000"}
],
"title": "Donkey Kong Country: Tropical Freeze"
},
{
"compatibility": 1,
"directory": "kirby-and-the-forgotten-land",
"releases": [
{"id": "01004D300C5AE000"}
],
"title": "Kirby and the Forgotten Land"
},
{
"compatibility": 2,
"directory": "mario-party-superstars",
"releases": [
{"id": "01006B400D8B2000"}
],
"title": "Mario Party Superstars"
},
{
"compatibility": 0,
"directory": "clubhouse-games-51-worldwide-classics",
"releases": [
{"id": "0100F8600D4B0000"}
],
"title": "Clubhouse Games: 51 Worldwide Classics"
},
{
"compatibility": 1,
"directory": "ring-fit-adventure",
"releases": [
{"id": "01006B300BAF8000"}
],
"title": "Ring Fit Adventure"
},
{
"compatibility": 2,
"directory": "arms",
"releases": [
{"id": "01009B500007C000"}
],
"title": "ARMS"
},
{
"compatibility": 0,
"directory": "super-mario-maker-2",
"releases": [
{"id": "01009B90006DC000"}
],
"title": "Super Mario Maker 2"
},
{
"compatibility": 0,
"directory": "pokemon-lets-go-pikachu",
"releases": [
{"id": "010003F003A34000"}
],
"title": "Pokémon: Let's Go, Pikachu!"
},
{
"compatibility": 1,
"directory": "pokemon-lets-go-eevee",
"releases": [
{"id": "0100187003A36000"}
],
"title": "Pokémon: Let's Go, Eevee!"
},
{
"compatibility": 2,
"directory": "pokemon-sword",
"releases": [
{"id": "0100ABF008968000"}
],
"title": "Pokémon Sword"
},
{
"compatibility": 2,
"directory": "pokemon-shield",
"releases": [
{"id": "01008DB008C2C000"}
],
"title": "Pokémon Shield"
},
{
"compatibility": 1,
"directory": "new-pokemon-snap",
"releases": [
{"id": "0100F4300C182000"}
],
"title": "New Pokémon Snap"
},
{
"compatibility": 0,
"directory": "mario-golf-super-rush",
"releases": [
{"id": "0100C9C00E25C000"}
],
"title": "Mario Golf: Super Rush"
},
{
"compatibility": 1,
"directory": "mario-tennis-aces",
"releases": [
{"id": "0100BDE00862A000"}
],
"title": "Mario Tennis Aces"
},
{
"compatibility": 2,
"directory": "wario-ware-get-it-together",
"releases": [
{"id": "0100563010F22000"}
],
"title": "WarioWare: Get It Together!"
},
{
"compatibility": 0,
"directory": "big-brain-academy-brain-vs-brain",
"releases": [
{"id": "0100190010F24000"}
],
"title": "Big Brain Academy: Brain vs. Brain"
}
]
-5
View File
@@ -1,5 +0,0 @@
<RCC>
<qresource prefix="compatibility_list">
<file>compatibility_list.json</file>
</qresource>
</RCC>
+603 -755
View File
File diff suppressed because it is too large Load Diff
+592 -749
View File
File diff suppressed because it is too large Load Diff
+578 -735
View File
File diff suppressed because it is too large Load Diff
+578 -735
View File
File diff suppressed because it is too large Load Diff
+581 -741
View File
File diff suppressed because it is too large Load Diff
+578 -735
View File
File diff suppressed because it is too large Load Diff
+581 -743
View File
File diff suppressed because it is too large Load Diff
+578 -735
View File
File diff suppressed because it is too large Load Diff
+579 -736
View File
File diff suppressed because it is too large Load Diff
+578 -735
View File
File diff suppressed because it is too large Load Diff
+578 -735
View File
File diff suppressed because it is too large Load Diff
+580 -739
View File
File diff suppressed because it is too large Load Diff
+578 -735
View File
File diff suppressed because it is too large Load Diff
+625 -777
View File
File diff suppressed because it is too large Load Diff
+578 -735
View File
File diff suppressed because it is too large Load Diff
+578 -735
View File
File diff suppressed because it is too large Load Diff
+580 -739
View File
File diff suppressed because it is too large Load Diff
+580 -740
View File
File diff suppressed because it is too large Load Diff
+578 -735
View File
File diff suppressed because it is too large Load Diff
+580 -740
View File
File diff suppressed because it is too large Load Diff
+580 -739
View File
File diff suppressed because it is too large Load Diff
+580 -737
View File
File diff suppressed because it is too large Load Diff
+580 -740
View File
File diff suppressed because it is too large Load Diff
+578 -735
View File
File diff suppressed because it is too large Load Diff
+578 -735
View File
File diff suppressed because it is too large Load Diff
+581 -741
View File
File diff suppressed because it is too large Load Diff
+578 -735
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -65,7 +65,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "dev.eden.eden_emulator" applicationId = "dev.eden.eden_emulator"
minSdk = 24 minSdk = 33
targetSdk = 36 targetSdk = 36
versionName = getGitVersion() versionName = getGitVersion()
versionCode = autoVersion versionCode = autoVersion
@@ -218,6 +218,8 @@ object NativeLibrary {
external fun logSettings() external fun logSettings()
external fun refreshThreadPolicies()
external fun getDebugKnobAt(index: Int): Boolean external fun getDebugKnobAt(index: Int): Boolean
/** /**
@@ -16,7 +16,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_USE_SPEED_LIMIT("use_speed_limit"), RENDERER_USE_SPEED_LIMIT("use_speed_limit"),
USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"), USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"),
SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"), SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"),
ANTIFLICKER("antiflicker"),
FIX_BLOOM_EFFECTS("fix_bloom_effects"), FIX_BLOOM_EFFECTS("fix_bloom_effects"),
EMULATE_BGR565("emulate_bgr565"), EMULATE_BGR565("emulate_bgr565"),
RESCALE_HACK("rescale_hack"), RESCALE_HACK("rescale_hack"),
@@ -28,6 +27,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"), RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"),
RENDERER_ASYNC_PRESENTATION("async_presentation"), RENDERER_ASYNC_PRESENTATION("async_presentation"),
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"), RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
RENDERER_UNIFIED_MEMORY("use_unified_memory"),
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"), RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
ENABLE_BUFFER_HISTORY("enable_buffer_history"), ENABLE_BUFFER_HISTORY("enable_buffer_history"),
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"), USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
@@ -37,6 +37,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_DEBUG("debug"), RENDERER_DEBUG("debug"),
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"), RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"), RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
RENDERER_DYNAMIC_RENDERING("dynamic_rendering"),
RENDERER_WORKGROUP_MEMORY_EXPLICIT_LAYOUT("workgroup_memory_explicit_layout"),
RENDERER_SAMPLE_SHADING("sample_shading"), RENDERER_SAMPLE_SHADING("sample_shading"),
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"), GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
PICTURE_IN_PICTURE("picture_in_picture"), PICTURE_IN_PICTURE("picture_in_picture"),
@@ -27,6 +27,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
RENDERER_DYNA_STATE("dyna_state"), RENDERER_DYNA_STATE("dyna_state"),
DMA_ACCURACY("dma_accuracy"), DMA_ACCURACY("dma_accuracy"),
GPU_FENCE_BEHAVIOR("gpu_fence_behavior"),
FRAME_PACING_MODE("frame_pacing_mode"), FRAME_PACING_MODE("frame_pacing_mode"),
AUDIO_OUTPUT_ENGINE("output_engine"), AUDIO_OUTPUT_ENGINE("output_engine"),
MAX_ANISOTROPY("max_anisotropy"), MAX_ANISOTROPY("max_anisotropy"),
@@ -155,6 +155,20 @@ abstract class SettingsItem(
descriptionId = R.string.vertex_input_dynamic_state_description descriptionId = R.string.vertex_input_dynamic_state_description
) )
) )
put(
SwitchSetting(
BooleanSetting.RENDERER_DYNAMIC_RENDERING,
titleId = R.string.dynamic_rendering,
descriptionId = R.string.dynamic_rendering_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_WORKGROUP_MEMORY_EXPLICIT_LAYOUT,
titleId = R.string.workgroup_memory_explicit_layout,
descriptionId = R.string.workgroup_memory_explicit_layout_description
)
)
put( put(
SliderSetting( SliderSetting(
IntSetting.RENDERER_SAMPLE_SHADING, IntSetting.RENDERER_SAMPLE_SHADING,
@@ -594,7 +608,7 @@ abstract class SettingsItem(
IntSetting.ANDROID_PIPELINE_WORKERS, IntSetting.ANDROID_PIPELINE_WORKERS,
titleId = R.string.pipeline_worker_cores, titleId = R.string.pipeline_worker_cores,
descriptionId = R.string.pipeline_worker_cores_description, descriptionId = R.string.pipeline_worker_cores_description,
min = 4, min = 2,
max = 8, max = 8,
units = "cores" units = "cores"
) )
@@ -669,6 +683,15 @@ abstract class SettingsItem(
valuesId = R.array.dmaAccuracyValues valuesId = R.array.dmaAccuracyValues
) )
) )
put(
SingleChoiceSetting(
IntSetting.GPU_FENCE_BEHAVIOR,
titleId = R.string.gpu_fence_behavior,
descriptionId = R.string.gpu_fence_behavior_description,
choicesId = R.array.gpuFenceBehaviorNames,
valuesId = R.array.gpuFenceBehaviorValues
)
)
put( put(
SwitchSetting( SwitchSetting(
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
@@ -676,6 +699,13 @@ abstract class SettingsItem(
descriptionId = R.string.renderer_asynchronous_shaders_description descriptionId = R.string.renderer_asynchronous_shaders_description
) )
) )
put(
SwitchSetting(
BooleanSetting.RENDERER_UNIFIED_MEMORY,
titleId = R.string.renderer_unified_memory,
descriptionId = R.string.renderer_unified_memory_description
)
)
put( put(
SingleChoiceSetting( SingleChoiceSetting(
IntSetting.FAST_GPU_TIME, IntSetting.FAST_GPU_TIME,
@@ -757,13 +787,6 @@ abstract class SettingsItem(
descriptionId = R.string.skip_cpu_inner_invalidation_description descriptionId = R.string.skip_cpu_inner_invalidation_description
) )
) )
put(
SwitchSetting(
BooleanSetting.ANTIFLICKER,
titleId = R.string.antiflicker,
descriptionId = R.string.antiflicker_description
)
)
put( put(
SwitchSetting( SwitchSetting(
BooleanSetting.FIX_BLOOM_EFFECTS, BooleanSetting.FIX_BLOOM_EFFECTS,
@@ -283,6 +283,7 @@ class SettingsFragmentPresenter(
add(IntSetting.RENDERER_ACCURACY.key) add(IntSetting.RENDERER_ACCURACY.key)
add(IntSetting.DMA_ACCURACY.key) add(IntSetting.DMA_ACCURACY.key)
add(IntSetting.GPU_FENCE_BEHAVIOR.key)
add(IntSetting.MAX_ANISOTROPY.key) add(IntSetting.MAX_ANISOTROPY.key)
add(IntSetting.RENDERER_VRAM_USAGE_MODE.key) add(IntSetting.RENDERER_VRAM_USAGE_MODE.key)
add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key) add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key)
@@ -299,11 +300,11 @@ class SettingsFragmentPresenter(
add(IntSetting.FAST_GPU_TIME.key) add(IntSetting.FAST_GPU_TIME.key)
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key) add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
add(BooleanSetting.ANTIFLICKER.key)
add(BooleanSetting.FIX_BLOOM_EFFECTS.key) add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
add(BooleanSetting.EMULATE_BGR565.key) add(BooleanSetting.EMULATE_BGR565.key)
add(BooleanSetting.RESCALE_HACK.key) add(BooleanSetting.RESCALE_HACK.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key) add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
add(BooleanSetting.RENDERER_UNIFIED_MEMORY.key)
add(IntSetting.ANDROID_PIPELINE_WORKERS.key) add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key) add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key) add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
@@ -313,6 +314,8 @@ class SettingsFragmentPresenter(
add(IntSetting.RENDERER_DYNA_STATE.key) add(IntSetting.RENDERER_DYNA_STATE.key)
add(BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE.key) add(BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE.key)
add(BooleanSetting.RENDERER_DYNAMIC_RENDERING.key)
add(BooleanSetting.RENDERER_WORKGROUP_MEMORY_EXPLICIT_LAYOUT.key)
add(IntSetting.RENDERER_SAMPLE_SHADING.key) add(IntSetting.RENDERER_SAMPLE_SHADING.key)
add(HeaderSetting(R.string.display)) add(HeaderSetting(R.string.display))
@@ -1451,6 +1451,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
NativeLibrary.refreshThreadPolicies()
val b = _binding ?: return val b = _binding ?: return
updateStatsPosition(IntSetting.PERF_OVERLAY_POSITION.getInt()) updateStatsPosition(IntSetting.PERF_OVERLAY_POSITION.getInt())
updateSocPosition(IntSetting.SOC_OVERLAY_POSITION.getInt()) updateSocPosition(IntSetting.SOC_OVERLAY_POSITION.getInt())
@@ -147,13 +147,6 @@ namespace AndroidSettings {
&show_performance_overlay}; &show_performance_overlay};
Settings::Setting<s32> pipeline_worker_count{linkage, 4, "pipeline_worker_count",
Settings::Category::Android,
Settings::Specialization::Default,
true,
true};
Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay", Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
Settings::Category::Overlay}; Settings::Category::Overlay};
Settings::Setting<bool> overlay_snap_to_grid{linkage, false, "overlay_snap_to_grid", Settings::Setting<bool> overlay_snap_to_grid{linkage, false, "overlay_snap_to_grid",
+5
View File
@@ -50,6 +50,7 @@ extern "C" {
#include "common/scope_exit.h" #include "common/scope_exit.h"
#include "common/settings.h" #include "common/settings.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "common/thread.h"
#include "frontend_common/play_time_manager.h" #include "frontend_common/play_time_manager.h"
#include "core/constants.h" #include "core/constants.h"
#include "core/core.h" #include "core/core.h"
@@ -1182,6 +1183,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj
Settings::LogSettings(); Settings::LogSettings();
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jobject jobj) {
Common::RefreshThreadPolicies();
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) { jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index))); return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
} }
@@ -476,6 +476,8 @@
<string name="renderer_accuracy_description">يتحكم في وضع محاكاة وحدة معالجة الرسومات. تعمل معظم الألعاب بشكل جيد مع وضعي سريع أو متوازن، لكن الوضع الدقيق لا يزال مطلوبًا لبعض الألعاب. تميل الجسيمات إلى العرض بشكل صحيح فقط عند استخدام الوضع الدقيق.</string> <string name="renderer_accuracy_description">يتحكم في وضع محاكاة وحدة معالجة الرسومات. تعمل معظم الألعاب بشكل جيد مع وضعي سريع أو متوازن، لكن الوضع الدقيق لا يزال مطلوبًا لبعض الألعاب. تميل الجسيمات إلى العرض بشكل صحيح فقط عند استخدام الوضع الدقيق.</string>
<string name="dma_accuracy">دقة DMA</string> <string name="dma_accuracy">دقة DMA</string>
<string name="dma_accuracy_description">يتحكم في دقة DMA. يمكن أن تؤدي الدقة الآمنة إلى حل المشكلات في بعض الألعاب، ولكنها قد تؤثر أيضًا على الأداء في بعض الحالات. إذا لم تكن متأكدًا، فاترك هذا الخيار على الإعداد الافتراضي.</string> <string name="dma_accuracy_description">يتحكم في دقة DMA. يمكن أن تؤدي الدقة الآمنة إلى حل المشكلات في بعض الألعاب، ولكنها قد تؤثر أيضًا على الأداء في بعض الحالات. إذا لم تكن متأكدًا، فاترك هذا الخيار على الإعداد الافتراضي.</string>
<string name="gpu_fence_behavior">سلوك حاجز وحدة معالجة الرسومات</string>
<string name="gpu_fence_behavior_description">يتحكم في سلوك تزامن حاجز وحدة معالجة الرسوميات. الخيار الفوري هو الأسرع، لكنه قد يسبب بعض المشاكل. الخيار المتوازن يقدم توافقًا أفضل وقد يصلح مشاكل في بعض الألعاب. الخيار الدقيق يحسن التوافق أكثر لكنه قد يقلل الأداء قليلاً. الخيار الصارم هو الأبطأ، لكنه قد يصلح المشاكل التي تتطلب تزامنًا أكثر صرامة. الإعداد الافتراضي يتبع إعداد دقة وحدة معالجة الرسوميات.</string>
<string name="anisotropic_filtering">تصفية متباينة الخواص</string> <string name="anisotropic_filtering">تصفية متباينة الخواص</string>
<string name="anisotropic_filtering_description">يحسن جودة الأنسجة عند عرضها بزوايا مائلة</string> <string name="anisotropic_filtering_description">يحسن جودة الأنسجة عند عرضها بزوايا مائلة</string>
<string name="vram_usage_mode">وضع استخدام ذاكرة VRAM</string> <string name="vram_usage_mode">وضع استخدام ذاكرة VRAM</string>
@@ -497,6 +499,8 @@
<string name="renderer_reactive_flushing_description">يحسن دقة العرض في بعض الألعاب على حساب الأداء.</string> <string name="renderer_reactive_flushing_description">يحسن دقة العرض في بعض الألعاب على حساب الأداء.</string>
<string name="enable_buffer_history">تمكين سجل التخزين المؤقت</string> <string name="enable_buffer_history">تمكين سجل التخزين المؤقت</string>
<string name="enable_buffer_history_description">يُتيح هذا الخيار الوصول إلى حالات التخزين المؤقت السابقة. وقد يُحسّن جودة العرض وثبات الأداء في بعض الألعاب.</string> <string name="enable_buffer_history_description">يُتيح هذا الخيار الوصول إلى حالات التخزين المؤقت السابقة. وقد يُحسّن جودة العرض وثبات الأداء في بعض الألعاب.</string>
<string name="enable_gpu_buffer_readback">تفعيل قراءة مخزن وحدة معالجة الرسومات</string>
<string name="enable_gpu_buffer_readback_description">يحافظ هذا النظام على بيانات المخزن المؤقت المُعدّلة بواسطة وحدة معالجة الرسومات عن طريق قراءتها مرة أخرى قبل التحميل. تتطلب بعض الألعاب ذلك لعرض بعض التأثيرات بشكل صحيح. قد يُسبب ذلك مشاكل إذا لم يتمكن الجهاز من التعامل مع عبء العمل الإضافي.</string>
<string name="use_optimized_vertex_buffers">مخازن الرؤوس المُحسّنة</string> <string name="use_optimized_vertex_buffers">مخازن الرؤوس المُحسّنة</string>
<string name="use_optimized_vertex_buffers_description">يُتيح ربطًا مُحسَّنًا لمخازن الرؤوس لتحسين الأداء. يتطلب برامج تشغيل Mesa 26.0+ Turnip/ برامج تشغيل QCOM. قد يتعطل على برامج تشغيل Turnip القديمة (25.3 وما دون).</string> <string name="use_optimized_vertex_buffers_description">يُتيح ربطًا مُحسَّنًا لمخازن الرؤوس لتحسين الأداء. يتطلب برامج تشغيل Mesa 26.0+ Turnip/ برامج تشغيل QCOM. قد يتعطل على برامج تشغيل Turnip القديمة (25.3 وما دون).</string>
@@ -506,8 +510,6 @@
<string name="fast_gpu_time_description">يُجبر هذا الخيار معظم الألعاب على العمل بأعلى دقة عرض أصلية. استخدم 256 للحصول على أقصى أداء و512 للحصول على أعلى جودة رسومات.</string> <string name="fast_gpu_time_description">يُجبر هذا الخيار معظم الألعاب على العمل بأعلى دقة عرض أصلية. استخدم 256 للحصول على أقصى أداء و512 للحصول على أعلى جودة رسومات.</string>
<string name="skip_cpu_inner_invalidation">تخطي إبطال صلاحية وحدة المعالجة المركزية الداخلية</string> <string name="skip_cpu_inner_invalidation">تخطي إبطال صلاحية وحدة المعالجة المركزية الداخلية</string>
<string name="skip_cpu_inner_invalidation_description">يتخطى بعض عمليات إبطال ذاكرة التخزين المؤقتة من جانب وحدة المعالجة المركزية أثناء تحديثات الذاكرة، مما يقلل من استخدام وحدة المعالجة المركزية ويحسن أداءها. قد يتسبب ذلك في حدوث أعطال أو تعطل في بعض الألعاب.</string> <string name="skip_cpu_inner_invalidation_description">يتخطى بعض عمليات إبطال ذاكرة التخزين المؤقتة من جانب وحدة المعالجة المركزية أثناء تحديثات الذاكرة، مما يقلل من استخدام وحدة المعالجة المركزية ويحسن أداءها. قد يتسبب ذلك في حدوث أعطال أو تعطل في بعض الألعاب.</string>
<string name="antiflicker">مضاد الوميض</string>
<string name="antiflicker_description">يُجبر هذا الوضع وظائف وحدة معالجة الرسومات على الانتظار حتى يتم إرسال العمل إليها. استخدمه مع وضع وحدة معالجة الرسومات السريع لتجنب الوميض مع تأثير أقل على الأداء.</string>
<string name="fix_bloom_effects">إصلاح تأثيرات التوهج</string> <string name="fix_bloom_effects">إصلاح تأثيرات التوهج</string>
<string name="fix_bloom_effects_description">يقلل من ضبابية التوهج في LA/EOW (Adreno A6XX - A7XX/ Turnip)، ويزيل التوهج في Burnout. تحذير: قد يسبب تشوهات رسومية في ألعاب أخرى.</string> <string name="fix_bloom_effects_description">يقلل من ضبابية التوهج في LA/EOW (Adreno A6XX - A7XX/ Turnip)، ويزيل التوهج في Burnout. تحذير: قد يسبب تشوهات رسومية في ألعاب أخرى.</string>
<string name="emulate_bgr565">محاكاة BGR565</string> <string name="emulate_bgr565">محاكاة BGR565</string>
@@ -573,6 +575,12 @@
<string name="gpu_log_level_description">مستوى التفاصيل لسجلات وحدة معالجة الرسومات (كلما زاد المستوى، زادت التفاصيل وزادت التكاليف الإضافية)</string> <string name="gpu_log_level_description">مستوى التفاصيل لسجلات وحدة معالجة الرسومات (كلما زاد المستوى، زادت التفاصيل وزادت التكاليف الإضافية)</string>
<string name="gpu_log_vulkan_calls">تسجيل استدعاءات واجهة برمجة تطبيقات Vulkan</string> <string name="gpu_log_vulkan_calls">تسجيل استدعاءات واجهة برمجة تطبيقات Vulkan</string>
<string name="gpu_log_vulkan_calls_description">تتبع جميع استدعاءات واجهة برمجة تطبيقات Vulkan في المخزن المؤقت الحلقي</string> <string name="gpu_log_vulkan_calls_description">تتبع جميع استدعاءات واجهة برمجة تطبيقات Vulkan في المخزن المؤقت الحلقي</string>
<string name="gpu_log_shader_dumps">تفريغ مظللات SPIR-V</string>
<string name="gpu_log_shader_dumps_description">احفظ ملفات SPIR-V الثنائية المُعاد تجميعها (.spv) في مجلد التفريغ. افحصها باستخدام spirv-dis/spirv-cross/spirv-val.</string>
<string name="dump_guest_shaders">تظليلات ضيف التفريغ (ماكسويل)</string>
<string name="dump_guest_shaders_description">احفظ ملفات بايت كود برنامج التظليل الضيف الخاص بـ «ماكسويل» (*.ash) في مجلد «dump». افحصها باستخدام nvdisasm.</string>
<string name="dump_macros">تفريغ ماكرو ماكسويل</string>
<string name="dump_macros_description">احفظ ملفات برامج ماكرو ماكسويل (*.macro) في مجلد «dump». افحصها باستخدام برنامج «envydis».</string>
<string name="gpu_log_memory_tracking">تتبع ذاكرة وحدة معالجة الرسومات</string> <string name="gpu_log_memory_tracking">تتبع ذاكرة وحدة معالجة الرسومات</string>
<string name="gpu_log_memory_tracking_description">مراقبة تخصيصات ذاكرة وحدة معالجة الرسومات وإلغاء تخصيصها</string> <string name="gpu_log_memory_tracking_description">مراقبة تخصيصات ذاكرة وحدة معالجة الرسومات وإلغاء تخصيصها</string>
<string name="gpu_log_driver_debug">معلومات تصحيح أخطاء برنامج التشغيل</string> <string name="gpu_log_driver_debug">معلومات تصحيح أخطاء برنامج التشغيل</string>
@@ -1000,6 +1008,13 @@
<string name="dma_accuracy_unsafe">غير آمن</string> <string name="dma_accuracy_unsafe">غير آمن</string>
<string name="dma_accuracy_safe">آمن</string> <string name="dma_accuracy_safe">آمن</string>
<!-- GPU Fence Behavior -->
<string name="gpu_fence_behavior_default">افتراضي</string>
<string name="gpu_fence_behavior_immediate">فوري</string>
<string name="gpu_fence_behavior_balanced">متوازن</string>
<string name="gpu_fence_behavior_accurate">دقيق</string>
<string name="gpu_fence_behavior_strict">صارم</string>
<string name="vram_usage_conservative">محافظ</string> <string name="vram_usage_conservative">محافظ</string>
<string name="vram_usage_aggressive">عدواني</string> <string name="vram_usage_aggressive">عدواني</string>
@@ -470,6 +470,7 @@
<string name="renderer_accuracy_description">Controla el modo de la emulación de la GPU. La mayoría de los juegos se renderizan correctamente en los modos Rápido o Equilibrado, pero algunos requieren Preciso. Las partículas tienden a renderizarse correctamente solo con el modo Preciso.</string> <string name="renderer_accuracy_description">Controla el modo de la emulación de la GPU. La mayoría de los juegos se renderizan correctamente en los modos Rápido o Equilibrado, pero algunos requieren Preciso. Las partículas tienden a renderizarse correctamente solo con el modo Preciso.</string>
<string name="dma_accuracy">Precisión de DMA</string> <string name="dma_accuracy">Precisión de DMA</string>
<string name="dma_accuracy_description">Controla la precisión de DMA. La precisión segura puede solucionar problemas en algunos juegos, pero también puede afectar al rendimiento en algunos casos. Si no está seguro, déjelo en Predeterminado.</string> <string name="dma_accuracy_description">Controla la precisión de DMA. La precisión segura puede solucionar problemas en algunos juegos, pero también puede afectar al rendimiento en algunos casos. Si no está seguro, déjelo en Predeterminado.</string>
<string name="gpu_fence_behavior">Comportamiento de vallado de la GPU</string>
<string name="anisotropic_filtering">Filtrado anisotrópico</string> <string name="anisotropic_filtering">Filtrado anisotrópico</string>
<string name="anisotropic_filtering_description">Mejora la calidad de las texturas al ser observadas desde ángulos oblicuos</string> <string name="anisotropic_filtering_description">Mejora la calidad de las texturas al ser observadas desde ángulos oblicuos</string>
<string name="vram_usage_mode">Modo de uso de VRAM</string> <string name="vram_usage_mode">Modo de uso de VRAM</string>
@@ -502,8 +503,6 @@
<string name="fast_gpu_time_description">Fuerza a la mayoría de los juegos a ejecutarse a su resolución nativa más alta. Usa 256 para un máximo rendimiento y 512 para una fidelidad gráfica óptima.</string> <string name="fast_gpu_time_description">Fuerza a la mayoría de los juegos a ejecutarse a su resolución nativa más alta. Usa 256 para un máximo rendimiento y 512 para una fidelidad gráfica óptima.</string>
<string name="skip_cpu_inner_invalidation">Omitir invalidación interna de la CPU</string> <string name="skip_cpu_inner_invalidation">Omitir invalidación interna de la CPU</string>
<string name="skip_cpu_inner_invalidation_description">Omite ciertas invalidaciones de caché de la CPU durante las actualizaciones de memoria, lo que reduce el uso de la CPU y mejora su rendimiento. Esto puede causar fallos o bloqueos en algunos juegos.</string> <string name="skip_cpu_inner_invalidation_description">Omite ciertas invalidaciones de caché de la CPU durante las actualizaciones de memoria, lo que reduce el uso de la CPU y mejora su rendimiento. Esto puede causar fallos o bloqueos en algunos juegos.</string>
<string name="antiflicker">Antiparpadeo</string>
<string name="antiflicker_description">Fuerza a las funciones de devolución de llamada de la GPU a esperar a que se envíen las tareas a la GPU.\nÚsalo con el modo de GPU rápida para evitar el parpadeo con un menor impacto en el rendimiento.</string>
<string name="fix_bloom_effects">Arreglar los efectos de resplandor</string> <string name="fix_bloom_effects">Arreglar los efectos de resplandor</string>
<string name="fix_bloom_effects_description">Reduce el efecto de resplandor en LA/EOW (Adreno A6XX - A7XX/ Turnip), elimina el resplandor en Burnout. Advertencia: puede causar artefactos gráficos en otros juegos.</string> <string name="fix_bloom_effects_description">Reduce el efecto de resplandor en LA/EOW (Adreno A6XX - A7XX/ Turnip), elimina el resplandor en Burnout. Advertencia: puede causar artefactos gráficos en otros juegos.</string>
<string name="emulate_bgr565">Emular BGR565</string> <string name="emulate_bgr565">Emular BGR565</string>
@@ -1002,6 +1001,13 @@
<string name="dma_accuracy_unsafe">Inseguro</string> <string name="dma_accuracy_unsafe">Inseguro</string>
<string name="dma_accuracy_safe">Seguro</string> <string name="dma_accuracy_safe">Seguro</string>
<!-- GPU Fence Behavior -->
<string name="gpu_fence_behavior_default">Predeterminado</string>
<string name="gpu_fence_behavior_immediate">Inmediato</string>
<string name="gpu_fence_behavior_balanced">Equilibrado</string>
<string name="gpu_fence_behavior_accurate">Preciso</string>
<string name="gpu_fence_behavior_strict">Estricto</string>
<string name="vram_usage_conservative">Conservador</string> <string name="vram_usage_conservative">Conservador</string>
<string name="vram_usage_aggressive">Agresivo</string> <string name="vram_usage_aggressive">Agresivo</string>
@@ -499,8 +499,6 @@
<string name="fast_gpu_time_description">Принудительно запускает большинство игр в их максимальном нативном разрешении. Используйте значение 256 для максимальной производительности и 512 для максимального качества графики.</string> <string name="fast_gpu_time_description">Принудительно запускает большинство игр в их максимальном нативном разрешении. Используйте значение 256 для максимальной производительности и 512 для максимального качества графики.</string>
<string name="skip_cpu_inner_invalidation">Пропустить внутреннюю инвалидацию ЦП</string> <string name="skip_cpu_inner_invalidation">Пропустить внутреннюю инвалидацию ЦП</string>
<string name="skip_cpu_inner_invalidation_description">Пропускает некоторые инвалидации кэша на стороне ЦП при обновлениях памяти, уменьшая нагрузку на процессор и повышая производительность. Может вызывать сбои в некоторых играх.</string> <string name="skip_cpu_inner_invalidation_description">Пропускает некоторые инвалидации кэша на стороне ЦП при обновлениях памяти, уменьшая нагрузку на процессор и повышая производительность. Может вызывать сбои в некоторых играх.</string>
<string name="antiflicker">Анти-мерцание</string>
<string name="antiflicker_description">Принудительно заставляет обратные вызовы ГПУ-фильтра ожидать выполнения отправленных задач на ГПУ. Используйте с Быстрым режимом ГПУ, что бы избежать мерцаний с меньшим влиянием на производительность.</string>
<string name="fix_bloom_effects">Исправить эффекты размытия</string> <string name="fix_bloom_effects">Исправить эффекты размытия</string>
<string name="fix_bloom_effects_description">Частично убирает размытие в LA/EOW (Adreno A6XX - A7XX/ Turnip), полностью отключает его в Burnout. Внимание: может вызывать графические артефакты в других играх.</string> <string name="fix_bloom_effects_description">Частично убирает размытие в LA/EOW (Adreno A6XX - A7XX/ Turnip), полностью отключает его в Burnout. Внимание: может вызывать графические артефакты в других играх.</string>
<string name="emulate_bgr565">Эмулировать BGR565</string> <string name="emulate_bgr565">Эмулировать BGR565</string>
@@ -502,8 +502,6 @@
<string name="fast_gpu_time_description">Примушує більшість ігор працювати на їхній максимальній нативній роздільності. Використовуйте 256 для максимальної продуктивності та 512 для найкращої якості.</string> <string name="fast_gpu_time_description">Примушує більшість ігор працювати на їхній максимальній нативній роздільності. Використовуйте 256 для максимальної продуктивності та 512 для найкращої якості.</string>
<string name="skip_cpu_inner_invalidation">Пропустити внутрішнє інвалідування CPU</string> <string name="skip_cpu_inner_invalidation">Пропустити внутрішнє інвалідування CPU</string>
<string name="skip_cpu_inner_invalidation_description">Пропускає деякі інвалідації кешу на стороні CPU під час оновлення пам\'яті, зменшуючи навантаження на процесор і покращуючи продуктивність. Може спричинити збої в деяких іграх.</string> <string name="skip_cpu_inner_invalidation_description">Пропускає деякі інвалідації кешу на стороні CPU під час оновлення пам\'яті, зменшуючи навантаження на процесор і покращуючи продуктивність. Може спричинити збої в деяких іграх.</string>
<string name="antiflicker">Антимерехтіння</string>
<string name="antiflicker_description">Змушує механізм синхронізації чекати, доки ГП завершить подані завдання. Використовуйте з режимом ГП «Швидко», щоб уникнути мерехтіння з меншими втратами продуктивності.</string>
<string name="fix_bloom_effects">Виправити ефекти світіння</string> <string name="fix_bloom_effects">Виправити ефекти світіння</string>
<string name="fix_bloom_effects_description">Зменшує розмиття світіння в LA/EOW (Adreno A6XX–A7XX / Turnip), прибирає світіння в Burnout. Увага: може спричинити графічні артефакти в інших іграх.</string> <string name="fix_bloom_effects_description">Зменшує розмиття світіння в LA/EOW (Adreno A6XX–A7XX / Turnip), прибирає світіння в Burnout. Увага: може спричинити графічні артефакти в інших іграх.</string>
<string name="emulate_bgr565">Емулювати BGR565</string> <string name="emulate_bgr565">Емулювати BGR565</string>
@@ -67,7 +67,7 @@
<string name="show_shaders_building">显示着色器编译信息</string> <string name="show_shaders_building">显示着色器编译信息</string>
<string name="show_shaders_building_description">显示当前正在编译的着色器数量</string> <string name="show_shaders_building_description">显示当前正在编译的着色器数量</string>
<string name="pipeline_worker_cores">管线工作线程</string> <string name="pipeline_worker_cores">管线工作线程</string>
<string name="pipeline_worker_cores_description">管理用于构建 Vulkan 管线的核心数量,数值越高则管线编译性能越好,但温度也会随之升高。</string> <string name="pipeline_worker_cores_description">管理用于构建 Vulkan 管线的核心数量,较高的值可提升管线编译性能,但温度也会随之升高。</string>
<string name="overlay_position">叠加层位置</string> <string name="overlay_position">叠加层位置</string>
<string name="overlay_position_description">选择叠加层在屏幕上显示的位置</string> <string name="overlay_position_description">选择叠加层在屏幕上显示的位置</string>
<string name="overlay_position_top_left">左上</string> <string name="overlay_position_top_left">左上</string>
@@ -185,7 +185,7 @@
<string name="multiplayer_preferred_game_name">首选游戏</string> <string name="multiplayer_preferred_game_name">首选游戏</string>
<string name="multiplayer_lobby_type">游戏大厅类型</string> <string name="multiplayer_lobby_type">游戏大厅类型</string>
<string name="multiplayer_room_name_error">长度需为3-20个字符</string> <string name="multiplayer_room_name_error">长度需为3-20个字符</string>
<string name="multiplayer_required">必填</string> <string name="multiplayer_required">需要</string>
<string name="multiplayer_token_required">需要Web令牌,请前往高级设置 -> 系统 -> 网络</string> <string name="multiplayer_token_required">需要Web令牌,请前往高级设置 -> 系统 -> 网络</string>
<string name="multiplayer_ip_error">IP格式无效</string> <string name="multiplayer_ip_error">IP格式无效</string>
<string name="multiplayer_username_error">必须为4至20个字符,且仅包含字母、数字、点号、连字符、下划线和空格</string> <string name="multiplayer_username_error">必须为4至20个字符,且仅包含字母、数字、点号、连字符、下划线和空格</string>
@@ -287,7 +287,7 @@
<string name="warning_skip">跳过</string> <string name="warning_skip">跳过</string>
<string name="warning_cancel">取消</string> <string name="warning_cancel">取消</string>
<string name="install_amiibo_keys">安装 Amiibo 密钥文件</string> <string name="install_amiibo_keys">安装 Amiibo 密钥文件</string>
<string name="install_amiibo_keys_description">在遊戏中使用 Amiibo 时必需</string> <string name="install_amiibo_keys_description">在遊戏中使用 Amiibo 时需要</string>
<string name="gpu_driver_fetcher">GPU驱动获取器</string> <string name="gpu_driver_fetcher">GPU驱动获取器</string>
<string name="gpu_driver_manager">GPU 驱动管理器</string> <string name="gpu_driver_manager">GPU 驱动管理器</string>
<string name="install_gpu_driver_description">安装替代的驱动程序以获得更好的性能和精度</string> <string name="install_gpu_driver_description">安装替代的驱动程序以获得更好的性能和精度</string>
@@ -463,11 +463,13 @@
<string name="advanced">高级</string> <string name="advanced">高级</string>
<string name="renderer_accuracy">GPU 模式</string> <string name="renderer_accuracy">GPU 模式</string>
<string name="renderer_accuracy_description">控制 GPU 模拟模式。大多数游戏在“快速”或“平衡”模式下都能正常渲染,但有些游戏仍需使用“精确”模式。粒子效果通常只有在“精确”模式下才能正确渲染。</string> <string name="renderer_accuracy_description">控制 GPU 模拟模式。大多数游戏在“快速”或“均衡”模式下都能获得良好的渲染,但有些游戏仍需使用“精确”模式。粒子效果通常只有在“精确”模式下才能正确渲染。</string>
<string name="dma_accuracy">DMA 精度</string> <string name="dma_accuracy">DMA 精度</string>
<string name="dma_accuracy_description">控制 DMA 的精准度。安全精度可以修复存在于某些游戏中的问题,但在某些情况下也会对性能造成影响。如不确定,请保持“默认”。</string> <string name="dma_accuracy_description">控制 DMA 的精准度。安全精度可以修复存在于某些游戏中的问题,但在某些情况下也会对性能造成影响。如不确定,请保持“默认”。</string>
<string name="gpu_fence_behavior">GPU 围栏行为</string>
<string name="gpu_fence_behavior_description">控制 GPU 围栏同步行为。“即时”是速度最快的选项,但可能会引入一些问题。“均衡”提供了更好的兼容性,可能修复某些游戏中的问题。“精确”在牺牲部分性能的前提下进一步提升兼容性。“严格”是速度最慢的选项,但可以修复那些要求更严格同步的问题。默认遵循 GPU “精确”设定。</string>
<string name="anisotropic_filtering">各向异性过滤</string> <string name="anisotropic_filtering">各向异性过滤</string>
<string name="anisotropic_filtering_description">提高斜角的纹理质量</string> <string name="anisotropic_filtering_description">提升斜视角下的纹理质量</string>
<string name="vram_usage_mode">显存使用模式</string> <string name="vram_usage_mode">显存使用模式</string>
<string name="vram_usage_mode_description">控制显存分配与释放策略</string> <string name="vram_usage_mode_description">控制显存分配与释放策略</string>
<string name="accelerate_astc">ASTC解码方式</string> <string name="accelerate_astc">ASTC解码方式</string>
@@ -488,7 +490,7 @@
<string name="enable_buffer_history">启用缓冲区历史</string> <string name="enable_buffer_history">启用缓冲区历史</string>
<string name="enable_buffer_history_description">启用对先前缓冲区状态的访问。此选项可在某些游戏中提升渲染质量并保持性能的一致性。</string> <string name="enable_buffer_history_description">启用对先前缓冲区状态的访问。此选项可在某些游戏中提升渲染质量并保持性能的一致性。</string>
<string name="enable_gpu_buffer_readback">启用 GPU 缓冲区回读</string> <string name="enable_gpu_buffer_readback">启用 GPU 缓冲区回读</string>
<string name="enable_gpu_buffer_readback_description">在上传前回读经由 GPU 修改过的缓冲区数据,以将其保留。一些游戏需要这样做才能正确渲染某些效果。如果硬件无法处理额外的工作负载,则可能会导致问题。</string> <string name="enable_gpu_buffer_readback_description">在上传前回读经由 GPU 修改过的缓冲区数据,以将其保留。一些游戏会用到这项设定以正确渲染某些效果。如果硬件无法处理额外的工作负载,则可能会导致问题。</string>
<string name="use_optimized_vertex_buffers">优化顶点缓冲区</string> <string name="use_optimized_vertex_buffers">优化顶点缓冲区</string>
<string name="use_optimized_vertex_buffers_description">启用经过优化的顶点缓冲区绑定以提升性能。需要 Mesa 26.0 及以上版本的 Turnip 或 QCOM 驱动程序。若使用较旧版本的 Turnip 驱动 (25.3 及以下版本) 则会导致崩溃。</string> <string name="use_optimized_vertex_buffers_description">启用经过优化的顶点缓冲区绑定以提升性能。需要 Mesa 26.0 及以上版本的 Turnip 或 QCOM 驱动程序。若使用较旧版本的 Turnip 驱动 (25.3 及以下版本) 则会导致崩溃。</string>
@@ -498,8 +500,6 @@
<string name="fast_gpu_time_description">强制大多数游戏以其最高原生分辨率运行。设置为 256 可获得最佳性能,设置为 512 可获得最佳画面保真度。</string> <string name="fast_gpu_time_description">强制大多数游戏以其最高原生分辨率运行。设置为 256 可获得最佳性能,设置为 512 可获得最佳画面保真度。</string>
<string name="skip_cpu_inner_invalidation">跳过CPU内部无效化</string> <string name="skip_cpu_inner_invalidation">跳过CPU内部无效化</string>
<string name="skip_cpu_inner_invalidation_description">在更新内存时跳过某些 CPU 端的缓存失效操作,从而降低 CPU 占用率并提升性能。可能会在某些游戏中引发故障点或崩溃。</string> <string name="skip_cpu_inner_invalidation_description">在更新内存时跳过某些 CPU 端的缓存失效操作,从而降低 CPU 占用率并提升性能。可能会在某些游戏中引发故障点或崩溃。</string>
<string name="antiflicker">防闪烁</string>
<string name="antiflicker_description">强制 GPU 围栏回调等待已提交的 GPU 任务。配合“快速 GPU 模式”一起使用,以牺牲少量性能为代价来避免画面闪烁现象。</string>
<string name="fix_bloom_effects">修复 Bloom 效果</string> <string name="fix_bloom_effects">修复 Bloom 效果</string>
<string name="fix_bloom_effects_description">减少《智慧的再现》和《众神的三角力量2》(Adreno A6XX - A7XX/ Turnip)中的 bloom 模糊,并移除《Burnout》中的 bloom 效果。警告:可能会导致在其他游戏中出现图形异常。</string> <string name="fix_bloom_effects_description">减少《智慧的再现》和《众神的三角力量2》(Adreno A6XX - A7XX/ Turnip)中的 bloom 模糊,并移除《Burnout》中的 bloom 效果。警告:可能会导致在其他游戏中出现图形异常。</string>
<string name="emulate_bgr565">模拟 BGR565</string> <string name="emulate_bgr565">模拟 BGR565</string>
@@ -513,7 +513,7 @@
<string name="gpu_unswizzle_enable">启用 GPU Unswizzle</string> <string name="gpu_unswizzle_enable">启用 GPU Unswizzle</string>
<string name="gpu_unswizzle_disabled">禁用</string> <string name="gpu_unswizzle_disabled">禁用</string>
<string name="gpu_unswizzle_texture_size">GPU Unswizzle 最大纹理尺寸</string> <string name="gpu_unswizzle_texture_size">GPU Unswizzle 最大纹理尺寸</string>
<string name="gpu_unswizzle_texture_size_description">设置基于 GPU 的纹理 unswizzling 的最大尺寸(MB)。虽然 GPU 处理中等和大型纹理的速度更快,但对于非常小的纹理,CPU 可能更为高效。通过调节此项设置,以尝试在 GPU 加速与 CPU 开销之间找到平衡。</string> <string name="gpu_unswizzle_texture_size_description">设置基于 GPU 的纹理 unswizzling 的最大尺寸(MB)。虽然 GPU 处理中等和大型纹理的速度更快,但对于非常小的纹理,CPU 可能更为高效。通过调节此项设置,以平衡GPU 加速与 CPU 开销。</string>
<string name="gpu_unswizzle_stream_size">GPU Unswizzle 流大小</string> <string name="gpu_unswizzle_stream_size">GPU Unswizzle 流大小</string>
<string name="gpu_unswizzle_stream_size_description">设置用于 unswizzling 大型纹理时的每帧数据限制。较高的数值可以加速纹理的加载过程,但会带来更高的帧延迟。而较低的数值则可以降低 GPU 的开销,但也可能会导致可见的纹理闪现。</string> <string name="gpu_unswizzle_stream_size_description">设置用于 unswizzling 大型纹理时的每帧数据限制。较高的数值可以加速纹理的加载过程,但会带来更高的帧延迟。而较低的数值则可以降低 GPU 的开销,但也可能会导致可见的纹理闪现。</string>
<string name="gpu_unswizzle_chunk_size">GPU Unswizzle 块大小</string> <string name="gpu_unswizzle_chunk_size">GPU Unswizzle 块大小</string>
@@ -990,7 +990,7 @@
<!-- Renderer Accuracy --> <!-- Renderer Accuracy -->
<string name="renderer_accuracy_low">快速</string> <string name="renderer_accuracy_low">快速</string>
<string name="renderer_accuracy_medium">平衡</string> <string name="renderer_accuracy_medium">均衡</string>
<string name="renderer_accuracy_high">精确</string> <string name="renderer_accuracy_high">精确</string>
<!-- DMA Accuracy --> <!-- DMA Accuracy -->
@@ -998,6 +998,13 @@
<string name="dma_accuracy_unsafe">不安全</string> <string name="dma_accuracy_unsafe">不安全</string>
<string name="dma_accuracy_safe">安全</string> <string name="dma_accuracy_safe">安全</string>
<!-- GPU Fence Behavior -->
<string name="gpu_fence_behavior_default">默认</string>
<string name="gpu_fence_behavior_immediate">即时</string>
<string name="gpu_fence_behavior_balanced">均衡</string>
<string name="gpu_fence_behavior_accurate">精确</string>
<string name="gpu_fence_behavior_strict">严格</string>
<string name="vram_usage_conservative">保守式</string> <string name="vram_usage_conservative">保守式</string>
<string name="vram_usage_aggressive">主动式</string> <string name="vram_usage_aggressive">主动式</string>
@@ -1021,7 +1028,7 @@
<string name="ratio_stretch">拉伸窗口</string> <string name="ratio_stretch">拉伸窗口</string>
<!-- CPU Accuracy --> <!-- CPU Accuracy -->
<string name="cpu_accuracy_accurate">精准</string> <string name="cpu_accuracy_accurate">精确</string>
<string name="cpu_accuracy_unsafe">不安全</string> <string name="cpu_accuracy_unsafe">不安全</string>
<string name="cpu_accuracy_paranoid">极致</string> <string name="cpu_accuracy_paranoid">极致</string>
<string name="cpu_accuracy_debugging">调试</string> <string name="cpu_accuracy_debugging">调试</string>
@@ -522,6 +522,21 @@
<item>2</item> <item>2</item>
</integer-array> </integer-array>
<string-array name="gpuFenceBehaviorNames">
<item>@string/gpu_fence_behavior_default</item>
<item>@string/gpu_fence_behavior_immediate</item>
<item>@string/gpu_fence_behavior_balanced</item>
<item>@string/gpu_fence_behavior_accurate</item>
<item>@string/gpu_fence_behavior_strict</item>
</string-array>
<integer-array name="gpuFenceBehaviorValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</integer-array>
<string-array name="appletEntries"> <string-array name="appletEntries">
<item>@string/applet_hle</item> <item>@string/applet_hle</item>
@@ -482,6 +482,8 @@
<string name="renderer_accuracy_description">Controls the GPU emulation mode. Most games render fine with Fast or Balanced modes, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode.</string> <string name="renderer_accuracy_description">Controls the GPU emulation mode. Most games render fine with Fast or Balanced modes, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode.</string>
<string name="dma_accuracy">DMA Accuracy</string> <string name="dma_accuracy">DMA Accuracy</string>
<string name="dma_accuracy_description">Controls the DMA precision accuracy. Safe precision can fix issues in some games, but it can also impact performance in some cases. If unsure, leave this on Default.</string> <string name="dma_accuracy_description">Controls the DMA precision accuracy. Safe precision can fix issues in some games, but it can also impact performance in some cases. If unsure, leave this on Default.</string>
<string name="gpu_fence_behavior">GPU Fence Behavior</string>
<string name="gpu_fence_behavior_description">Controls the GPU fence synchronization behavior. Immediate is the fastest option, but can introduce some issues. Balanced offers better compatibility and may fix issues in some games. Accurate further improves compatibility at the cost of some performance. Strict is the slowest option, but can fix issues that require stricter synchronization. Default follows the GPU Accuracy setting.</string>
<string name="anisotropic_filtering">Anisotropic filtering</string> <string name="anisotropic_filtering">Anisotropic filtering</string>
<string name="anisotropic_filtering_description">Improves the quality of textures when viewed at oblique angles</string> <string name="anisotropic_filtering_description">Improves the quality of textures when viewed at oblique angles</string>
<string name="vram_usage_mode">VRAM Usage Mode</string> <string name="vram_usage_mode">VRAM Usage Mode</string>
@@ -514,8 +516,6 @@
<string name="fast_gpu_time_description">Forces most games to run at their highest native resolution. Use 256 for maximal performance and 512 for maximal graphics fidelity.</string> <string name="fast_gpu_time_description">Forces most games to run at their highest native resolution. Use 256 for maximal performance and 512 for maximal graphics fidelity.</string>
<string name="skip_cpu_inner_invalidation">Skip CPU Inner Invalidation</string> <string name="skip_cpu_inner_invalidation">Skip CPU Inner Invalidation</string>
<string name="skip_cpu_inner_invalidation_description">Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games.</string> <string name="skip_cpu_inner_invalidation_description">Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games.</string>
<string name="antiflicker">Anti-Flicker</string>
<string name="antiflicker_description">Forces GPU fence callbacks to wait for submitted GPU work. Use with Fast GPU mode, to avoid flicker with lower performance impact.</string>
<string name="fix_bloom_effects">Fix Bloom Effects</string> <string name="fix_bloom_effects">Fix Bloom Effects</string>
<string name="fix_bloom_effects_description">Reduces bloom blur in LA/EOW (Adreno A6XX - A7XX/ Turnip), removes bloom in Burnout. Warning: may cause graphical artifacts in other games.</string> <string name="fix_bloom_effects_description">Reduces bloom blur in LA/EOW (Adreno A6XX - A7XX/ Turnip), removes bloom in Burnout. Warning: may cause graphical artifacts in other games.</string>
<string name="emulate_bgr565">Emulate BGR565</string> <string name="emulate_bgr565">Emulate BGR565</string>
@@ -524,6 +524,8 @@
<string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string> <string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string>
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string> <string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string> <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
<string name="renderer_unified_memory">Unified memory access (UMA)</string>
<string name="renderer_unified_memory_description">Allows GPU write buffer readbacks directly into guest memory, skipping the CPU staging copy.</string>
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string> <string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
<string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string> <string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string>
<string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string> <string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string>
@@ -544,6 +546,10 @@
<string name="disabled">Disabled</string> <string name="disabled">Disabled</string>
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string> <string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
<string name="vertex_input_dynamic_state_description">Enabling this feature allows for more flexible vertex input handling, potentially reducing pipeline compilation time in vertex/buffer.</string> <string name="vertex_input_dynamic_state_description">Enabling this feature allows for more flexible vertex input handling, potentially reducing pipeline compilation time in vertex/buffer.</string>
<string name="dynamic_rendering">Dynamic Rendering</string>
<string name="dynamic_rendering_description">Render without render pass and framebuffer objects. Results vary by driver: some gain performance, others lose it.</string>
<string name="workgroup_memory_explicit_layout">Workgroup Memory Explicit Layout</string>
<string name="workgroup_memory_explicit_layout_description">Let shaders declare explicit layouts for workgroup memory. Disabled by default: some Qualcomm drivers are unstable with it.</string>
<string name="sample_shading_fraction">Sample Shading</string> <string name="sample_shading_fraction">Sample Shading</string>
<string name="sample_shading_fraction_description">Allows the fragment shader to execute per sample in a multi-sampled fragment instead once per fragment. Improves graphics quality at the cost of some performance.</string> <string name="sample_shading_fraction_description">Allows the fragment shader to execute per sample in a multi-sampled fragment instead once per fragment. Improves graphics quality at the cost of some performance.</string>
@@ -1047,6 +1053,13 @@
<string name="dma_accuracy_unsafe">Unsafe</string> <string name="dma_accuracy_unsafe">Unsafe</string>
<string name="dma_accuracy_safe">Safe</string> <string name="dma_accuracy_safe">Safe</string>
<!-- GPU Fence Behavior -->
<string name="gpu_fence_behavior_default">Default</string>
<string name="gpu_fence_behavior_immediate">Immediate</string>
<string name="gpu_fence_behavior_balanced">Balanced</string>
<string name="gpu_fence_behavior_accurate">Accurate</string>
<string name="gpu_fence_behavior_strict">Strict</string>
<!-- ASTC Decoding Method Choices --> <!-- ASTC Decoding Method Choices -->
<string name="accelerate_astc_cpu" translatable="false">CPU</string> <string name="accelerate_astc_cpu" translatable="false">CPU</string>
<string name="accelerate_astc_gpu" translatable="false">GPU</string> <string name="accelerate_astc_gpu" translatable="false">GPU</string>
+366 -6
View File
@@ -51,14 +51,45 @@
#endif // ^^^ POSIX ^^^ #endif // ^^^ POSIX ^^^
#include <atomic>
#include <mutex> #include <mutex>
#include <random> #include <random>
#include <vector>
#include "common/alignment.h" #include "common/alignment.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/free_region_manager.h" #include "common/free_region_manager.h"
#include "common/host_memory.h" #include "common/host_memory.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/memory_detect.h"
#include "common/settings.h"
#ifdef __ANDROID__
#include <dlfcn.h>
#include <android/hardware_buffer.h>
namespace {
struct NativeHandle {
int version;
int numFds;
int numInts;
int data[1];
};
using PFN_AHardwareBuffer_getNativeHandle = const NativeHandle* (*)(const AHardwareBuffer*);
PFN_AHardwareBuffer_getNativeHandle ResolveGetNativeHandle() {
void* const lib = dlopen("libnativewindow.so", RTLD_NOW);
if (lib == nullptr) {
return nullptr;
}
return reinterpret_cast<PFN_AHardwareBuffer_getNativeHandle>(
dlsym(lib, "AHardwareBuffer_getNativeHandle"));
}
} // namespace
#endif
#if defined(__ANDROID__) && __ANDROID_API__ < 30 #if defined(__ANDROID__) && __ANDROID_API__ < 30
#include <sys/syscall.h> #include <sys/syscall.h>
@@ -75,6 +106,12 @@ namespace Common {
[[maybe_unused]] constexpr size_t PageAlignment = 0x1000; [[maybe_unused]] constexpr size_t PageAlignment = 0x1000;
[[maybe_unused]] constexpr size_t HugePageSize = 0x200000; [[maybe_unused]] constexpr size_t HugePageSize = 0x200000;
static std::atomic<u64> committed_backing_size{};
u64 GetCommittedBackingSize() noexcept {
return committed_backing_size.load(std::memory_order_relaxed);
}
#ifdef _WIN32 #ifdef _WIN32
// Manually imported for MinGW compatibility // Manually imported for MinGW compatibility
@@ -123,7 +160,7 @@ static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn
class HostMemory::Impl { class HostMemory::Impl {
public: public:
explicit Impl(size_t backing_size_, size_t virtual_size_) explicit Impl(size_t backing_size_, size_t virtual_size_, size_t)
: backing_size{backing_size_} : backing_size{backing_size_}
, virtual_size{virtual_size_} , virtual_size{virtual_size_}
, process{GetCurrentProcess()} , process{GetCurrentProcess()}
@@ -229,6 +266,10 @@ public:
UNREACHABLE(); UNREACHABLE();
} }
bool IsBackingShared() const noexcept {
return true;
}
const size_t backing_size; ///< Size of the backing memory in bytes const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
@@ -501,9 +542,10 @@ static int shm_open_anon(int flags, mode_t mode) {
class HostMemory::Impl { class HostMemory::Impl {
public: public:
explicit Impl(size_t backing_size_, size_t virtual_size_) explicit Impl(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
: backing_size{backing_size_} : backing_size{backing_size_}
, virtual_size{virtual_size_} , virtual_size{virtual_size_}
, preferred_offset{preferred_offset_}
{} {}
bool Init() { bool Init() {
@@ -543,10 +585,15 @@ public:
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones"); LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
if (fd > 0) { if (fd > 0) {
fd = -1;
close(fd); close(fd);
} }
fd = -1;
} else { } else {
#ifdef __ANDROID__
if (InitAhbBacking()) {
return InitVirtual();
}
#endif
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
} }
if (backing_base == MAP_FAILED) { if (backing_base == MAP_FAILED) {
@@ -554,7 +601,10 @@ public:
return false; return false;
} }
// Virtual memory initialization return InitVirtual();
}
bool InitVirtual() {
virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size)); virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
if (virtual_base == MAP_FAILED) { if (virtual_base == MAP_FAILED) {
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno)); LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
@@ -567,6 +617,248 @@ public:
return true; return true;
} }
#ifdef __ANDROID__
static AHardwareBuffer_Desc MakeBlobDesc(size_t len) {
return AHardwareBuffer_Desc{
.width = static_cast<u32>(len),
.height = 1,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_BLOB,
.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER,
.stride = 0,
.rfu0 = 0,
.rfu1 = 0,
};
}
static bool ProbeAhbBacking(PFN_AHardwareBuffer_getNativeHandle get_native_handle) {
const AHardwareBuffer_Desc desc = MakeBlobDesc(PageAlignment * 2);
AHardwareBuffer* buffer{};
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
LOG_WARNING(HW_Memory, "Hardware buffer probe allocation failed");
return false;
}
const NativeHandle* const handle = get_native_handle(buffer);
if (handle == nullptr || handle->numFds < 1) {
LOG_WARNING(HW_Memory, "Hardware buffer has no mappable file descriptor");
AHardwareBuffer_release(buffer);
return false;
}
const int probe_fd = handle->data[0];
bool ok = true;
const auto try_map = [&](int prot, off_t offset, const char* what) {
if (!ok) {
return;
}
void* const ptr = mmap(nullptr, PageAlignment, prot, MAP_SHARED, probe_fd, offset);
if (ptr == MAP_FAILED) {
LOG_WARNING(HW_Memory, "Hardware buffer backing rejects {}: {}", what,
strerror(errno));
ok = false;
return;
}
munmap(ptr, PageAlignment);
};
try_map(PROT_READ | PROT_WRITE, 0, "shared mappings");
try_map(PROT_READ | PROT_WRITE, static_cast<off_t>(PageAlignment), "mappings at an offset");
#ifdef ARCHITECTURE_arm64
try_map(PROT_READ | PROT_EXEC, 0, "executable mappings");
#endif
AHardwareBuffer_release(buffer);
return ok;
}
size_t ComputeAhbBudget(size_t window_size) const {
const u64 total_physical = Common::GetMemInfo().TotalPhysicalMemory;
if (total_physical == 0) {
LOG_WARNING(HW_Memory, "Host memory size is unknown, not committing hardware buffers");
return 0;
}
constexpr u64 MinimumTotalPhysical = 7ULL << 30;
if (total_physical < MinimumTotalPhysical) {
LOG_INFO(HW_Memory,
"Skipping hardware buffer backing, {} MiB of RAM is below the {} MiB minimum",
total_physical >> 20, MinimumTotalPhysical >> 20);
return 0;
}
const u64 max_map_count = Common::GetMaxMapCount();
constexpr u64 ReservedMaps = 24576;
if (max_map_count == 0 || max_map_count <= ReservedMaps) {
LOG_WARNING(HW_Memory,
"Skipping hardware buffer backing, vm.max_map_count is unknown or too low");
return 0;
}
u64 budget = total_physical / 6;
budget = (std::min)(budget, (max_map_count - ReservedMaps) * PageAlignment);
const u64 available = Common::GetAvailablePhysicalMemory();
if (available != 0) {
constexpr u64 Headroom = 2ULL << 30;
budget = (std::min)(budget, available > Headroom ? available - Headroom : 0);
}
budget = (std::min)(budget, static_cast<u64>(backing_size));
budget = Common::AlignDown(budget, window_size);
constexpr u64 MinimumBudget = 256ULL << 20;
if (budget < MinimumBudget) {
LOG_INFO(HW_Memory,
"Skipping hardware buffer backing, only {} MiB could be committed on a {} MiB "
"system with {} MiB available and vm.max_map_count {}",
budget >> 20, total_physical >> 20, available >> 20, max_map_count);
return 0;
}
return static_cast<size_t>(budget);
}
bool InitAhbBacking() {
if (!Settings::values.use_unified_memory.GetValue()) {
return false;
}
static const PFN_AHardwareBuffer_getNativeHandle get_native_handle =
ResolveGetNativeHandle();
if (get_native_handle == nullptr) {
LOG_WARNING(HW_Memory, "AHardwareBuffer_getNativeHandle is not available");
return false;
}
constexpr size_t window_size = 64ULL << 20;
const AHardwareBuffer_Desc window_desc = MakeBlobDesc(window_size);
if (AHardwareBuffer_isSupported(&window_desc) == 0) {
LOG_WARNING(HW_Memory, "Allocator rejects {} MiB hardware buffer windows",
window_size >> 20);
return false;
}
const size_t budget = ComputeAhbBudget(window_size);
if (budget == 0) {
return false;
}
if (!ProbeAhbBacking(get_native_handle)) {
return false;
}
const size_t aligned_backing = Common::AlignDown(backing_size, window_size);
const size_t region_size = (std::min)(budget, aligned_backing);
const size_t region_base = Common::AlignDown(
(std::min)(preferred_offset, aligned_backing - region_size), window_size);
const size_t num_windows = region_size / window_size;
std::vector<AHardwareBuffer*> buffers;
std::vector<int> buffer_fds;
const auto cleanup = [&] {
for (AHardwareBuffer* buffer : buffers) {
AHardwareBuffer_release(buffer);
}
buffers.clear();
buffer_fds.clear();
};
for (size_t i = 0; i < num_windows; ++i) {
const AHardwareBuffer_Desc desc = MakeBlobDesc(window_size);
AHardwareBuffer* buffer{};
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
LOG_WARNING(HW_Memory, "Hardware buffer allocation failed for window {} of {}", i,
num_windows);
cleanup();
return false;
}
buffers.push_back(buffer);
const NativeHandle* const handle = get_native_handle(buffer);
if (handle == nullptr || handle->numFds < 1) {
LOG_WARNING(HW_Memory, "Hardware buffer has no mappable file descriptor");
cleanup();
return false;
}
const int buffer_fd = handle->data[0];
const off_t buffer_len = lseek(buffer_fd, 0, SEEK_END);
if (buffer_len < static_cast<off_t>(window_size)) {
LOG_WARNING(HW_Memory, "Hardware buffer descriptor smaller than requested");
cleanup();
return false;
}
buffer_fds.push_back(buffer_fd);
}
u8* const base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
if (base == MAP_FAILED) {
LOG_WARNING(HW_Memory, "Failed to reserve backing address space: {}", strerror(errno));
cleanup();
return false;
}
const auto map_over_reservation = [&](size_t offset, size_t len, int map_fd,
off_t map_offset) {
if (len == 0) {
return true;
}
if (mmap(base + offset, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, map_fd,
map_offset) == MAP_FAILED) {
LOG_WARNING(HW_Memory, "Backing mmap failed: {}", strerror(errno));
munmap(base, backing_size);
cleanup();
return false;
}
return true;
};
if (!map_over_reservation(0, region_base, fd, 0)) {
return false;
}
for (size_t i = 0; i < num_windows; ++i) {
if (!map_over_reservation(region_base + i * window_size, window_size, buffer_fds[i],
0)) {
return false;
}
}
const size_t tail_offset = region_base + region_size;
if (!map_over_reservation(tail_offset, backing_size - tail_offset, fd,
static_cast<off_t>(tail_offset))) {
return false;
}
backing_base = base;
ahb_windows = std::move(buffers);
ahb_fds = std::move(buffer_fds);
ahb_window_size = window_size;
ahb_base = region_base;
ahb_bytes = region_size;
committed_backing_size.store(region_size, std::memory_order_relaxed);
LOG_INFO(HW_Memory,
"Guest memory {:#x}-{:#x} backed by {} hardware buffer windows, {} MiB committed",
region_base, region_base + region_size, ahb_windows.size(), region_size >> 20);
return true;
}
void MapBackingRange(size_t virtual_offset, size_t host_offset, size_t length, int prot_flags) {
while (length > 0) {
int map_fd = fd;
off_t map_offset = static_cast<off_t>(host_offset);
size_t chunk = length;
if (host_offset < ahb_base) {
chunk = (std::min)(chunk, ahb_base - host_offset);
} else if (host_offset < ahb_base + ahb_bytes) {
const size_t relative = host_offset - ahb_base;
const size_t window = relative / ahb_window_size;
const size_t local = relative % ahb_window_size;
map_fd = ahb_fds[window];
map_offset = static_cast<off_t>(local);
chunk = (std::min)(chunk, ahb_window_size - local);
}
void* const ret = mmap(virtual_base + virtual_offset, chunk, prot_flags,
MAP_SHARED | MAP_FIXED, map_fd, map_offset);
ASSERT_MSG(ret != MAP_FAILED, "mmap: {}", strerror(errno));
virtual_offset += chunk;
host_offset += chunk;
length -= chunk;
}
}
std::span<AHardwareBuffer* const> AhbWindows() const noexcept {
return ahb_windows;
}
size_t AhbWindowSize() const noexcept {
return ahb_bytes != 0 ? ahb_window_size : 0;
}
size_t AhbBase() const noexcept {
return ahb_base;
}
#endif
~Impl() { ~Impl() {
Release(); Release();
} }
@@ -587,6 +879,12 @@ public:
#ifdef ARCHITECTURE_arm64 #ifdef ARCHITECTURE_arm64
if (True(perms & MemoryPermission::Execute)) if (True(perms & MemoryPermission::Execute))
prot_flags |= PROT_EXEC; prot_flags |= PROT_EXEC;
#endif
#ifdef __ANDROID__
if (ahb_bytes != 0) {
MapBackingRange(virtual_offset, host_offset, length, prot_flags);
return;
}
#endif #endif
int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED; int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset); void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
@@ -632,8 +930,18 @@ public:
virtual_base = nullptr; virtual_base = nullptr;
} }
bool IsBackingShared() const noexcept {
#ifdef __ANDROID__
if (ahb_bytes != 0) {
return true;
}
#endif
return fd >= 0;
}
const size_t backing_size; ///< Size of the backing memory in bytes const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
const size_t preferred_offset;
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)}; u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)}; u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
@@ -656,6 +964,18 @@ private:
int ret = close(fd); int ret = close(fd);
ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno)); ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno));
} }
#ifdef __ANDROID__
for (AHardwareBuffer* buffer : ahb_windows) {
AHardwareBuffer_release(buffer);
}
ahb_windows.clear();
ahb_fds.clear();
if (ahb_bytes != 0) {
committed_backing_size.store(0, std::memory_order_relaxed);
ahb_bytes = 0;
}
#endif
} }
void AdjustMap(size_t* virtual_offset, size_t* length) { void AdjustMap(size_t* virtual_offset, size_t* length) {
@@ -681,11 +1001,19 @@ private:
int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
FreeRegionManager free_manager{}; FreeRegionManager free_manager{};
#ifdef __ANDROID__
std::vector<AHardwareBuffer*> ahb_windows;
std::vector<int> ahb_fds;
size_t ahb_window_size{};
size_t ahb_base{};
size_t ahb_bytes{};
#endif
}; };
#endif // ^^^ POSIX ^^^ #endif // ^^^ POSIX ^^^
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_) HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
: backing_size(backing_size_) : backing_size(backing_size_)
, virtual_size(virtual_size_) , virtual_size(virtual_size_)
{ {
@@ -697,7 +1025,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
#else #else
// Try to allocate a fastmem arena. // Try to allocate a fastmem arena.
// The implementation will fail with std::bad_alloc on errors. // The implementation will fail with std::bad_alloc on errors.
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize); impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize, preferred_offset_);
if (impl->Init()) { if (impl->Init()) {
backing_base = impl->backing_base; backing_base = impl->backing_base;
virtual_base = impl->virtual_base; virtual_base = impl->virtual_base;
@@ -767,6 +1095,38 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f
std::memset(backing_base + physical_offset, fill_value, length); std::memset(backing_base + physical_offset, fill_value, length);
} }
std::span<AHardwareBuffer* const> HostMemory::BackingHardwareBuffers() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbWindows() : std::span<AHardwareBuffer* const>{};
#else
return {};
#endif
}
size_t HostMemory::BackingHardwareBufferWindowSize() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbWindowSize() : 0;
#else
return 0;
#endif
}
bool HostMemory::IsBackingShared() const noexcept {
#if defined(__OPENORBIS__) || defined(__managarm__)
return false;
#else
return impl && impl->IsBackingShared();
#endif
}
size_t HostMemory::BackingHardwareBufferBase() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbBase() : 0;
#else
return 0;
#endif
}
void HostMemory::EnableDirectMappedAddress() { void HostMemory::EnableDirectMappedAddress() {
#if !(defined(__OPENORBIS__) || defined(__managarm__)) #if !(defined(__OPENORBIS__) || defined(__managarm__))
if (impl) { if (impl) {
+18 -1
View File
@@ -8,12 +8,17 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/virtual_buffer.h" #include "common/virtual_buffer.h"
struct AHardwareBuffer;
namespace Common { namespace Common {
[[nodiscard]] u64 GetCommittedBackingSize() noexcept;
enum class MemoryPermission : u32 { enum class MemoryPermission : u32 {
Read = 1 << 0, Read = 1 << 0,
Write = 1 << 1, Write = 1 << 1,
@@ -28,7 +33,7 @@ DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
*/ */
class HostMemory { class HostMemory {
public: public:
explicit HostMemory(size_t backing_size_, size_t virtual_size_); explicit HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_ = 0);
~HostMemory(); ~HostMemory();
/** /**
@@ -62,6 +67,18 @@ public:
return backing_base; return backing_base;
} }
[[nodiscard]] size_t BackingSize() const noexcept {
return backing_size;
}
[[nodiscard]] std::span<AHardwareBuffer* const> BackingHardwareBuffers() const noexcept;
[[nodiscard]] size_t BackingHardwareBufferWindowSize() const noexcept;
[[nodiscard]] size_t BackingHardwareBufferBase() const noexcept;
[[nodiscard]] bool IsBackingShared() const noexcept;
[[nodiscard]] u8* VirtualBasePointer() noexcept { [[nodiscard]] u8* VirtualBasePointer() noexcept {
return virtual_base; return virtual_base;
} }
+55
View File
@@ -17,6 +17,10 @@
#endif #endif
#endif #endif
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "common/memory_detect.h" #include "common/memory_detect.h"
namespace Common { namespace Common {
@@ -69,4 +73,55 @@ const MemoryInfo& GetMemInfo() {
return mem_info; return mem_info;
} }
u64 GetAvailablePhysicalMemory() {
#ifdef _WIN32
MEMORYSTATUSEX memorystatus;
memorystatus.dwLength = sizeof(memorystatus);
if (GlobalMemoryStatusEx(&memorystatus)) {
return memorystatus.ullAvailPhys;
}
return 0;
#elif defined(__linux__)
if (std::FILE* const file = std::fopen("/proc/meminfo", "re")) {
char line[256];
u64 available = 0;
while (std::fgets(line, sizeof(line), file) != nullptr) {
if (std::strncmp(line, "MemAvailable:", 13) == 0) {
available = std::strtoull(line + 13, nullptr, 10) * 1024ULL;
break;
}
}
std::fclose(file);
if (available != 0) {
return available;
}
}
struct sysinfo info;
if (sysinfo(&info) == 0) {
const u64 unit = info.mem_unit != 0 ? info.mem_unit : 1ULL;
return (static_cast<u64>(info.freeram) + static_cast<u64>(info.bufferram)) * unit;
}
return 0;
#else
return 0;
#endif
}
u64 GetMaxMapCount() {
#ifdef __linux__
if (std::FILE* const file = std::fopen("/proc/sys/vm/max_map_count", "re")) {
char line[32];
u64 count = 0;
if (std::fgets(line, sizeof(line), file) != nullptr) {
count = std::strtoull(line, nullptr, 10);
}
std::fclose(file);
return count;
}
return 0;
#else
return 0;
#endif
}
} // namespace Common } // namespace Common
+4
View File
@@ -18,4 +18,8 @@ struct MemoryInfo {
*/ */
[[nodiscard]] const MemoryInfo& GetMemInfo(); [[nodiscard]] const MemoryInfo& GetMemInfo();
[[nodiscard]] u64 GetAvailablePhysicalMemory();
[[nodiscard]] u64 GetMaxMapCount();
} // namespace Common } // namespace Common
+16 -8
View File
@@ -156,14 +156,6 @@ void UpdateGPUAccuracy() {
values.current_gpu_accuracy = values.gpu_accuracy.GetValue(); values.current_gpu_accuracy = values.gpu_accuracy.GetValue();
} }
bool IsGPULevelLow() {
return values.current_gpu_accuracy == GpuAccuracy::Low;
}
bool IsGPULevelMedium() {
return values.current_gpu_accuracy == GpuAccuracy::Medium;
}
bool IsGPULevelHigh() { bool IsGPULevelHigh() {
return values.current_gpu_accuracy == GpuAccuracy::High; return values.current_gpu_accuracy == GpuAccuracy::High;
} }
@@ -176,6 +168,22 @@ bool IsDMALevelSafe() {
return values.dma_accuracy.GetValue() == DmaAccuracy::Safe; return values.dma_accuracy.GetValue() == DmaAccuracy::Safe;
} }
bool IsGPUFenceBehaviorDefault() {
return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Default;
}
bool IsGPUFenceBehaviorBalanced() {
return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Balanced;
}
bool IsGPUFenceBehaviorAccurate() {
return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Accurate;
}
bool IsGPUFenceBehaviorStrict() {
return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Strict;
}
bool IsFastmemEnabled() { bool IsFastmemEnabled() {
if (values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Debugging) if (values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Debugging)
return bool(values.cpuopt_fastmem); return bool(values.cpuopt_fastmem);
+30 -11
View File
@@ -420,7 +420,7 @@ struct Values {
#ifdef __ANDROID__ #ifdef __ANDROID__
GpuAccuracy::Low, GpuAccuracy::Low,
#else #else
GpuAccuracy::Medium, GpuAccuracy::High,
#endif #endif
"gpu_accuracy", "gpu_accuracy",
Category::RendererAdvanced, Category::RendererAdvanced,
@@ -428,7 +428,7 @@ struct Values {
true, true,
true}; true};
GpuAccuracy current_gpu_accuracy{GpuAccuracy::Medium}; GpuAccuracy current_gpu_accuracy{GpuAccuracy::High};
SwitchableSetting<DmaAccuracy, true> dma_accuracy{linkage, SwitchableSetting<DmaAccuracy, true> dma_accuracy{linkage,
DmaAccuracy::Default, DmaAccuracy::Default,
@@ -438,6 +438,16 @@ struct Values {
true, true,
true}; true};
SwitchableSetting<GpuFenceBehavior, true> gpu_fence_behavior{linkage,
GpuFenceBehavior::Default,
GpuFenceBehavior::Default,
GpuFenceBehavior::Strict,
"gpu_fence_behavior",
Category::RendererAdvanced,
Specialization::Default,
true,
true};
SwitchableSetting<VramUsageMode, true> vram_usage_mode{linkage, SwitchableSetting<VramUsageMode, true> vram_usage_mode{linkage,
VramUsageMode::Conservative, VramUsageMode::Conservative,
"vram_usage_mode", "vram_usage_mode",
@@ -545,13 +555,6 @@ struct Values {
Specialization::Default, Specialization::Default,
true, true,
true}; true};
SwitchableSetting<bool> antiflicker{linkage,
false,
"antiflicker",
Category::RendererHacks,
Specialization::Default,
true,
true};
SwitchableSetting<bool> async_presentation{linkage, SwitchableSetting<bool> async_presentation{linkage,
#ifdef __ANDROID__ #ifdef __ANDROID__
false, false,
@@ -584,6 +587,9 @@ struct Values {
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders", SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererHacks}; Category::RendererHacks};
SwitchableSetting<bool> use_unified_memory{linkage, false, "use_unified_memory",
Category::RendererHacks};
SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage, SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage,
GpuUnswizzleSize::Large, GpuUnswizzleSize::Large,
"gpu_unswizzle_texture_size", "gpu_unswizzle_texture_size",
@@ -632,6 +638,16 @@ struct Values {
#endif #endif
"vertex_input_dynamic_state", Category::RendererExtensions}; "vertex_input_dynamic_state", Category::RendererExtensions};
SwitchableSetting<bool> dynamic_rendering{linkage, true, "dynamic_rendering",
Category::RendererExtensions};
SwitchableSetting<bool> workgroup_memory_explicit_layout{
linkage, false, "workgroup_memory_explicit_layout", Category::RendererExtensions};
SwitchableSetting<s32, true> pipeline_worker_count{
linkage, 2, 2, 8, "pipeline_worker_count", Category::RendererAdvanced,
Specialization::Scalar};
Setting<bool> renderer_debug{linkage, false, "debug", Category::RendererDebug}; Setting<bool> renderer_debug{linkage, false, "debug", Category::RendererDebug};
Setting<bool> renderer_shader_feedback{linkage, false, "shader_feedback", Setting<bool> renderer_shader_feedback{linkage, false, "shader_feedback",
Category::RendererDebug}; Category::RendererDebug};
@@ -871,13 +887,16 @@ extern Values values;
bool getDebugKnobAt(u8 i); bool getDebugKnobAt(u8 i);
void UpdateGPUAccuracy(); void UpdateGPUAccuracy();
bool IsGPULevelLow();
bool IsGPULevelMedium();
bool IsGPULevelHigh(); bool IsGPULevelHigh();
bool IsDMALevelDefault(); bool IsDMALevelDefault();
bool IsDMALevelSafe(); bool IsDMALevelSafe();
bool IsGPUFenceBehaviorDefault();
bool IsGPUFenceBehaviorBalanced();
bool IsGPUFenceBehaviorAccurate();
bool IsGPUFenceBehaviorStrict();
bool IsFastmemEnabled(); bool IsFastmemEnabled();
void SetNceEnabled(bool is_64bit); void SetNceEnabled(bool is_64bit);
bool IsNceEnabled(); bool IsNceEnabled();
+2 -1
View File
@@ -135,8 +135,9 @@ ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_90, Target_120);
ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed); ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed);
ENUM(VramUsageMode, Conservative, Aggressive); ENUM(VramUsageMode, Conservative, Aggressive);
ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV); ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV);
ENUM(GpuAccuracy, Low, Medium, High); ENUM(GpuAccuracy, Low, High);
ENUM(DmaAccuracy, Default, Unsafe, Safe); ENUM(DmaAccuracy, Default, Unsafe, Safe);
ENUM(GpuFenceBehavior, Default, Immediate, Balanced, Accurate, Strict);
ENUM(CpuBackend, Dynarmic, Nce); ENUM(CpuBackend, Dynarmic, Nce);
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid, Debugging); ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid, Debugging);
ENUM(CpuClock, Off, Boost, Fast) ENUM(CpuClock, Off, Boost, Fast)
+293 -21
View File
@@ -1,5 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project // SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -39,6 +40,258 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
#ifdef __ANDROID__
#include <sys/resource.h>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <mutex>
#include <utility>
#include <vector>
namespace {
constexpr int ANDROID_THREAD_PRIORITY_AUDIO = -16;
constexpr int ANDROID_THREAD_PRIORITY_URGENT_DISPLAY = -8;
constexpr int ANDROID_THREAD_PRIORITY_DISPLAY = -4;
constexpr int ANDROID_THREAD_PRIORITY_DEFAULT = 0;
constexpr int ANDROID_THREAD_PRIORITY_BACKGROUND = 10;
constexpr size_t ANDROID_MINIMUM_PERFORMANCE_CORES = 4;
enum class CoreGroup {
Unrestricted,
Performance,
Efficiency,
};
struct CoreTopology {
cpu_set_t allowed;
cpu_set_t performance;
cpu_set_t efficiency;
bool separated;
bool initialized;
};
struct ThreadPolicy {
pid_t tid;
CoreGroup group;
int nice_value;
bool has_nice;
};
std::mutex g_topology_mutex;
CoreTopology g_topology{};
std::mutex g_policy_mutex;
std::vector<ThreadPolicy>& Policies() {
static auto* const policies = new std::vector<ThreadPolicy>();
return *policies;
}
struct PolicyRegistration {
~PolicyRegistration() {
const pid_t tid = gettid();
std::scoped_lock lock{g_policy_mutex};
std::erase_if(Policies(), [tid](const ThreadPolicy& policy) { return policy.tid == tid; });
}
};
thread_local PolicyRegistration t_policy_registration;
int PossibleCpuCount() {
std::ifstream file("/sys/devices/system/cpu/possible");
std::string list;
if (file && std::getline(file, list) && !list.empty()) {
int highest = -1;
const char* cursor = list.c_str();
while (*cursor != '\0') {
char* end = nullptr;
const long value = std::strtol(cursor, &end, 10);
if (end == cursor) {
break;
}
highest = (std::max)(highest, static_cast<int>(value));
cursor = end;
while (*cursor == '-' || *cursor == ',') {
++cursor;
}
}
if (highest >= 0) {
return (std::min)(highest + 1, CPU_SETSIZE);
}
}
const long configured = sysconf(_SC_NPROCESSORS_CONF);
if (configured > 0) {
return static_cast<int>((std::min<long>)(configured, CPU_SETSIZE));
}
return static_cast<int>((std::min<unsigned>)(std::thread::hardware_concurrency(), CPU_SETSIZE));
}
long ReadCpuScalar(int cpu, const char* node) {
long value = 0;
std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) + "/" + node);
if (!file || !(file >> value) || value <= 0) {
return 0;
}
return value;
}
std::vector<std::pair<long, int>> CollectCoreWeights(const cpu_set_t& allowed, int total,
const char* node, bool require_all) {
std::vector<std::pair<long, int>> cores;
for (int cpu = 0; cpu < total; ++cpu) {
if (!CPU_ISSET(cpu, &allowed)) {
continue;
}
const long weight = ReadCpuScalar(cpu, node);
if (weight <= 0) {
if (require_all) {
return {};
}
LOG_WARNING(Common, "Could not read {} for CPU {}, treating it as an efficiency core",
node, cpu);
continue;
}
cores.emplace_back(weight, cpu);
}
return cores;
}
void ComputeTopologyLocked() {
g_topology.initialized = true;
g_topology.separated = false;
CPU_ZERO(&g_topology.allowed);
CPU_ZERO(&g_topology.performance);
CPU_ZERO(&g_topology.efficiency);
if (sched_getaffinity(getpid(), sizeof(g_topology.allowed), &g_topology.allowed) != 0) {
LOG_WARNING(Common, "Could not query process CPU affinity: {}",
::Common::GetLastErrorMsg());
return;
}
const int total = PossibleCpuCount();
auto cores = CollectCoreWeights(g_topology.allowed, total, "cpu_capacity", true);
if (cores.empty()) {
cores = CollectCoreWeights(g_topology.allowed, total, "cpufreq/cpuinfo_max_freq", false);
}
if (cores.empty()) {
LOG_WARNING(Common, "Could not determine CPU topology, thread placement is disabled");
return;
}
std::sort(cores.begin(), cores.end(),
[](const auto& lhs, const auto& rhs) { return lhs.first > rhs.first; });
const size_t allowed_count = static_cast<size_t>(CPU_COUNT(&g_topology.allowed));
const size_t maximum =
allowed_count > 2 * ANDROID_MINIMUM_PERFORMANCE_CORES
? allowed_count - ANDROID_MINIMUM_PERFORMANCE_CORES
: ANDROID_MINIMUM_PERFORMANCE_CORES;
size_t taken = 0;
long cluster_weight = cores.front().first;
for (const auto& [weight, cpu] : cores) {
if (weight != cluster_weight) {
if (taken >= ANDROID_MINIMUM_PERFORMANCE_CORES) {
break;
}
cluster_weight = weight;
}
if (taken >= maximum) {
break;
}
CPU_SET(cpu, &g_topology.performance);
++taken;
}
if (taken == 0) {
return;
}
for (int cpu = 0; cpu < total; ++cpu) {
if (CPU_ISSET(cpu, &g_topology.allowed) && !CPU_ISSET(cpu, &g_topology.performance)) {
CPU_SET(cpu, &g_topology.efficiency);
}
}
g_topology.separated = CPU_COUNT(&g_topology.efficiency) > 0;
LOG_INFO(Common, "CPU topology: {} performance cores, {} efficiency cores, separation {}",
CPU_COUNT(&g_topology.performance), CPU_COUNT(&g_topology.efficiency),
g_topology.separated ? "enabled" : "unavailable");
}
void EnsureTopologyLocked() {
if (!g_topology.initialized) {
ComputeTopologyLocked();
}
}
void RefreshTopologyLocked() {
if (!g_topology.initialized) {
ComputeTopologyLocked();
return;
}
cpu_set_t current;
CPU_ZERO(&current);
if (sched_getaffinity(getpid(), sizeof(current), &current) != 0) {
return;
}
if (std::memcmp(&current, &g_topology.allowed, sizeof(current)) != 0) {
ComputeTopologyLocked();
}
}
bool ApplyCoreGroupLocked(pid_t tid, CoreGroup group) {
if (!g_topology.separated || group == CoreGroup::Unrestricted) {
return false;
}
const cpu_set_t& mask =
group == CoreGroup::Performance ? g_topology.performance : g_topology.efficiency;
if (CPU_COUNT(&mask) == 0) {
return false;
}
if (sched_setaffinity(tid, sizeof(mask), &mask) != 0) {
LOG_WARNING(Common, "Could not restrict thread {} to its core group: {}", tid,
::Common::GetLastErrorMsg());
return false;
}
return true;
}
ThreadPolicy& AcquirePolicyLocked(pid_t tid) {
auto& policies = Policies();
for (auto& policy : policies) {
if (policy.tid == tid) {
return policy;
}
}
return policies.emplace_back(ThreadPolicy{tid, CoreGroup::Unrestricted, 0, false});
}
void SetCurrentThreadCoreGroup(CoreGroup group) {
const pid_t tid = gettid();
{
std::scoped_lock lock{g_topology_mutex};
EnsureTopologyLocked();
ApplyCoreGroupLocked(tid, group);
}
(void)&t_policy_registration;
std::scoped_lock lock{g_policy_mutex};
AcquirePolicyLocked(tid).group = group;
}
void RememberCurrentThreadNice(pid_t tid, int nice_value) {
(void)&t_policy_registration;
std::scoped_lock lock{g_policy_mutex};
ThreadPolicy& policy = AcquirePolicyLocked(tid);
policy.nice_value = nice_value;
policy.has_nice = true;
}
} // Anonymous namespace
#endif
#include "common/cpu_features.h" #include "common/cpu_features.h"
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
#ifdef _MSC_VER #ifdef _MSC_VER
@@ -48,7 +301,6 @@
#endif #endif
#include "common/x64/rdtsc.h" #include "common/x64/rdtsc.h"
#endif #endif
#include "core/core_timing.h"
namespace Common { namespace Common {
@@ -78,6 +330,24 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
} }
}(); }();
set_thread_priority(find_thread(NULL), priority); set_thread_priority(find_thread(NULL), priority);
#elif defined(__ANDROID__)
const int nice_value = [&]() {
switch (new_priority) {
case ThreadPriority::Low: return ANDROID_THREAD_PRIORITY_BACKGROUND;
case ThreadPriority::Normal: return ANDROID_THREAD_PRIORITY_DEFAULT;
case ThreadPriority::High: return ANDROID_THREAD_PRIORITY_DISPLAY;
case ThreadPriority::VeryHigh: return ANDROID_THREAD_PRIORITY_URGENT_DISPLAY;
case ThreadPriority::Critical: return ANDROID_THREAD_PRIORITY_AUDIO;
default: return ANDROID_THREAD_PRIORITY_DEFAULT;
}
}();
const pid_t tid = gettid();
if (setpriority(PRIO_PROCESS, static_cast<id_t>(tid), nice_value) != 0) {
LOG_WARNING(Common, "Could not set thread nice value to {}: {}", nice_value,
GetLastErrorMsg());
return;
}
RememberCurrentThreadNice(tid, nice_value);
#else #else
pthread_t this_thread = pthread_self(); pthread_t this_thread = pthread_self();
const auto scheduling_type = SCHED_OTHER; const auto scheduling_type = SCHED_OTHER;
@@ -132,29 +402,31 @@ void SetCurrentThreadName(const char* name) {
#endif #endif
} }
void PinCurrentThreadToPerformanceCore(size_t core_id) { void SetCurrentThreadToPerformanceCores() {
ASSERT(core_id < 4);
// If we set a flag for a CPU that doesn't exist, the thread may not be allowed to
// run in ANY processor!
auto const total_cores = std::thread::hardware_concurrency();
if (core_id < total_cores) {
#if defined(__ANDROID__) #if defined(__ANDROID__)
cpu_set_t set; SetCurrentThreadCoreGroup(CoreGroup::Performance);
CPU_ZERO(&set);
CPU_SET(core_id, &set);
sched_setaffinity(pthread_self(), sizeof(set), &set);
#elif defined(__linux__) || defined(__FreeBSD__)
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(core_id, &set);
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
#elif defined(_WIN32)
DWORD set = 1UL << core_id;
SetThreadAffinityMask(GetCurrentThread(), set);
#else
// No pin functionality implemented
#endif #endif
}
void SetCurrentThreadToEfficiencyCores() {
#if defined(__ANDROID__)
SetCurrentThreadCoreGroup(CoreGroup::Efficiency);
#endif
}
void RefreshThreadPolicies() {
#if defined(__ANDROID__)
std::scoped_lock topology_lock{g_topology_mutex};
RefreshTopologyLocked();
std::scoped_lock policy_lock{g_policy_mutex};
for (const auto& policy : Policies()) {
if (policy.has_nice) {
setpriority(PRIO_PROCESS, static_cast<id_t>(policy.tid), policy.nice_value);
}
ApplyCoreGroupLocked(policy.tid, policy.group);
} }
#endif
} }
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
+9 -1
View File
@@ -99,8 +99,16 @@ enum class ThreadPriority : u32 {
Critical = 4, Critical = 4,
}; };
enum class ThreadPlacement : u32 {
Default = 0,
Background = 1,
Efficiency = 2,
};
void SetCurrentThreadPriority(ThreadPriority new_priority); void SetCurrentThreadPriority(ThreadPriority new_priority);
void SetCurrentThreadName(const char* name); void SetCurrentThreadName(const char* name);
void PinCurrentThreadToPerformanceCore(size_t core_id); void SetCurrentThreadToPerformanceCores();
void SetCurrentThreadToEfficiencyCores();
void RefreshThreadPolicies();
} // namespace Common } // namespace Common
+10 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -37,10 +37,17 @@ class StatefulThreadWorker {
using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>; using StateMaker = std::conditional_t<with_state, std::function<StateType()>, DummyCallable>;
public: public:
explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {}) explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {},
ThreadPlacement placement = ThreadPlacement::Default)
: workers_queued{num_workers}, thread_name{std::move(name)} { : workers_queued{num_workers}, thread_name{std::move(name)} {
const auto lambda = [this, func](std::stop_token stop_token) { const auto lambda = [this, func, placement](std::stop_token stop_token) {
Common::SetCurrentThreadName(thread_name.c_str()); Common::SetCurrentThreadName(thread_name.c_str());
if (placement != ThreadPlacement::Default) {
Common::SetCurrentThreadPriority(ThreadPriority::Low);
}
if (placement == ThreadPlacement::Efficiency) {
Common::SetCurrentThreadToEfficiencyCores();
}
{ {
[[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func()}; [[maybe_unused]] std::conditional_t<with_state, StateType, int> state{func()};
while (!stop_token.stop_requested()) { while (!stop_token.stop_requested()) {
+3 -1
View File
@@ -157,6 +157,8 @@ bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info,
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context); return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
} }
constexpr size_t NCE_WRITE_FAULT_CLUSTER_PAGES = 4;
bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) { bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
auto* info = static_cast<siginfo_t*>(raw_info); auto* info = static_cast<siginfo_t*>(raw_info);
@@ -165,7 +167,7 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi
const Common::ProcessAddress addr = const Common::ProcessAddress addr =
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK); (reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory(); auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory();
if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) { if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE * NCE_WRITE_FAULT_CLUSTER_PAGES)) {
// We handled the access successfully and are returning to guest code. // We handled the access successfully and are returning to guest code.
return true; return true;
} }
+5 -1
View File
@@ -118,6 +118,7 @@ struct System::Impl {
is_multicore = Settings::values.use_multi_core.GetValue(); is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb; extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
unified_memory = Settings::values.use_unified_memory.GetValue();
core_timing.SetMulticore(is_multicore); core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); }); core_timing.Initialize([&system]() { system.RegisterHostThread(); });
@@ -145,7 +146,8 @@ struct System::Impl {
!device_memory.has_value() || !device_memory.has_value() ||
is_multicore != Settings::values.use_multi_core.GetValue() || is_multicore != Settings::values.use_multi_core.GetValue() ||
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() != extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
Settings::MemoryLayout::Memory_4Gb); Settings::MemoryLayout::Memory_4Gb) ||
unified_memory != Settings::values.use_unified_memory.GetValue();
if (!must_reinitialize) { if (!must_reinitialize) {
return; return;
@@ -156,6 +158,7 @@ struct System::Impl {
is_multicore = Settings::values.use_multi_core.GetValue(); is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout = extended_memory_layout =
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb; Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
unified_memory = Settings::values.use_unified_memory.GetValue();
Initialize(system); Initialize(system);
} }
@@ -503,6 +506,7 @@ struct System::Impl {
std::atomic_bool is_powered_on{}; std::atomic_bool is_powered_on{};
bool is_multicore : 1 = false; bool is_multicore : 1 = false;
bool extended_memory_layout : 1 = false; bool extended_memory_layout : 1 = false;
bool unified_memory : 1 = false;
bool exit_locked : 1 = false; bool exit_locked : 1 = false;
bool exit_requested : 1 = false; bool exit_requested : 1 = false;
bool nvdec_active : 1 = false; bool nvdec_active : 1 = false;
+2 -1
View File
@@ -58,7 +58,8 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
if (is_multicore) { if (is_multicore) {
timer_thread = std::jthread([this](std::stop_token stop_token) { timer_thread = std::jthread([this](std::stop_token stop_token) {
Common::SetCurrentThreadName("HostTiming"); Common::SetCurrentThreadName("HostTiming");
Common::SetCurrentThreadPriority(Common::ThreadPriority::High); Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
Common::SetCurrentThreadToPerformanceCores();
on_thread_init(); on_thread_init();
has_started = true; has_started = true;
+1 -6
View File
@@ -174,12 +174,7 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) {
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"}; std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
Common::SetCurrentThreadName(name.c_str()); Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
#ifdef __ANDROID__ Common::SetCurrentThreadToPerformanceCores();
// Aimed specifically for Snapdragon 8 Elite devices
// This kills performance on desktop, but boosts perf for UMA devices
// like the S8E. Mediatek and Mali likely won't suffer.
Common::PinCurrentThreadToPerformanceCore(core);
#endif
auto& data = core_data[core]; auto& data = core_data[core];
data.host_context = Common::Fiber::ThreadToFiber(); data.host_context = Common::Fiber::ThreadToFiber();
+10 -1
View File
@@ -12,9 +12,18 @@ constexpr size_t VirtualReserveSize = 1ULL << 38;
constexpr size_t VirtualReserveSize = 1ULL << 39; constexpr size_t VirtualReserveSize = 1ULL << 39;
#endif #endif
namespace {
size_t ApplicationPoolOffset() {
using Init = Kernel::Board::Nintendo::Nx::KSystemControl::Init;
const size_t dram_size = Init::GetIntendedMemorySize();
const size_t application_pool_size = Init::GetApplicationPoolSize();
return dram_size > application_pool_size ? dram_size - application_pool_size : 0;
}
}
DeviceMemory::DeviceMemory() DeviceMemory::DeviceMemory()
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(), : buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
VirtualReserveSize} {} VirtualReserveSize, ApplicationPoolOffset()} {}
DeviceMemory::~DeviceMemory() = default; DeviceMemory::~DeviceMemory() = default;
+40
View File
@@ -20,6 +20,8 @@
#include "common/scratch_buffer.h" #include "common/scratch_buffer.h"
#include "common/virtual_buffer.h" #include "common/virtual_buffer.h"
struct AHardwareBuffer;
namespace Core { namespace Core {
constexpr size_t DEVICE_PAGEBITS = 12ULL; constexpr size_t DEVICE_PAGEBITS = 12ULL;
@@ -95,6 +97,34 @@ public:
ApplyOpOnPAddr(address, buffer, operation); ApplyOpOnPAddr(address, buffer, operation);
} }
u8* GetPhysicalBase() noexcept {
return reinterpret_cast<u8*>(physical_base);
}
const u8* GetPhysicalBase() const noexcept {
return reinterpret_cast<const u8*>(physical_base);
}
size_t GetPhysicalSize() const noexcept {
return physical_size;
}
std::span<AHardwareBuffer* const> GetBackingHardwareBuffers() const noexcept {
return ahb_windows;
}
size_t GetBackingHardwareBufferWindowSize() const noexcept {
return ahb_window_size;
}
size_t GetBackingHardwareBufferBase() const noexcept {
return ahb_base;
}
bool IsBackingShared() const noexcept {
return backing_is_shared;
}
PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const { PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const {
PAddr subbits = PAddr(address & page_mask); PAddr subbits = PAddr(address & page_mask);
auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr; auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr;
@@ -126,6 +156,10 @@ public:
// New batch API to update multiple ranges with a single lock acquisition. // New batch API to update multiple ranges with a single lock acquisition.
void UpdatePagesCachedBatch(std::span<const std::pair<DAddr, size_t>> ranges, s32 delta); void UpdatePagesCachedBatch(std::span<const std::pair<DAddr, size_t>> ranges, s32 delta);
void UpdateTexturePagesCount(DAddr addr, size_t size, s32 delta);
[[nodiscard]] bool IsRegionTextureCached(DAddr addr, size_t size) const noexcept;
private: private:
struct TranslationEntry { struct TranslationEntry {
DAddr guest_page{}; DAddr guest_page{};
@@ -171,6 +205,11 @@ private:
std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl; std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl;
const uintptr_t physical_base; const uintptr_t physical_base;
const size_t physical_size;
const std::span<AHardwareBuffer* const> ahb_windows;
const size_t ahb_window_size;
const size_t ahb_base;
const bool backing_is_shared;
DeviceInterface* device_inter; DeviceInterface* device_inter;
struct TrackedEntry { struct TrackedEntry {
@@ -234,6 +273,7 @@ private:
(1ULL << (device_virtual_bits - page_bits)) / subentries; (1ULL << (device_virtual_bits - page_bits)) / subentries;
using CachedPages = std::array<CounterEntry, num_counter_entries>; using CachedPages = std::array<CounterEntry, num_counter_entries>;
std::unique_ptr<CachedPages> cached_pages; std::unique_ptr<CachedPages> cached_pages;
std::unique_ptr<CachedPages> texture_cached_pages;
Common::RangeMutex counter_guard; Common::RangeMutex counter_guard;
std::mutex mapping_guard; std::mutex mapping_guard;
+28
View File
@@ -171,12 +171,18 @@ struct DeviceMemoryManagerAllocator {
template <typename Traits> template <typename Traits>
DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_) DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_)
: physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())} : physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())}
, physical_size{device_memory_.buffer.BackingSize()}
, ahb_windows{device_memory_.buffer.BackingHardwareBuffers()}
, ahb_window_size{device_memory_.buffer.BackingHardwareBufferWindowSize()}
, ahb_base{device_memory_.buffer.BackingHardwareBufferBase()}
, backing_is_shared{device_memory_.buffer.IsBackingShared()}
, device_inter{nullptr} , device_inter{nullptr}
, compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS)) , compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS))
, tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS) , tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS)
{ {
impl = std::make_unique<DeviceMemoryManagerAllocator<Traits>>(); impl = std::make_unique<DeviceMemoryManagerAllocator<Traits>>();
cached_pages = std::make_unique<CachedPages>(); cached_pages = std::make_unique<CachedPages>();
texture_cached_pages = std::make_unique<CachedPages>();
const size_t total_virtual = device_as_size >> Memory::YUZU_PAGEBITS; const size_t total_virtual = device_as_size >> Memory::YUZU_PAGEBITS;
for (size_t i = 0; i < total_virtual; i++) { for (size_t i = 0; i < total_virtual; i++) {
@@ -625,6 +631,28 @@ void DeviceMemoryManager<Traits>::UpdatePagesCachedCount(DAddr addr, size_t size
UpdatePagesCachedCountNoLock(addr, size, delta); UpdatePagesCachedCountNoLock(addr, size, delta);
} }
template <typename Traits>
void DeviceMemoryManager<Traits>::UpdateTexturePagesCount(DAddr addr, size_t size, s32 delta) {
Common::ScopedRangeLock lk(counter_guard, addr, size);
const size_t page_end = Common::DivCeil(addr + size, Memory::YUZU_PAGESIZE);
for (size_t page = addr >> Memory::YUZU_PAGEBITS; page != page_end; ++page) {
CounterAtomicType& count = texture_cached_pages->at(page >> subentries_shift).Count(page);
count.fetch_add(static_cast<CounterType>(delta), std::memory_order_release);
}
}
template <typename Traits>
bool DeviceMemoryManager<Traits>::IsRegionTextureCached(DAddr addr, size_t size) const noexcept {
const size_t page_end = Common::DivCeil(addr + size, Memory::YUZU_PAGESIZE);
for (size_t page = addr >> Memory::YUZU_PAGEBITS; page != page_end; ++page) {
if (texture_cached_pages->at(page >> subentries_shift).Count(page).load(
std::memory_order_acquire) != 0) {
return true;
}
}
return false;
}
template <typename Traits> template <typename Traits>
void DeviceMemoryManager<Traits>::UpdatePagesCachedBatch(std::span<const std::pair<DAddr, size_t>> ranges, s32 delta) { void DeviceMemoryManager<Traits>::UpdatePagesCachedBatch(std::span<const std::pair<DAddr, size_t>> ranges, s32 delta) {
if (ranges.empty()) { if (ranges.empty()) {
+1 -1
View File
@@ -140,7 +140,7 @@ void ProgramMetadata::LoadManual(bool is_64_bit, ProgramAddressSpaceType address
} }
bool ProgramMetadata::Is64BitProgram() const { bool ProgramMetadata::Is64BitProgram() const {
return npdm_header.has_64_bit_instructions; return bool(npdm_header.has_64_bit_instructions);
} }
ProgramAddressSpaceType ProgramMetadata::GetAddressSpaceType() const { ProgramAddressSpaceType ProgramMetadata::GetAddressSpaceType() const {
@@ -375,39 +375,42 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, false, big_page, false)); mapping_map.insert_or_assign(params.offset, Mapping(params.handle, device_address, params.offset, size, false, big_page, false));
} }
map_buffer_offsets.insert(params.offset);
return NvResult::Success; return NvResult::Success;
} }
NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) { NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) {
LOG_DEBUG(Service_NVDRV, "called, offset={:#X}", params.offset);
std::scoped_lock lock(mutex); std::scoped_lock lock(mutex);
if (auto const offset_it = map_buffer_offsets.find(params.offset); offset_it != map_buffer_offsets.end()) {
LOG_DEBUG(Service_NVDRV, "called, offset={:#X}", params.offset);
if (!vm.initialised) {
return NvResult::BadValue;
}
auto const it = mapping_map.find(params.offset); if (!vm.initialised) {
auto const mapping = it->second; return NvResult::BadValue;
if (!mapping.fixed) {
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
u32 page_size_bits{mapping.big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
allocator.Free(u32(mapping.offset >> page_size_bits), u32(mapping.size >> page_size_bits));
}
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
// Only FreeSpace can unmap them fully
if (mapping.sparse_alloc) {
gmmu->MapSparse(params.offset, mapping.size, mapping.big_page);
} else {
gmmu->Unmap(params.offset, mapping.size);
}
nvmap.UnpinHandle(mapping.handle);
mapping_map.erase(params.offset);
map_buffer_offsets.erase(params.offset);
} }
auto const it = mapping_map.find(params.offset);
if (it == mapping_map.end()) {
LOG_WARNING(Service_NVDRV, "Couldn't find region to unmap at {:#X}", params.offset);
return NvResult::Success;
}
auto const mapping = it->second;
if (!mapping.fixed) {
auto& allocator{mapping.big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
u32 page_size_bits{mapping.big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
allocator.Free(u32(mapping.offset >> page_size_bits), u32(mapping.size >> page_size_bits));
}
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
// Only FreeSpace can unmap them fully
if (mapping.sparse_alloc) {
gmmu->MapSparse(params.offset, mapping.size, mapping.big_page);
} else {
gmmu->Unmap(params.offset, mapping.size);
}
nvmap.UnpinHandle(mapping.handle);
mapping_map.erase(it);
return NvResult::Success; return NvResult::Success;
} }
@@ -13,7 +13,6 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <optional> #include <optional>
#include <ankerl/unordered_dense.h>
#include <vector> #include <vector>
#include "common/address_space.h" #include "common/address_space.h"
@@ -113,8 +112,6 @@ private:
}; };
static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size"); static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
ankerl::unordered_dense::set<s64_le> map_buffer_offsets{};
struct IoctlMapBufferEx { struct IoctlMapBufferEx {
MappingFlags flags{}; // bit0: fixed_offset, bit2: cacheable MappingFlags flags{}; // bit0: fixed_offset, bit2: cacheable
u32_le kind{}; // -1 is default u32_le kind{}; // -1 is default
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring> #include <cstring>
#include "common/assert.h" #include "common/assert.h"
#include "common/logging.h" #include "common/logging.h"
@@ -264,7 +265,7 @@ NvResult nvhost_ctrl_gpu::ZCullGetInfo(IoctlNvgpuGpuZcullGetInfoArgs& params) {
} }
NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) { NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) {
if (params.type > supported_types) { if (params.type == 0 || params.type > supported_types) {
LOG_ERROR(Service_NVDRV, "ZBCSetTable: invalid type {:#X}", params.type); LOG_ERROR(Service_NVDRV, "ZBCSetTable: invalid type {:#X}", params.type);
return NvResult::BadParameter; return NvResult::BadParameter;
} }
@@ -279,42 +280,61 @@ NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) {
color_entry.format = params.format; color_entry.format = params.format;
color_entry.ref_cnt = 1u; color_entry.ref_cnt = 1u;
auto color_it = std::ranges::find_if(zbc_colors, const auto color_end = zbc_colors.begin() + zbc_used_color_entries;
[&](const ZbcColorEntry& color_in_question) { auto color_it = std::find_if(zbc_colors.begin(), color_end,
return color_entry.format == color_in_question.format && [&](const ZbcColorEntry& color_in_question) {
color_entry.color_ds == color_in_question.color_ds && return color_entry.format == color_in_question.format &&
color_entry.color_l2 == color_in_question.color_l2; color_entry.color_ds == color_in_question.color_ds &&
}); color_entry.color_l2 == color_in_question.color_l2;
});
if (color_it != zbc_colors.end()) { if (color_it != color_end) {
++color_it->ref_cnt; ++color_it->ref_cnt;
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused color entry fmt={:#X}, ref_cnt={:#X}", LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused color entry fmt={:#X}, ref_cnt={:#X}",
params.format, color_it->ref_cnt); params.format, color_it->ref_cnt);
} else { break;
zbc_colors.push_back(color_entry);
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added color entry fmt={:#X}, index={:#X}",
params.format, zbc_colors.size() - 1);
} }
if (zbc_used_color_entries >= zbc_table_size) {
LOG_WARNING(Service_NVDRV, "ZBCSetTable: color table is full, fmt={:#X}",
params.format);
return NvResult::InsufficientMemory;
}
zbc_colors[zbc_used_color_entries] = color_entry;
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added color entry fmt={:#X}, index={:#X}",
params.format, zbc_used_color_entries);
++zbc_used_color_entries;
break; break;
} }
case ZBCTypes::depth: { case ZBCTypes::depth: {
ZbcDepthEntry depth_entry{params.depth, params.format, 1u}; ZbcDepthEntry depth_entry{params.depth, params.format, 1u};
auto depth_it = std::ranges::find_if(zbc_depths, const auto depth_end = zbc_depths.begin() + zbc_used_depth_entries;
[&](const ZbcDepthEntry& depth_entry_in_question) { auto depth_it = std::find_if(zbc_depths.begin(), depth_end,
return depth_entry.format == depth_entry_in_question.format && [&](const ZbcDepthEntry& depth_entry_in_question) {
depth_entry.depth == depth_entry_in_question.depth; return depth_entry.format == depth_entry_in_question.format &&
}); depth_entry.depth == depth_entry_in_question.depth;
});
if (depth_it != zbc_depths.end()) { if (depth_it != depth_end) {
++depth_it->ref_cnt; ++depth_it->ref_cnt;
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused depth entry fmt={:#X}, ref_cnt={:#X}", LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused depth entry fmt={:#X}, ref_cnt={:#X}",
depth_entry.format, depth_it->ref_cnt); depth_entry.format, depth_it->ref_cnt);
} else { break;
zbc_depths.push_back(depth_entry);
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added depth entry fmt={:#X}, index={:#X}",
depth_entry.format, zbc_depths.size() - 1);
} }
if (zbc_used_depth_entries >= zbc_table_size) {
LOG_WARNING(Service_NVDRV, "ZBCSetTable: depth table is full, fmt={:#X}",
depth_entry.format);
return NvResult::InsufficientMemory;
}
zbc_depths[zbc_used_depth_entries] = depth_entry;
LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added depth entry fmt={:#X}, index={:#X}",
depth_entry.format, zbc_used_depth_entries);
++zbc_used_depth_entries;
break;
} }
} }
@@ -329,35 +349,34 @@ NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) {
std::scoped_lock lk(zbc_mutex); std::scoped_lock lk(zbc_mutex);
if (params.type == 0) {
params.index_size = zbc_table_size;
return NvResult::Success;
}
if (params.index_size >= zbc_table_size) {
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid index {:#X}", params.index_size);
return NvResult::BadParameter;
}
switch (static_cast<ZBCTypes>(params.type)) { switch (static_cast<ZBCTypes>(params.type)) {
case ZBCTypes::color: { case ZBCTypes::color: {
if (params.index_size >= zbc_colors.size()) {
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid color index {:#X}", params.index_size);
return NvResult::BadParameter;
}
const auto& colors = zbc_colors[params.index_size]; const auto& colors = zbc_colors[params.index_size];
std::copy_n(colors.color_ds.begin(), colors.color_ds.size(), std::begin(params.color_ds)); std::copy_n(colors.color_ds.begin(), colors.color_ds.size(), std::begin(params.color_ds));
std::copy_n(colors.color_l2.begin(), colors.color_l2.size(), std::begin(params.color_l2)); std::copy_n(colors.color_l2.begin(), colors.color_l2.size(), std::begin(params.color_l2));
params.depth = 0; params.depth = 0;
params.ref_cnt = colors.ref_cnt; params.ref_cnt = colors.ref_cnt;
params.format = colors.format; params.format = colors.format;
params.index_size = static_cast<u32>(zbc_colors.size());
break; break;
} }
case ZBCTypes::depth: { case ZBCTypes::depth: {
if (params.index_size >= zbc_depths.size()) {
LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid depth index {:#X}", params.index_size);
return NvResult::BadParameter;
}
const auto& depth_entry = zbc_depths[params.index_size]; const auto& depth_entry = zbc_depths[params.index_size];
std::fill(std::begin(params.color_ds), std::end(params.color_ds), 0); std::fill(std::begin(params.color_ds), std::end(params.color_ds), 0);
std::fill(std::begin(params.color_l2), std::end(params.color_l2), 0); std::fill(std::begin(params.color_l2), std::end(params.color_l2), 0);
params.depth = depth_entry.depth; params.depth = depth_entry.depth;
params.ref_cnt = depth_entry.ref_cnt; params.ref_cnt = depth_entry.ref_cnt;
params.format = depth_entry.format; params.format = depth_entry.format;
params.index_size = static_cast<u32>(zbc_depths.size()); break;
} }
} }
@@ -6,7 +6,7 @@
#pragma once #pragma once
#include <vector> #include <array>
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
@@ -212,9 +212,13 @@ private:
Kernel::KEvent* unknown_event; Kernel::KEvent* unknown_event;
// ZBC Tables // ZBC Tables
static constexpr u32 zbc_table_size = 15u;
std::mutex zbc_mutex{}; std::mutex zbc_mutex{};
std::vector<ZbcColorEntry> zbc_colors{}; std::array<ZbcColorEntry, zbc_table_size> zbc_colors{};
std::vector<ZbcDepthEntry> zbc_depths{}; std::array<ZbcDepthEntry, zbc_table_size> zbc_depths{};
u32 zbc_used_color_entries{};
u32 zbc_used_depth_entries{};
const u32 supported_types = 2u; const u32 supported_types = 2u;
}; };
@@ -174,7 +174,9 @@ NvResult nvhost_gpu::SetChannelPriority(IoctlChannelSetPriority& params) {
case ChannelPriority::Low: channel_timeslice = 1300; break; case ChannelPriority::Low: channel_timeslice = 1300; break;
case ChannelPriority::Medium: channel_timeslice = 2600; break; case ChannelPriority::Medium: channel_timeslice = 2600; break;
case ChannelPriority::High: channel_timeslice = 5200; break; case ChannelPriority::High: channel_timeslice = 5200; break;
default : return NvResult::BadParameter; default:
LOG_WARNING(Service_NVDRV, "unknown channel priority {:#X}", channel_priority);
break;
} }
return NvResult::Success; return NvResult::Success;
@@ -278,18 +280,20 @@ NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) {
params.flags = allowed_mask; params.flags = allowed_mask;
} }
s32_le ctx_class_number_index = params.obj_id = 0;
s32_le ctx_class_number_index =
GetObjectContextClassNumberIndex(static_cast<CtxClasses>(params.class_num)); GetObjectContextClassNumberIndex(static_cast<CtxClasses>(params.class_num));
if (ctx_class_number_index < 0) { if (ctx_class_number_index < 0) {
LOG_ERROR(Service_NVDRV, "Invalid class number for object context: {:#X}", LOG_WARNING(Service_NVDRV, "Untracked class number for object context: {:#X}",
params.class_num); params.class_num);
return NvResult::BadParameter; return NvResult::Success;
} }
if (ctxObjs[ctx_class_number_index].has_value()) { if (ctxObjs[ctx_class_number_index].has_value()) {
LOG_WARNING(Service_NVDRV, "Object context for class {:#X} already allocated on this channel", LOG_DEBUG(Service_NVDRV, "Object context for class {:#X} already allocated on this channel",
params.class_num); params.class_num);
return NvResult::AlreadyAllocated; return NvResult::Success;
} }
// Defer actual hardware context binding until channel is initialized. // Defer actual hardware context binding until channel is initialized.
@@ -435,10 +439,6 @@ NvResult nvhost_gpu::ChannelSetTimeout(IoctlChannelSetTimeout& params) {
NvResult nvhost_gpu::ChannelSetTimeslice(IoctlSetTimeslice& params) { NvResult nvhost_gpu::ChannelSetTimeslice(IoctlSetTimeslice& params) {
LOG_INFO(Service_NVDRV, "called, timeslice={:#X}", params.timeslice); LOG_INFO(Service_NVDRV, "called, timeslice={:#X}", params.timeslice);
if (params.timeslice < 1000 || params.timeslice > 5000) {
return NvResult::BadParameter;
}
channel_timeslice = params.timeslice; channel_timeslice = params.timeslice;
return NvResult::Success; return NvResult::Success;
@@ -20,33 +20,23 @@ BufferQueueCore::~BufferQueueCore() = default;
void BufferQueueCore::PushHistory(u64 frame_number, s64 queue_time, s64 presentation_time, BufferState state) { void BufferQueueCore::PushHistory(u64 frame_number, s64 queue_time, s64 presentation_time, BufferState state) {
std::lock_guard lk(buffer_history_mutex); std::lock_guard lk(buffer_history_mutex);
auto it = buffer_history_map.find(frame_number); buffer_history_pos = (buffer_history_pos + 1) % BUFFER_HISTORY_SIZE;
if (it != buffer_history_map.end()) { buffer_history[buffer_history_pos] = BufferHistoryInfo{
it->second.state = state;
return;
}
buffer_history_map.emplace(frame_number, BufferHistoryInfo{
frame_number, frame_number,
queue_time, queue_time,
presentation_time, presentation_time,
state state
}); };
buffer_history_order.push_back(frame_number);
if (buffer_history_order.size() > BUFFER_HISTORY_SIZE) {
u64 oldest_frame = buffer_history_order.front();
buffer_history_order.pop_front();
buffer_history_map.erase(oldest_frame);
}
} }
void BufferQueueCore::UpdateHistory(u64 frame_number, BufferState state) { void BufferQueueCore::UpdateHistory(u64 frame_number, BufferState state) {
std::lock_guard lk(buffer_history_mutex); std::lock_guard lk(buffer_history_mutex);
auto it = buffer_history_map.find(frame_number); for (auto& entry : buffer_history) {
if (it != buffer_history_map.end()) { if (entry.frame_number == frame_number) {
it->second.state = state; entry.state = state;
return;
}
} }
} }
@@ -9,14 +9,13 @@
#pragma once #pragma once
#include <array>
#include <condition_variable> #include <condition_variable>
#include <deque>
#include <list> #include <list>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <set> #include <set>
#include <vector> #include <vector>
#include <unordered_map>
#include <algorithm> #include <algorithm>
#include "core/hle/service/nvnflinger/buffer_item.h" #include "core/hle/service/nvnflinger/buffer_item.h"
@@ -28,12 +27,15 @@
namespace Service::android { namespace Service::android {
#pragma pack(push, 1)
struct BufferHistoryInfo { struct BufferHistoryInfo {
u64 frame_number{}; u64 frame_number;
s64 queue_time{}; s64 queue_time;
s64 presentation_time{}; s64 presentation_time;
BufferState state{}; BufferState state;
}; };
#pragma pack(pop)
static_assert(sizeof(BufferHistoryInfo) == 0x1C, "BufferHistoryInfo must be 28 bytes");
class IConsumerListener; class IConsumerListener;
class IProducerListener; class IProducerListener;
@@ -88,9 +90,9 @@ private:
bool buffer_has_been_queued{}; bool buffer_has_been_queued{};
u64 frame_counter{}; u64 frame_counter{};
std::unordered_map<u64, BufferHistoryInfo> buffer_history_map{}; std::array<BufferHistoryInfo, BUFFER_HISTORY_SIZE> buffer_history{};
u32 buffer_history_pos{BUFFER_HISTORY_SIZE - 1};
mutable std::mutex buffer_history_mutex{}; mutable std::mutex buffer_history_mutex{};
std::deque<u64> buffer_history_order;
u32 transform_hint{}; u32 transform_hint{};
bool is_allocating{}; bool is_allocating{};
@@ -507,6 +507,8 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
sticky_transform = sticky_transform_; sticky_transform = sticky_transform_;
const bool track_history = Settings::values.enable_buffer_history.GetValue();
if (core->queue.empty()) { if (core->queue.empty()) {
core->queue.push_back(item); core->queue.push_back(item);
listener_available = core->consumer_listener; listener_available = core->consumer_listener;
@@ -514,7 +516,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
auto front = core->queue.begin(); auto front = core->queue.begin();
if (front->is_droppable && core->StillTracking(*front)) { if (front->is_droppable && core->StillTracking(*front)) {
slots[front->slot].buffer_state = BufferState::Free; slots[front->slot].buffer_state = BufferState::Free;
if (Settings::values.enable_buffer_history.GetValue()) { if (track_history) {
core->UpdateHistory(front->frame_number, BufferState::Free); core->UpdateHistory(front->frame_number, BufferState::Free);
} }
slots[front->slot].frame_number = 0; slots[front->slot].frame_number = 0;
@@ -529,7 +531,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
} }
} }
if (Settings::values.enable_buffer_history.GetValue()) { if (track_history) {
core->PushHistory(core->frame_counter, slots[slot].queue_time, slots[slot].presentation_time, BufferState::Queued); core->PushHistory(core->frame_counter, slots[slot].queue_time, slots[slot].presentation_time, BufferState::Queued);
} }
@@ -902,26 +904,31 @@ void BufferQueueProducer::Transact(u32 code, std::span<const u8> parcel_data,
const s32 request = parcel_in.Read<s32>(); const s32 request = parcel_in.Read<s32>();
if (request <= 0) { if (request <= 0) {
parcel_out.Write(Status::BadValue); status = Status::BadValue;
parcel_out.Write<s32>(0); parcel_out.Write<s32>(0);
break; break;
} }
std::vector<BufferHistoryInfo> snapshot; constexpr u32 history_size = BufferQueueCore::BUFFER_HISTORY_SIZE;
std::array<BufferHistoryInfo, history_size> snapshot{};
s32 count{};
{ {
std::scoped_lock lk(core->buffer_history_mutex); std::scoped_lock lk(core->buffer_history_mutex);
for (auto& [frame, info] : core->buffer_history_map) {
snapshot.push_back(info); const u32 newest = core->buffer_history_pos;
for (u32 i = 0; i < history_size; ++i) {
const auto& entry = core->buffer_history[(newest + history_size - i) % history_size];
if (entry.frame_number == 0) {
break;
}
snapshot[count] = entry;
++count;
} }
} }
std::sort(snapshot.begin(), snapshot.end(), [](auto& a, auto& b){ const s32 limit = (std::min)(request, count);
return a.frame_number > b.frame_number;
});
const s32 limit = std::min(request, (s32)snapshot.size());
parcel_out.Write(Status::NoError);
parcel_out.Write<s32>(limit); parcel_out.Write<s32>(limit);
for (s32 i = 0; i < limit; ++i) { for (s32 i = 0; i < limit; ++i) {
parcel_out.Write(snapshot[i]); parcel_out.Write(snapshot[i]);
+2
View File
@@ -5,6 +5,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h" #include "common/settings.h"
#include "common/thread.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/hle/service/vi/conductor.h" #include "core/hle/service/vi/conductor.h"
@@ -76,6 +77,7 @@ void Conductor::ProcessVsync() {
void Conductor::VsyncThread(std::stop_token token) { void Conductor::VsyncThread(std::stop_token token) {
Common::SetCurrentThreadName("VSyncThread"); Common::SetCurrentThreadName("VSyncThread");
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
while (!token.stop_requested()) { while (!token.stop_requested()) {
m_signal.Wait(); m_signal.Wait();
+11 -11
View File
@@ -1662,17 +1662,17 @@ bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const {
bool EmulatedController::IsControllerSupported(bool use_temporary_value) const { bool EmulatedController::IsControllerSupported(bool use_temporary_value) const {
const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load(); const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load();
switch (type) { switch (type) {
case NpadStyleIndex::Fullkey: return supported_style_tag.fullkey; case NpadStyleIndex::Fullkey: return bool(supported_style_tag.fullkey);
case NpadStyleIndex::Handheld: return supported_style_tag.handheld; case NpadStyleIndex::Handheld: return bool(supported_style_tag.handheld);
case NpadStyleIndex::JoyconDual: return supported_style_tag.joycon_dual; case NpadStyleIndex::JoyconDual: return bool(supported_style_tag.joycon_dual);
case NpadStyleIndex::JoyconLeft: return supported_style_tag.joycon_left; case NpadStyleIndex::JoyconLeft: return bool(supported_style_tag.joycon_left);
case NpadStyleIndex::JoyconRight: return supported_style_tag.joycon_right; case NpadStyleIndex::JoyconRight: return bool(supported_style_tag.joycon_right);
case NpadStyleIndex::GameCube: return supported_style_tag.gamecube; case NpadStyleIndex::GameCube: return bool(supported_style_tag.gamecube);
case NpadStyleIndex::Pokeball: return supported_style_tag.palma; case NpadStyleIndex::Pokeball: return bool(supported_style_tag.palma);
case NpadStyleIndex::NES: return supported_style_tag.lark; case NpadStyleIndex::NES: return bool(supported_style_tag.lark);
case NpadStyleIndex::SNES: return supported_style_tag.lucia; case NpadStyleIndex::SNES: return bool(supported_style_tag.lucia);
case NpadStyleIndex::N64: return supported_style_tag.lagoon; case NpadStyleIndex::N64: return bool(supported_style_tag.lagoon);
case NpadStyleIndex::SegaGenesis: return supported_style_tag.lager; case NpadStyleIndex::SegaGenesis: return bool(supported_style_tag.lager);
default: return false; default: return false;
} }
} }
@@ -66,7 +66,7 @@ Result NpadAbstractSixAxisHandler::UpdateSixAxisState() {
continue; continue;
} }
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)]; auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateSixaxisInternalState(npad_entry, data->aruid, data->flag.enable_six_axis_sensor); UpdateSixaxisInternalState(npad_entry, data->aruid, bool(data->flag.enable_six_axis_sensor));
} }
return ResultSuccess; return ResultSuccess;
} }
@@ -78,7 +78,7 @@ Result NpadAbstractSixAxisHandler::UpdateSixAxisState(u64 aruid) {
return ResultSuccess; return ResultSuccess;
} }
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)]; auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateSixaxisInternalState(npad_entry, data->aruid, data->flag.enable_six_axis_sensor); UpdateSixaxisInternalState(npad_entry, data->aruid, bool(data->flag.enable_six_axis_sensor));
return ResultSuccess; return ResultSuccess;
} }
@@ -89,7 +89,7 @@ Result NpadAbstractSixAxisHandler::UpdateSixAxisState2(u64 aruid) {
return ResultSuccess; return ResultSuccess;
} }
auto& npad_internal_state = aruid_data->shared_memory_format->npad.npad_entry[npad_index]; auto& npad_internal_state = aruid_data->shared_memory_format->npad.npad_entry[npad_index];
UpdateSixaxisInternalState(npad_internal_state, aruid, aruid_data->flag.enable_six_axis_sensor); UpdateSixaxisInternalState(npad_internal_state, aruid, bool(aruid_data->flag.enable_six_axis_sensor));
return ResultSuccess; return ResultSuccess;
} }
+17 -21
View File
@@ -24,7 +24,7 @@ void NPadData::SetNpadAnalogStickUseCenterClamp(bool is_enabled) {
} }
bool NPadData::GetNpadAnalogStickUseCenterClamp() const { bool NPadData::GetNpadAnalogStickUseCenterClamp() const {
return status.use_center_clamp; return bool(status.use_center_clamp);
} }
void NPadData::SetNpadSystemExtStateEnabled(bool is_enabled) { void NPadData::SetNpadSystemExtStateEnabled(bool is_enabled) {
@@ -32,7 +32,7 @@ void NPadData::SetNpadSystemExtStateEnabled(bool is_enabled) {
} }
bool NPadData::GetNpadSystemExtState() const { bool NPadData::GetNpadSystemExtState() const {
return status.system_ext_state; return bool(status.system_ext_state);
} }
Result NPadData::SetSupportedNpadIdType(std::span<const Core::HID::NpadIdType> list) { Result NPadData::SetSupportedNpadIdType(std::span<const Core::HID::NpadIdType> list) {
@@ -42,18 +42,14 @@ Result NPadData::SetSupportedNpadIdType(std::span<const Core::HID::NpadIdType> l
} }
supported_npad_id_types_count = list.size(); supported_npad_id_types_count = list.size();
memcpy(supported_npad_id_types.data(), list.data(), std::memcpy(supported_npad_id_types.data(), list.data(), list.size() * sizeof(Core::HID::NpadIdType));
list.size() * sizeof(Core::HID::NpadIdType));
return ResultSuccess; return ResultSuccess;
} }
std::size_t NPadData::GetSupportedNpadIdType(std::span<Core::HID::NpadIdType> out_list) const { std::size_t NPadData::GetSupportedNpadIdType(std::span<Core::HID::NpadIdType> out_list) const {
std::size_t out_size = (std::min)(supported_npad_id_types_count, out_list.size()); std::size_t out_size = (std::min)(supported_npad_id_types_count, out_list.size());
memcpy(out_list.data(), supported_npad_id_types.data(), std::memcpy(out_list.data(), supported_npad_id_types.data(), out_size * sizeof(Core::HID::NpadIdType));
out_size * sizeof(Core::HID::NpadIdType));
return out_size; return out_size;
} }
@@ -154,27 +150,27 @@ bool NPadData::IsNpadStyleIndexSupported(Core::HID::NpadStyleIndex style_index)
Core::HID::NpadStyleTag style = {supported_npad_style_set}; Core::HID::NpadStyleTag style = {supported_npad_style_set};
switch (style_index) { switch (style_index) {
case Core::HID::NpadStyleIndex::Fullkey: case Core::HID::NpadStyleIndex::Fullkey:
return style.fullkey; return bool(style.fullkey);
case Core::HID::NpadStyleIndex::Handheld: case Core::HID::NpadStyleIndex::Handheld:
return style.handheld; return bool(style.handheld);
case Core::HID::NpadStyleIndex::JoyconDual: case Core::HID::NpadStyleIndex::JoyconDual:
return style.joycon_dual; return bool(style.joycon_dual);
case Core::HID::NpadStyleIndex::JoyconLeft: case Core::HID::NpadStyleIndex::JoyconLeft:
return style.joycon_left; return bool(style.joycon_left);
case Core::HID::NpadStyleIndex::JoyconRight: case Core::HID::NpadStyleIndex::JoyconRight:
return style.joycon_right; return bool(style.joycon_right);
case Core::HID::NpadStyleIndex::GameCube: case Core::HID::NpadStyleIndex::GameCube:
return style.gamecube; return bool(style.gamecube);
case Core::HID::NpadStyleIndex::Pokeball: case Core::HID::NpadStyleIndex::Pokeball:
return style.palma; return bool(style.palma);
case Core::HID::NpadStyleIndex::NES: case Core::HID::NpadStyleIndex::NES:
return style.lark; return bool(style.lark);
case Core::HID::NpadStyleIndex::SNES: case Core::HID::NpadStyleIndex::SNES:
return style.lucia; return bool(style.lucia);
case Core::HID::NpadStyleIndex::N64: case Core::HID::NpadStyleIndex::N64:
return style.lagoon; return bool(style.lagoon);
case Core::HID::NpadStyleIndex::SegaGenesis: case Core::HID::NpadStyleIndex::SegaGenesis:
return style.lager; return bool(style.lager);
default: default:
return false; return false;
} }
@@ -185,7 +181,7 @@ void NPadData::SetLrAssignmentMode(bool is_enabled) {
} }
bool NPadData::GetLrAssignmentMode() const { bool NPadData::GetLrAssignmentMode() const {
return status.lr_assignment_mode; return bool(status.lr_assignment_mode);
} }
void NPadData::SetAssigningSingleOnSlSrPress(bool is_enabled) { void NPadData::SetAssigningSingleOnSlSrPress(bool is_enabled) {
@@ -193,7 +189,7 @@ void NPadData::SetAssigningSingleOnSlSrPress(bool is_enabled) {
} }
bool NPadData::GetAssigningSingleOnSlSrPress() const { bool NPadData::GetAssigningSingleOnSlSrPress() const {
return status.assigning_single_on_sl_sr_press; return bool(status.assigning_single_on_sl_sr_press);
} }
void NPadData::SetHomeProtectionEnabled(bool is_enabled, Core::HID::NpadIdType npad_id) { void NPadData::SetHomeProtectionEnabled(bool is_enabled, Core::HID::NpadIdType npad_id) {
@@ -548,7 +548,7 @@ void TouchResource::OnTouchUpdate(s64 timestamp) {
} }
auto& touch_shared = applet_data->shared_memory_format->touch_screen; auto& touch_shared = applet_data->shared_memory_format->touch_screen;
StorePreviousTouchState(previous_touch_state, data.finger_map, current_touch_state, applet_data->flag.enable_touchscreen); StorePreviousTouchState(previous_touch_state, data.finger_map, current_touch_state, bool(applet_data->flag.enable_touchscreen));
touch_shared.touch_screen_lifo.WriteNextEntry(current_touch_state); touch_shared.touch_screen_lifo.WriteNextEntry(current_touch_state);
} }
} }
+24 -9
View File
@@ -193,9 +193,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
INSERT(Settings, skip_cpu_inner_invalidation, tr("Skip CPU Inner Invalidation"), INSERT(Settings, skip_cpu_inner_invalidation, tr("Skip CPU Inner Invalidation"),
tr("Skips certain cache invalidations during memory updates, reducing CPU usage and " tr("Skips certain cache invalidations during memory updates, reducing CPU usage and "
"improving latency. This may cause soft-crashes.")); "improving latency. This may cause soft-crashes."));
INSERT(Settings, antiflicker, tr("Anti-Flicker"),
tr("Forces GPU fence callbacks to wait for submitted GPU work.\n"
"Use with Fast GPU mode, to avoid flicker with lower performance impact."));
INSERT(Settings, vsync_mode, tr("VSync Mode:"), INSERT(Settings, vsync_mode, tr("VSync Mode:"),
tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen " tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
"refresh rate.\nFIFO Relaxed allows tearing as it recovers from a slow down.\n" "refresh rate.\nFIFO Relaxed allows tearing as it recovers from a slow down.\n"
@@ -223,16 +220,21 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
tr("Controls the quality of texture rendering at oblique angles.\nSafe to set at 16x on " tr("Controls the quality of texture rendering at oblique angles.\nSafe to set at 16x on "
"most GPUs.")); "most GPUs."));
INSERT(Settings, gpu_accuracy, tr("GPU Mode:"), INSERT(Settings, gpu_accuracy, tr("GPU Mode:"),
tr("Controls the GPU emulation mode.\nMost games render fine with Fast or Balanced " tr("Controls the GPU emulation mode.\nMost games render fine with Fast, but Accurate is still "
"modes, but Accurate is still "
"required for some.\nParticles tend to only render correctly with Accurate mode.")); "required for some.\nParticles tend to only render correctly with Accurate mode."));
INSERT(Settings, dma_accuracy, tr("DMA Accuracy:"), INSERT(Settings, dma_accuracy, tr("DMA Accuracy:"),
tr("Controls the DMA precision accuracy. Safe precision fixes issues in some games but " tr("Controls the DMA read mode.\nUnsafe is faster, while Safe is more stable and can fix issues in some games.\nDefault follows the GPU Accuracy setting."));
"may degrade performance.")); INSERT(Settings, gpu_fence_behavior, tr("GPU Fence Behavior:"),
tr("Controls the GPU fence synchronization behavior.\nImmediate is the fastest option, but can introduce some issues.\nBalanced offers better compatibility and may fix issues in some games.\nAccurate further improves compatibility at the cost of some performance.\nStrict is the slowest option, but can fix issues that require stricter synchronization.\nDefault follows the GPU Accuracy setting."));
INSERT(Settings, enable_gpu_buffer_readback, tr("Enable GPU buffer readback"), INSERT(Settings, enable_gpu_buffer_readback, tr("Enable GPU buffer readback"),
tr("Preserves GPU-modified buffer data by reading it back before uploads.\nSome games require this to render certain effects properly.\nMay cause issues if the hardware cannot handle the additional workload.")); tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly."));
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"), INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
tr("May reduce shader stutter.")); tr("May reduce shader stutter."));
INSERT(Settings, use_unified_memory, tr("Enable unified memory access (UMA)"),
tr("Lets the GPU write buffer readbacks directly into guest memory."));
INSERT(Settings, pipeline_worker_count, tr("Pipeline Worker Threads"),
tr("Number of threads used to build Vulkan pipelines.\n"
"Higher values speed up compilation at the cost of heat and power."));
INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"), INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"),
tr("Overclocks the emulated GPU to increase dynamic resolution and render " tr("Overclocks the emulated GPU to increase dynamic resolution and render "
"distance.\nUse 256 for maximal performance and 512 for maximal graphics fidelity.")); "distance.\nUse 256 for maximal performance and 512 for maximal graphics fidelity."));
@@ -290,6 +292,12 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
INSERT(Settings, vertex_input_dynamic_state, tr("Vertex Input Dynamic State"), INSERT(Settings, vertex_input_dynamic_state, tr("Vertex Input Dynamic State"),
tr("Enables vertex input dynamic state feature for better quality and performance.")); tr("Enables vertex input dynamic state feature for better quality and performance."));
INSERT(Settings, dynamic_rendering, tr("Dynamic Rendering"),
tr("Renders without render pass and framebuffer objects.\n"
"Results vary by driver: some gain performance, others lose it."));
INSERT(Settings, workgroup_memory_explicit_layout, QString(), QString());
INSERT( INSERT(
Settings, sample_shading, tr("Sample Shading"), Settings, sample_shading, tr("Sample Shading"),
tr("Allows the fragment shader to execute per sample in a multi-sampled fragment " tr("Allows the fragment shader to execute per sample in a multi-sampled fragment "
@@ -430,7 +438,6 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
translations->insert({Settings::EnumMetadata<Settings::GpuAccuracy>::Index(), translations->insert({Settings::EnumMetadata<Settings::GpuAccuracy>::Index(),
{ {
PAIR(GpuAccuracy, Low, tr("Fast")), PAIR(GpuAccuracy, Low, tr("Fast")),
PAIR(GpuAccuracy, Medium, tr("Balanced")),
PAIR(GpuAccuracy, High, tr("Accurate")), PAIR(GpuAccuracy, High, tr("Accurate")),
}}); }});
translations->insert({Settings::EnumMetadata<Settings::DmaAccuracy>::Index(), translations->insert({Settings::EnumMetadata<Settings::DmaAccuracy>::Index(),
@@ -439,6 +446,14 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
PAIR(DmaAccuracy, Unsafe, tr("Unsafe (fast)")), PAIR(DmaAccuracy, Unsafe, tr("Unsafe (fast)")),
PAIR(DmaAccuracy, Safe, tr("Safe (stable)")), PAIR(DmaAccuracy, Safe, tr("Safe (stable)")),
}}); }});
translations->insert({Settings::EnumMetadata<Settings::GpuFenceBehavior>::Index(),
{
PAIR(GpuFenceBehavior, Default, tr("Default")),
PAIR(GpuFenceBehavior, Immediate, tr("Immediate")),
PAIR(GpuFenceBehavior, Balanced, tr("Balanced")),
PAIR(GpuFenceBehavior, Accurate, tr("Accurate")),
PAIR(GpuFenceBehavior, Strict, tr("Strict")),
}});
translations->insert( translations->insert(
{Settings::EnumMetadata<Settings::CpuAccuracy>::Index(), {Settings::EnumMetadata<Settings::CpuAccuracy>::Index(),
{ {
@@ -64,7 +64,6 @@ static const std::map<Settings::ConsoleMode, QString> use_docked_mode_texts_map
static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map = { static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map = {
{Settings::GpuAccuracy::Low, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Fast"))}, {Settings::GpuAccuracy::Low, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Fast"))},
{Settings::GpuAccuracy::Medium, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Balanced"))},
{Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Accurate"))}, {Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Accurate"))},
}; };
-3
View File
@@ -223,9 +223,6 @@ struct Values {
// perf overlay // perf overlay
Setting<bool> show_perf_overlay{linkage, false, "show_perf_overlay", Category::UiGameList}; Setting<bool> show_perf_overlay{linkage, false, "show_perf_overlay", Category::UiGameList};
// Compatibility List
Setting<bool> show_compat{linkage, true, "show_compat", Category::UiGameList};
// Size & File Types Column // Size & File Types Column
Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList}; Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList};
Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList}; Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList};
+1 -50
View File
@@ -52,7 +52,7 @@ void GameListModel::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
current_worker.reset(); current_worker.reset();
removeRows(0, rowCount()); removeRows(0, rowCount());
current_worker = std::make_unique<GameListWorker>(vfs, provider, game_dirs, compatibility_list, current_worker = std::make_unique<GameListWorker>(vfs, provider, game_dirs,
play_time_manager, system); play_time_manager, system);
connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameListModel::WorkerEvent, connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameListModel::WorkerEvent,
@@ -157,50 +157,6 @@ void GameListModel::RemoveFavorite(u64 program_id) {
} }
} }
void GameListModel::LoadCompatibilityList() {
QFile compat_list{QStringLiteral(":compatibility_list/compatibility_list.json")};
if (!compat_list.open(QFile::ReadOnly | QFile::Text)) {
LOG_ERROR(Frontend, "Unable to open game compatibility list");
return;
}
if (compat_list.size() == 0) {
LOG_WARNING(Frontend, "Game compatibility list is empty");
return;
}
const QByteArray content = compat_list.readAll();
if (content.isEmpty()) {
LOG_ERROR(Frontend, "Unable to completely read game compatibility list");
return;
}
const QJsonDocument json = QJsonDocument::fromJson(content);
const QJsonArray arr = json.array();
for (const QJsonValue& value : arr) {
const QJsonObject game = value.toObject();
const QString compatibility_key = QStringLiteral("compatibility");
if (!game.contains(compatibility_key) || !game[compatibility_key].isDouble()) {
continue;
}
const int compatibility = game[compatibility_key].toInt();
const QString directory = game[QStringLiteral("directory")].toString();
const QJsonArray ids = game[QStringLiteral("releases")].toArray();
for (const QJsonValue& id_ref : ids) {
const QJsonObject id_object = id_ref.toObject();
const QString id = id_object[QStringLiteral("id")].toString();
compatibility_list.emplace(id.toUpper().toStdString(),
std::make_pair(QString::number(compatibility), directory));
}
}
}
void GameListModel::Repopulate() { void GameListModel::Repopulate() {
current_worker.reset(); current_worker.reset();
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
@@ -236,7 +192,6 @@ void GameListModel::ResetExternalWatcher() {
void GameListModel::RetranslateUI() { void GameListModel::RetranslateUI() {
setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));
setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility"));
setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
@@ -247,10 +202,6 @@ QFileSystemWatcher* GameListModel::GetWatcher() const {
return watcher; return watcher;
} }
const CompatibilityList& GameListModel::GetCompatibilityList() const {
return compatibility_list;
}
void GameListModel::SetFlat(bool flat) { void GameListModel::SetFlat(bool flat) {
m_flat = flat; m_flat = flat;
} }
-7
View File
@@ -12,7 +12,6 @@
#include "common/common_types.h" #include "common/common_types.h"
#include "frontend_common/play_time_manager.h" #include "frontend_common/play_time_manager.h"
#include "qt_common/config/uisettings.h" #include "qt_common/config/uisettings.h"
#include "yuzu/compatibility_list.h"
namespace Core { namespace Core {
class System; class System;
@@ -37,7 +36,6 @@ public:
COLUMN_SIZE, COLUMN_SIZE,
COLUMN_PLAY_TIME, COLUMN_PLAY_TIME,
COLUMN_ADD_ONS, COLUMN_ADD_ONS,
COLUMN_COMPATIBILITY,
COLUMN_COUNT, COLUMN_COUNT,
}; };
@@ -62,14 +60,10 @@ public:
void RefreshExternalContent(); void RefreshExternalContent();
void ResetExternalWatcher(); void ResetExternalWatcher();
void LoadCompatibilityList();
void RetranslateUI(); void RetranslateUI();
QFileSystemWatcher* GetWatcher() const; QFileSystemWatcher* GetWatcher() const;
const CompatibilityList& GetCompatibilityList() const;
void SetFlat(bool flat); void SetFlat(bool flat);
signals: signals:
@@ -89,7 +83,6 @@ private:
std::shared_ptr<FileSys::VfsFilesystem> vfs; std::shared_ptr<FileSys::VfsFilesystem> vfs;
FileSys::ManualContentProvider* provider; FileSys::ManualContentProvider* provider;
CompatibilityList compatibility_list;
const PlayTime::PlayTimeManager& play_time_manager; const PlayTime::PlayTimeManager& play_time_manager;
Core::System& system; Core::System& system;
+3 -12
View File
@@ -33,7 +33,6 @@
#include "qt_common/qt_common.h" #include "qt_common/qt_common.h"
#include "qt_common/game_list/game_list_p.h" #include "qt_common/game_list/game_list_p.h"
#include "yuzu/compatibility_list.h"
#include "qt_common/game_list/model.h" #include "qt_common/game_list/model.h"
#include "qt_common/game_list/worker.h" #include "qt_common/game_list/worker.h"
@@ -203,14 +202,8 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name, QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name,
const std::size_t size, const std::vector<u8>& icon, const std::size_t size, const std::vector<u8>& icon,
Loader::AppLoader& loader, u64 program_id, Loader::AppLoader& loader, u64 program_id,
const CompatibilityList& compatibility_list,
const PlayTime::PlayTimeManager& play_time_manager, const PlayTime::PlayTimeManager& play_time_manager,
const FileSys::PatchManager& patch) { const FileSys::PatchManager& patch) {
auto const it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
// The game list uses 99 as compatibility number for untested games
QString compatibility =
it != compatibility_list.end() ? it->second.first : QStringLiteral("99");
auto const file_type = loader.GetFileType(); auto const file_type = loader.GetFileType();
auto const file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type)); auto const file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type));
@@ -227,7 +220,6 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
new GameListItemSize(size), new GameListItemSize(size),
new GameListItemPlayTime(play_time), new GameListItemPlayTime(play_time),
new GameListItem(patch_versions), new GameListItem(patch_versions),
new GameListItemCompat(compatibility),
}; };
} }
} // Anonymous namespace } // Anonymous namespace
@@ -235,11 +227,10 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
FileSys::ManualContentProvider* provider_, FileSys::ManualContentProvider* provider_,
QVector<UISettings::GameDir>& game_dirs_, QVector<UISettings::GameDir>& game_dirs_,
const CompatibilityList& compatibility_list_,
const PlayTime::PlayTimeManager& play_time_manager_, const PlayTime::PlayTimeManager& play_time_manager_,
Core::System& system_) Core::System& system_)
: vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, play_time_manager{play_time_manager_},
system{system_} { system{system_} {
// We want the game list to manage our lifetime. // We want the game list to manage our lifetime.
setAutoDelete(false); setAutoDelete(false);
@@ -335,7 +326,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
} }
auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
program_id, compatibility_list, play_time_manager, patch); program_id, play_time_manager, patch);
RecordEvent([=](GameListModel* model) { model->AddEntry(entry, parent_dir); }); RecordEvent([=](GameListModel* model) { model->AddEntry(entry, parent_dir); });
} }
} }
@@ -405,7 +396,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
auto entry = MakeGameListEntry( auto entry = MakeGameListEntry(
physical_name, name, Common::FS::GetSize(physical_name), icon, *app_loader, physical_name, name, Common::FS::GetSize(physical_name), icon, *app_loader,
id, compatibility_list, play_time_manager, patch); id, play_time_manager, patch);
RecordEvent([=](GameListModel* model) { model->AddEntry(entry, parent_dir); }); RecordEvent([=](GameListModel* model) { model->AddEntry(entry, parent_dir); });
}; };
-3
View File
@@ -20,7 +20,6 @@
#include "core/file_sys/registered_cache.h" #include "core/file_sys/registered_cache.h"
#include "frontend_common/play_time_manager.h" #include "frontend_common/play_time_manager.h"
#include "qt_common/config/uisettings.h" #include "qt_common/config/uisettings.h"
#include "yuzu/compatibility_list.h"
namespace Core { namespace Core {
class System; class System;
@@ -46,7 +45,6 @@ public:
explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
FileSys::ManualContentProvider* provider_, FileSys::ManualContentProvider* provider_,
QVector<UISettings::GameDir>& game_dirs_, QVector<UISettings::GameDir>& game_dirs_,
const CompatibilityList& compatibility_list_,
const PlayTime::PlayTimeManager& play_time_manager_, const PlayTime::PlayTimeManager& play_time_manager_,
Core::System& system_); Core::System& system_);
~GameListWorker() override; ~GameListWorker() override;
@@ -85,7 +83,6 @@ private:
std::shared_ptr<FileSys::VfsFilesystem> vfs; std::shared_ptr<FileSys::VfsFilesystem> vfs;
FileSys::ManualContentProvider* provider; FileSys::ManualContentProvider* provider;
QVector<UISettings::GameDir>& game_dirs; QVector<UISettings::GameDir>& game_dirs;
const CompatibilityList& compatibility_list;
const PlayTime::PlayTimeManager& play_time_manager; const PlayTime::PlayTimeManager& play_time_manager;
QStringList watch_list; QStringList watch_list;
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -665,6 +665,8 @@ void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU3
const IR::Value& clamp, const IR::Value& segmentation_mask); const IR::Value& clamp, const IR::Value& segmentation_mask);
void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 index, void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 index,
const IR::Value& clamp, const IR::Value& segmentation_mask); const IR::Value& clamp, const IR::Value& segmentation_mask);
void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 lane);
void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 direction);
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a, ScalarF32 op_b, void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a, ScalarF32 op_b,
ScalarU32 swizzle); ScalarU32 swizzle);
void EmitDPdxFine(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a); void EmitDPdxFine(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a);
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -97,6 +100,24 @@ void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, Sca
Shuffle(ctx, inst, value, index, clamp, segmentation_mask, "XOR"); Shuffle(ctx, inst, value, index, clamp, segmentation_mask, "XOR");
} }
void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 lane) {
const Register ret{ctx.reg_alloc.Define(inst)};
ctx.Add("AND.U RC.x,{}.threadid,~3;"
"AND.U RC.y,{},3;"
"OR.U RC.x,RC.x,RC.y;"
"SHFIDX.U {},{},RC.x,0x1C03;"
"MOV.U {}.x,{}.y;",
ctx.stage_name, lane, ret, value, ret, ret);
}
void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 direction) {
const Register ret{ctx.reg_alloc.Define(inst)};
ctx.Add("ADD.U RC.x,{},1;"
"SHFXOR.U {},{},RC.x,0x1C03;"
"MOV.U {}.x,{}.y;",
direction, ret, value, ret, ret);
}
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a, ScalarF32 op_b, void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a, ScalarF32 op_b,
ScalarU32 swizzle) { ScalarU32 swizzle) {
const auto ret{ctx.reg_alloc.Define(inst)}; const auto ret{ctx.reg_alloc.Define(inst)};
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -743,6 +743,10 @@ void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, std::string_view value,
void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view value, void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view value,
std::string_view index, std::string_view clamp, std::string_view index, std::string_view clamp,
std::string_view segmentation_mask); std::string_view segmentation_mask);
void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, std::string_view value,
std::string_view lane);
void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, std::string_view value,
std::string_view direction);
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b, void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b,
std::string_view swizzle); std::string_view swizzle);
void EmitDPdxFine(EmitContext& ctx, IR::Inst& inst, std::string_view op_a); void EmitDPdxFine(EmitContext& ctx, IR::Inst& inst, std::string_view op_a);
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -200,6 +203,18 @@ void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view val
ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value); ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value);
} }
void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, std::string_view value,
std::string_view lane) {
const auto src_thread_id{fmt::format("(({}&~3)|({}& 3))", THREAD_ID, lane)};
ctx.AddU32("{}=readInvocationARB({},{});", inst, value, src_thread_id);
}
void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, std::string_view value,
std::string_view direction) {
const auto src_thread_id{fmt::format("({}^({}+1))", THREAD_ID, direction)};
ctx.AddU32("{}=readInvocationARB({},{});", inst, value, src_thread_id);
}
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b, void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b,
std::string_view swizzle) { std::string_view swizzle) {
const auto mask{fmt::format("({}>>((gl_SubGroupInvocationARB&3)<<1))&3", swizzle)}; const auto mask{fmt::format("({}>>((gl_SubGroupInvocationARB&3)<<1))&3", swizzle)};
@@ -322,6 +322,11 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
if (ctx.runtime_info.force_early_z) { if (ctx.runtime_info.force_early_z) {
ctx.AddExecutionMode(main, spv::ExecutionMode::EarlyFragmentTests); ctx.AddExecutionMode(main, spv::ExecutionMode::EarlyFragmentTests);
} }
if (ctx.profile.support_shader_quad_control && program.info.uses_quad_shuffles) {
ctx.AddExtension("SPV_KHR_quad_control");
ctx.AddCapability(spv::Capability::QuadControlKHR);
ctx.AddExecutionMode(main, spv::ExecutionMode::RequireFullQuadsKHR);
}
break; break;
default: default:
throw NotImplementedException("Stage {}", program.stage); throw NotImplementedException("Stage {}", program.stage);
@@ -443,6 +448,12 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
ctx.AddCapability(spv::Capability::GroupNonUniformVote); ctx.AddCapability(spv::Capability::GroupNonUniformVote);
} }
} }
if (info.uses_quad_shuffles) {
if (profile.support_quad_shuffles) {
ctx.AddCapability(spv::Capability::GroupNonUniformQuad);
}
ctx.AddCapability(spv::Capability::GroupNonUniformShuffle);
}
if (info.uses_int64_bit_atomics && profile.support_int64_atomics) { if (info.uses_int64_bit_atomics && profile.support_int64_atomics) {
ctx.AddCapability(spv::Capability::Int64Atomics); ctx.AddCapability(spv::Capability::Int64Atomics);
} }
@@ -465,12 +476,22 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
ctx.AddCapability(spv::Capability::ImageGatherExtended); ctx.AddCapability(spv::Capability::ImageGatherExtended);
ctx.AddCapability(spv::Capability::ImageQuery); ctx.AddCapability(spv::Capability::ImageQuery);
ctx.AddCapability(spv::Capability::SampledBuffer); ctx.AddCapability(spv::Capability::SampledBuffer);
// TODO: this usage needs to be tracked properly if (!ctx.non_uniform_ids.empty()) {
if (ctx.profile.support_sampled_image_array_nonuniform_indexing) { if (ctx.profile.supported_spirv < 0x00010500)
if (ctx.profile.supported_spirv < 0x00010400)
ctx.AddExtension("SPV_EXT_descriptor_indexing"); ctx.AddExtension("SPV_EXT_descriptor_indexing");
ctx.AddCapability(spv::Capability::ShaderNonUniform); ctx.AddCapability(spv::Capability::ShaderNonUniform);
ctx.AddCapability(spv::Capability::SampledImageArrayNonUniformIndexing); if (ctx.uses_nonuniform_sampled_image) {
ctx.AddCapability(spv::Capability::SampledImageArrayNonUniformIndexing);
}
if (ctx.uses_nonuniform_storage_image) {
ctx.AddCapability(spv::Capability::StorageImageArrayNonUniformIndexing);
}
if (ctx.uses_nonuniform_uniform_texel_buffer) {
ctx.AddCapability(spv::Capability::UniformTexelBufferArrayNonUniformIndexing);
}
if (ctx.uses_nonuniform_storage_texel_buffer) {
ctx.AddCapability(spv::Capability::StorageTexelBufferArrayNonUniformIndexing);
}
} }
} }
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -15,7 +18,7 @@ Id SharedPointer(EmitContext& ctx, Id offset, u32 index_offset = 0) {
if (index_offset > 0) { if (index_offset > 0) {
index = ctx.OpIAdd(ctx.U32[1], index, ctx.Const(index_offset)); index = ctx.OpIAdd(ctx.U32[1], index, ctx.Const(index_offset));
} }
return ctx.profile.support_explicit_workgroup_layout return ctx.uses_explicit_workgroup_layout
? ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index) ? ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)
: ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index); : ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index);
} }
@@ -155,7 +158,7 @@ Id EmitSharedAtomicExchange32(EmitContext& ctx, Id offset, Id value) {
} }
Id EmitSharedAtomicExchange64(EmitContext& ctx, Id offset, Id value) { Id EmitSharedAtomicExchange64(EmitContext& ctx, Id offset, Id value) {
if (ctx.profile.support_int64_atomics && ctx.profile.support_explicit_workgroup_layout) { if (ctx.profile.support_shared_int64_atomics && ctx.uses_explicit_workgroup_layout) {
const Id shift_id{ctx.Const(3U)}; const Id shift_id{ctx.Const(3U)};
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
const Id pointer{ const Id pointer{
@@ -624,12 +624,14 @@ Id EmitRenderArea(EmitContext& ctx) {
} }
Id EmitLoadLocal(EmitContext& ctx, Id word_offset) { Id EmitLoadLocal(EmitContext& ctx, Id word_offset) {
const Id pointer{ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset)}; const Id pointer{
ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset, ctx.Const(0U))};
return ctx.OpLoad(ctx.U32[1], pointer); return ctx.OpLoad(ctx.U32[1], pointer);
} }
void EmitWriteLocal(EmitContext& ctx, Id word_offset, Id value) { void EmitWriteLocal(EmitContext& ctx, Id word_offset, Id value) {
const Id pointer{ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset)}; const Id pointer{
ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset, ctx.Const(0U))};
ctx.OpStore(pointer, value); ctx.OpStore(pointer, value);
} }
@@ -15,8 +15,56 @@
namespace Shader::Backend::SPIRV { namespace Shader::Backend::SPIRV {
namespace { namespace {
[[nodiscard]] bool IsNonUniformDescriptor(EmitContext& ctx, const IR::Value& index) noexcept { enum class NonUniformKind {
return ctx.profile.support_sampled_image_array_nonuniform_indexing && !index.IsImmediate(); SampledImage,
StorageImage,
UniformTexelBuffer,
StorageTexelBuffer,
};
[[nodiscard]] bool IsNonUniformSupported(const Profile& profile, NonUniformKind kind) noexcept {
switch (kind) {
case NonUniformKind::SampledImage:
return profile.support_sampled_image_array_nonuniform_indexing;
case NonUniformKind::StorageImage:
return profile.support_storage_image_array_nonuniform_indexing;
case NonUniformKind::UniformTexelBuffer:
return profile.support_uniform_texel_buffer_array_nonuniform_indexing;
case NonUniformKind::StorageTexelBuffer:
return profile.support_storage_texel_buffer_array_nonuniform_indexing;
}
return false;
}
void DecorateNonUniform(EmitContext& ctx, Id object) {
if (ctx.non_uniform_ids.contains(object.value)) {
return;
}
ctx.Decorate(object, spv::Decoration::NonUniform);
ctx.non_uniform_ids.insert(object.value);
}
[[nodiscard]] bool MarkNonUniform(EmitContext& ctx, Id idx, const IR::Value& index,
NonUniformKind kind) {
if (index.IsImmediate() || !IsNonUniformSupported(ctx.profile, kind)) {
return false;
}
DecorateNonUniform(ctx, idx);
switch (kind) {
case NonUniformKind::SampledImage:
ctx.uses_nonuniform_sampled_image = true;
break;
case NonUniformKind::StorageImage:
ctx.uses_nonuniform_storage_image = true;
break;
case NonUniformKind::UniformTexelBuffer:
ctx.uses_nonuniform_uniform_texel_buffer = true;
break;
case NonUniformKind::StorageTexelBuffer:
ctx.uses_nonuniform_storage_texel_buffer = true;
break;
}
return true;
} }
class ImageOperands { class ImageOperands {
@@ -195,12 +243,13 @@ Id Texture(EmitContext& ctx, IR::TextureInstInfo info, [[maybe_unused]] const IR
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
if (def.count > 1) { if (def.count > 1) {
auto const idx = index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index); auto const idx = index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index);
if (!ctx.non_uniform_ids.contains(idx.value) && IsNonUniformDescriptor(ctx, index)) { const bool non_uniform{MarkNonUniform(ctx, idx, index, NonUniformKind::SampledImage)};
ctx.Decorate(idx, spv::Decoration::NonUniform);
ctx.non_uniform_ids.insert(idx.value);
}
const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx)}; const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx)};
const Id object{ctx.OpLoad(def.sampled_type, pointer)}; const Id object{ctx.OpLoad(def.sampled_type, pointer)};
if (non_uniform) {
DecorateNonUniform(ctx, pointer);
DecorateNonUniform(ctx, object);
}
return object; return object;
} else { } else {
return ctx.OpLoad(def.sampled_type, def.id); return ctx.OpLoad(def.sampled_type, def.id);
@@ -212,21 +261,30 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
const TextureBufferDefinition& def{ctx.texture_buffers.at(info.descriptor_index)}; const TextureBufferDefinition& def{ctx.texture_buffers.at(info.descriptor_index)};
if (def.count > 1) { if (def.count > 1) {
const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)}; const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)};
const bool non_uniform{
MarkNonUniform(ctx, idx, index, NonUniformKind::UniformTexelBuffer)};
const Id ptr{ctx.OpAccessChain(ctx.image_buffer_type, def.id, idx)}; const Id ptr{ctx.OpAccessChain(ctx.image_buffer_type, def.id, idx)};
return ctx.OpLoad(ctx.image_buffer_type, ptr); const Id object{ctx.OpLoad(ctx.image_buffer_type, ptr)};
if (non_uniform) {
DecorateNonUniform(ctx, ptr);
DecorateNonUniform(ctx, object);
}
return object;
} }
return ctx.OpLoad(ctx.image_buffer_type, def.id); return ctx.OpLoad(ctx.image_buffer_type, def.id);
} else { } else {
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
if (def.count > 1) { if (def.count > 1) {
auto const idx = index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index); auto const idx = index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index);
if (!ctx.non_uniform_ids.contains(idx.value) && IsNonUniformDescriptor(ctx, index)) { const bool non_uniform{MarkNonUniform(ctx, idx, index, NonUniformKind::SampledImage)};
ctx.Decorate(idx, spv::Decoration::NonUniform);
ctx.non_uniform_ids.insert(idx.value);
}
const Id ptr = ctx.OpAccessChain(def.pointer_type, def.id, idx); const Id ptr = ctx.OpAccessChain(def.pointer_type, def.id, idx);
const Id object = ctx.OpLoad(def.sampled_type, ptr); const Id object = ctx.OpLoad(def.sampled_type, ptr);
const Id image = ctx.OpImage(def.image_type, object); const Id image = ctx.OpImage(def.image_type, object);
if (non_uniform) {
DecorateNonUniform(ctx, ptr);
DecorateNonUniform(ctx, object);
DecorateNonUniform(ctx, image);
}
return image; return image;
} }
return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, def.id)); return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, def.id));
@@ -238,16 +296,29 @@ std::pair<Id, bool> Image(EmitContext& ctx, const IR::Value& index, IR::TextureI
const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)}; const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)};
if (def.count > 1) { if (def.count > 1) {
const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)}; const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)};
const bool non_uniform{
MarkNonUniform(ctx, idx, index, NonUniformKind::StorageTexelBuffer)};
const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx)}; const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx)};
return {ctx.OpLoad(def.image_type, ptr), def.is_integer}; const Id image{ctx.OpLoad(def.image_type, ptr)};
if (non_uniform) {
DecorateNonUniform(ctx, ptr);
DecorateNonUniform(ctx, image);
}
return {image, def.is_integer};
} }
return {ctx.OpLoad(def.image_type, def.id), def.is_integer}; return {ctx.OpLoad(def.image_type, def.id), def.is_integer};
} else { } else {
const ImageDefinition def{ctx.images.at(info.descriptor_index)}; const ImageDefinition def{ctx.images.at(info.descriptor_index)};
if (def.count > 1) { if (def.count > 1) {
const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)}; const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)};
const bool non_uniform{MarkNonUniform(ctx, idx, index, NonUniformKind::StorageImage)};
const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx)}; const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx)};
return {ctx.OpLoad(def.image_type, ptr), def.is_integer}; const Id image{ctx.OpLoad(def.image_type, ptr)};
if (non_uniform) {
DecorateNonUniform(ctx, ptr);
DecorateNonUniform(ctx, image);
}
return {image, def.is_integer};
} }
return {ctx.OpLoad(def.image_type, def.id), def.is_integer}; return {ctx.OpLoad(def.image_type, def.id), def.is_integer};
} }
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -622,6 +622,8 @@ Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clam
Id segmentation_mask); Id segmentation_mask);
Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask); Id segmentation_mask);
Id EmitQuadBroadcast(EmitContext& ctx, Id value, Id lane);
Id EmitQuadSwap(EmitContext& ctx, Id value, Id direction);
Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle); Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle);
Id EmitDPdxFine(EmitContext& ctx, Id op_a); Id EmitDPdxFine(EmitContext& ctx, Id op_a);
Id EmitDPdyFine(EmitContext& ctx, Id op_a); Id EmitDPdyFine(EmitContext& ctx, Id op_a);
@@ -159,7 +159,7 @@ void EmitWriteGlobal128(EmitContext& ctx, Id address, Id value) {
} }
Id EmitLoadStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { Id EmitLoadStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_int8 && ctx.profile.support_uniform_and_storage_buffer_8bit && if (ctx.profile.support_int8 && ctx.profile.support_storage_buffer_8bit &&
ctx.profile.support_descriptor_aliasing) { ctx.profile.support_descriptor_aliasing) {
return ctx.OpUConvert(ctx.U32[1], return ctx.OpUConvert(ctx.U32[1],
LoadStorage(ctx, binding, offset, ctx.U8, ctx.storage_types.U8, LoadStorage(ctx, binding, offset, ctx.U8, ctx.storage_types.U8,
@@ -171,7 +171,7 @@ Id EmitLoadStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value
} }
Id EmitLoadStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { Id EmitLoadStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_int8 && ctx.profile.support_uniform_and_storage_buffer_8bit && if (ctx.profile.support_int8 && ctx.profile.support_storage_buffer_8bit &&
ctx.profile.support_descriptor_aliasing) { ctx.profile.support_descriptor_aliasing) {
return ctx.OpSConvert(ctx.U32[1], return ctx.OpSConvert(ctx.U32[1],
LoadStorage(ctx, binding, offset, ctx.S8, ctx.storage_types.S8, LoadStorage(ctx, binding, offset, ctx.S8, ctx.storage_types.S8,
@@ -183,7 +183,7 @@ Id EmitLoadStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value
} }
Id EmitLoadStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { Id EmitLoadStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_int16 && ctx.profile.support_uniform_and_storage_buffer_16bit && if (ctx.profile.support_int16 && ctx.profile.support_storage_buffer_16bit &&
ctx.profile.support_descriptor_aliasing) { ctx.profile.support_descriptor_aliasing) {
return ctx.OpUConvert(ctx.U32[1], return ctx.OpUConvert(ctx.U32[1],
LoadStorage(ctx, binding, offset, ctx.U16, ctx.storage_types.U16, LoadStorage(ctx, binding, offset, ctx.U16, ctx.storage_types.U16,
@@ -195,7 +195,7 @@ Id EmitLoadStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Valu
} }
Id EmitLoadStorageS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { Id EmitLoadStorageS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) {
if (ctx.profile.support_int16 && ctx.profile.support_uniform_and_storage_buffer_16bit && if (ctx.profile.support_int16 && ctx.profile.support_storage_buffer_16bit &&
ctx.profile.support_descriptor_aliasing) { ctx.profile.support_descriptor_aliasing) {
return ctx.OpSConvert(ctx.U32[1], return ctx.OpSConvert(ctx.U32[1],
LoadStorage(ctx, binding, offset, ctx.S16, ctx.storage_types.S16, LoadStorage(ctx, binding, offset, ctx.S16, ctx.storage_types.S16,
@@ -234,7 +234,8 @@ Id EmitLoadStorage128(EmitContext& ctx, const IR::Value& binding, const IR::Valu
void EmitWriteStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, void EmitWriteStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
Id value) { Id value) {
if (ctx.profile.support_int8 && ctx.profile.support_uniform_and_storage_buffer_8bit) { if (ctx.profile.support_int8 && ctx.profile.support_storage_buffer_8bit &&
ctx.profile.support_descriptor_aliasing) {
WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U8, value), ctx.storage_types.U8, WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U8, value), ctx.storage_types.U8,
sizeof(u8), &StorageDefinitions::U8); sizeof(u8), &StorageDefinitions::U8);
} else { } else {
@@ -244,7 +245,8 @@ void EmitWriteStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Va
void EmitWriteStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, void EmitWriteStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
Id value) { Id value) {
if (ctx.profile.support_int8 && ctx.profile.support_uniform_and_storage_buffer_8bit) { if (ctx.profile.support_int8 && ctx.profile.support_storage_buffer_8bit &&
ctx.profile.support_descriptor_aliasing) {
WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S8, value), ctx.storage_types.S8, WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S8, value), ctx.storage_types.S8,
sizeof(s8), &StorageDefinitions::S8); sizeof(s8), &StorageDefinitions::S8);
} else { } else {
@@ -254,7 +256,8 @@ void EmitWriteStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Va
void EmitWriteStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, void EmitWriteStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
Id value) { Id value) {
if (ctx.profile.support_int16 && ctx.profile.support_uniform_and_storage_buffer_16bit) { if (ctx.profile.support_int16 && ctx.profile.support_storage_buffer_16bit &&
ctx.profile.support_descriptor_aliasing) {
WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U16, value), ctx.storage_types.U16, WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U16, value), ctx.storage_types.U16,
sizeof(u16), &StorageDefinitions::U16); sizeof(u16), &StorageDefinitions::U16);
} else { } else {
@@ -264,7 +267,8 @@ void EmitWriteStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::V
void EmitWriteStorageS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, void EmitWriteStorageS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
Id value) { Id value) {
if (ctx.profile.support_int16 && ctx.profile.support_uniform_and_storage_buffer_16bit) { if (ctx.profile.support_int16 && ctx.profile.support_storage_buffer_16bit &&
ctx.profile.support_descriptor_aliasing) {
WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S16, value), ctx.storage_types.S16, WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S16, value), ctx.storage_types.S16,
sizeof(s16), &StorageDefinitions::S16); sizeof(s16), &StorageDefinitions::S16);
} else { } else {
@@ -31,7 +31,7 @@ std::pair<Id, Id> ExtractArgs(EmitContext& ctx, Id offset, u32 mask, u32 count)
} // Anonymous namespace } // Anonymous namespace
Id EmitLoadSharedU8(EmitContext& ctx, Id offset) { Id EmitLoadSharedU8(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{ const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer)); return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer));
@@ -42,7 +42,7 @@ Id EmitLoadSharedU8(EmitContext& ctx, Id offset) {
} }
Id EmitLoadSharedS8(EmitContext& ctx, Id offset) { Id EmitLoadSharedS8(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{ const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer)); return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer));
@@ -53,7 +53,7 @@ Id EmitLoadSharedS8(EmitContext& ctx, Id offset) {
} }
Id EmitLoadSharedU16(EmitContext& ctx, Id offset) { Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer)); return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer));
} else { } else {
@@ -63,7 +63,7 @@ Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
} }
Id EmitLoadSharedS16(EmitContext& ctx, Id offset) { Id EmitLoadSharedS16(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer)); return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer));
} else { } else {
@@ -73,7 +73,7 @@ Id EmitLoadSharedS16(EmitContext& ctx, Id offset) {
} }
Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2)}; const Id pointer{Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2)};
return ctx.OpLoad(ctx.U32[1], pointer); return ctx.OpLoad(ctx.U32[1], pointer);
} else { } else {
@@ -82,7 +82,7 @@ Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
} }
Id EmitLoadSharedU64(EmitContext& ctx, Id offset) { Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)}; const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)};
return ctx.OpLoad(ctx.U32[2], pointer); return ctx.OpLoad(ctx.U32[2], pointer);
} else { } else {
@@ -97,7 +97,7 @@ Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
} }
Id EmitLoadSharedU128(EmitContext& ctx, Id offset) { Id EmitLoadSharedU128(EmitContext& ctx, Id offset) {
if (ctx.profile.support_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)}; const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)};
return ctx.OpLoad(ctx.U32[4], pointer); return ctx.OpLoad(ctx.U32[4], pointer);
} }
@@ -113,7 +113,7 @@ Id EmitLoadSharedU128(EmitContext& ctx, Id offset) {
} }
void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) { void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) {
if (ctx.profile.support_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{ const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
ctx.OpStore(pointer, ctx.OpUConvert(ctx.U8, value)); ctx.OpStore(pointer, ctx.OpUConvert(ctx.U8, value));
@@ -123,7 +123,7 @@ void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) {
} }
void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) { void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
if (ctx.profile.support_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
ctx.OpStore(pointer, ctx.OpUConvert(ctx.U16, value)); ctx.OpStore(pointer, ctx.OpUConvert(ctx.U16, value));
} else { } else {
@@ -133,7 +133,7 @@ void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) { void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
Id pointer{}; Id pointer{};
if (ctx.profile.support_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
pointer = Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2); pointer = Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2);
} else { } else {
const Id shift{ctx.Const(2U)}; const Id shift{ctx.Const(2U)};
@@ -144,7 +144,7 @@ void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
} }
void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
if (ctx.profile.support_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)}; const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)};
ctx.OpStore(pointer, value); ctx.OpStore(pointer, value);
return; return;
@@ -159,7 +159,7 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
} }
void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value) { void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value) {
if (ctx.profile.support_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)}; const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)};
ctx.OpStore(pointer, value); ctx.OpStore(pointer, value);
return; return;
@@ -260,6 +260,21 @@ Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id
return SelectValue(ctx, in_range, value, src_thread_id); return SelectValue(ctx, in_range, value, src_thread_id);
} }
Id EmitQuadBroadcast(EmitContext& ctx, Id value, Id lane) {
if (ctx.profile.support_quad_shuffles) {
return ctx.OpGroupNonUniformQuadBroadcast(ctx.U32[1], SubgroupScope(ctx), value, lane);
}
const Id base{ctx.OpBitwiseAnd(ctx.U32[1], GetThreadId(ctx), ctx.Const(~3u))};
const Id local_lane{ctx.OpBitwiseAnd(ctx.U32[1], lane, ctx.Const(3u))};
const Id src_thread_id{ctx.OpBitwiseOr(ctx.U32[1], base, local_lane)};
return ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id);
}
Id EmitQuadSwap(EmitContext& ctx, Id value, Id direction) {
const Id xor_mask{ctx.OpIAdd(ctx.U32[1], direction, ctx.Const(1u))};
return ctx.OpGroupNonUniformShuffleXor(ctx.U32[1], SubgroupScope(ctx), value, xor_mask);
}
Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle) { Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle) {
const Id three{ctx.Const(3U)}; const Id three{ctx.Const(3U)};
Id mask{GetThreadId(ctx)}; Id mask{GetThreadId(ctx)};
@@ -371,7 +371,7 @@ Id CasFunction(EmitContext& ctx, Operation operation, Id value_type) {
Id CasLoop(EmitContext& ctx, Operation operation, Id array_pointer, Id element_pointer, Id CasLoop(EmitContext& ctx, Operation operation, Id array_pointer, Id element_pointer,
Id value_type, Id memory_type, spv::Scope scope) { Id value_type, Id memory_type, spv::Scope scope) {
const bool is_shared{scope == spv::Scope::Workgroup}; const bool is_shared{scope == spv::Scope::Workgroup};
const bool is_struct{!is_shared || ctx.profile.support_explicit_workgroup_layout}; const bool is_struct{!is_shared || ctx.uses_explicit_workgroup_layout};
const Id cas_func{CasFunction(ctx, operation, value_type)}; const Id cas_func{CasFunction(ctx, operation, value_type)};
const Id zero{ctx.u32_zero_value}; const Id zero{ctx.u32_zero_value};
const Id scope_id{ctx.Const(static_cast<u32>(scope))}; const Id scope_id{ctx.Const(static_cast<u32>(scope))};
@@ -591,7 +591,8 @@ void EmitContext::DefineLocalMemory(const IR::Program& program) {
return; return;
} }
const u32 num_elements{Common::DivCeil(program.local_memory_size, 4U)}; const u32 num_elements{Common::DivCeil(program.local_memory_size, 4U)};
const Id type{TypeArray(U32[1], Const(num_elements))}; const Id element_type{TypeStruct(U32[1])};
const Id type{TypeArray(element_type, Const(num_elements))};
const Id pointer{TypePointer(spv::StorageClass::Private, type)}; const Id pointer{TypePointer(spv::StorageClass::Private, type)};
local_memory = AddGlobalVariable(pointer, spv::StorageClass::Private); local_memory = AddGlobalVariable(pointer, spv::StorageClass::Private);
if (profile.supported_spirv >= 0x00010400) { if (profile.supported_spirv >= 0x00010400) {
@@ -600,6 +601,10 @@ void EmitContext::DefineLocalMemory(const IR::Program& program) {
} }
void EmitContext::DefineSharedMemory(const IR::Program& program) { void EmitContext::DefineSharedMemory(const IR::Program& program) {
uses_explicit_workgroup_layout =
profile.support_explicit_workgroup_layout &&
(!program.info.uses_int8 || profile.support_workgroup_layout_8bit_access) &&
(!program.info.uses_int16 || profile.support_workgroup_layout_16bit_access);
if (program.shared_memory_size == 0) { if (program.shared_memory_size == 0) {
return; return;
} }
@@ -620,18 +625,18 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) {
return std::make_tuple(variable, element_pointer, pointer); return std::make_tuple(variable, element_pointer, pointer);
}}; }};
if (profile.support_explicit_workgroup_layout) { if (uses_explicit_workgroup_layout) {
AddExtension("SPV_KHR_workgroup_memory_explicit_layout"); AddExtension("SPV_KHR_workgroup_memory_explicit_layout");
AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR); AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR);
if (program.info.uses_int8) { if (program.info.uses_int8 && profile.support_int8) {
AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout8BitAccessKHR); AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout8BitAccessKHR);
std::tie(shared_memory_u8, shared_u8, std::ignore) = make(U8, 1); std::tie(shared_memory_u8, shared_u8, std::ignore) = make(U8, 1);
} }
if (program.info.uses_int16) { if (program.info.uses_int16 && profile.support_int16) {
AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR); AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR);
std::tie(shared_memory_u16, shared_u16, std::ignore) = make(U16, 2); std::tie(shared_memory_u16, shared_u16, std::ignore) = make(U16, 2);
} }
if (program.info.uses_int64) { if (program.info.uses_int64 && profile.support_int64) {
std::tie(shared_memory_u64, shared_u64, std::ignore) = make(U64, 8); std::tie(shared_memory_u64, shared_u64, std::ignore) = make(U64, 8);
} }
std::tie(shared_memory_u32, shared_u32, shared_memory_u32_type) = make(U32[1], 4); std::tie(shared_memory_u32, shared_u32, shared_memory_u32_type) = make(U32[1], 4);
@@ -1229,16 +1234,17 @@ void EmitContext::DefineStorageBuffers(const Info& info, u32& binding) {
} }
AddExtension("SPV_KHR_storage_buffer_storage_class"); AddExtension("SPV_KHR_storage_buffer_storage_class");
const IR::Type used_types{profile.support_descriptor_aliasing ? info.used_storage_buffer_types IR::Type used_types{profile.support_descriptor_aliasing ? info.used_storage_buffer_types
: IR::Type::U32}; : IR::Type::U32};
if (profile.support_int8 && profile.support_uniform_and_storage_buffer_8bit && used_types |= IR::Type::U32;
if (profile.support_int8 && profile.support_storage_buffer_8bit &&
True(used_types & IR::Type::U8)) { True(used_types & IR::Type::U8)) {
DefineSsbos(*this, storage_types.U8, &StorageDefinitions::U8, info, binding, U8, DefineSsbos(*this, storage_types.U8, &StorageDefinitions::U8, info, binding, U8,
sizeof(u8)); sizeof(u8));
DefineSsbos(*this, storage_types.S8, &StorageDefinitions::S8, info, binding, S8, DefineSsbos(*this, storage_types.S8, &StorageDefinitions::S8, info, binding, S8,
sizeof(u8)); sizeof(u8));
} }
if (profile.support_int16 && profile.support_uniform_and_storage_buffer_16bit && if (profile.support_int16 && profile.support_storage_buffer_16bit &&
True(used_types & IR::Type::U16)) { True(used_types & IR::Type::U16)) {
DefineSsbos(*this, storage_types.U16, &StorageDefinitions::U16, info, binding, U16, DefineSsbos(*this, storage_types.U16, &StorageDefinitions::U16, info, binding, U16,
sizeof(u16)); sizeof(u16));
@@ -311,6 +311,7 @@ public:
Id local_memory{}; Id local_memory{};
bool uses_explicit_workgroup_layout{};
Id shared_memory_u8{}; Id shared_memory_u8{};
Id shared_memory_u16{}; Id shared_memory_u16{};
Id shared_memory_u32{}; Id shared_memory_u32{};
@@ -371,6 +372,11 @@ public:
// Sirit::Id doesn't play nice with *::set<> // Sirit::Id doesn't play nice with *::set<>
ankerl::unordered_dense::set<u32> non_uniform_ids; ankerl::unordered_dense::set<u32> non_uniform_ids;
bool uses_nonuniform_sampled_image{};
bool uses_nonuniform_storage_image{};
bool uses_nonuniform_uniform_texel_buffer{};
bool uses_nonuniform_storage_texel_buffer{};
private: private:
void DefineCommonTypes(const Info& info); void DefineCommonTypes(const Info& info);
void DefineCommonConstants(); void DefineCommonConstants();
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -2100,6 +2100,14 @@ U32 IREmitter::ShuffleButterfly(const IR::U32& value, const IR::U32& index, cons
return Inst<U32>(Opcode::ShuffleButterfly, value, index, clamp, seg_mask); return Inst<U32>(Opcode::ShuffleButterfly, value, index, clamp, seg_mask);
} }
U32 IREmitter::QuadBroadcast(const IR::U32& value, const IR::U32& lane) {
return Inst<U32>(Opcode::QuadBroadcast, value, lane);
}
U32 IREmitter::QuadSwap(const IR::U32& value, const IR::U32& direction) {
return Inst<U32>(Opcode::QuadSwap, value, direction);
}
F32 IREmitter::FSwizzleAdd(const F32& a, const F32& b, const U32& swizzle, FpControl control) { F32 IREmitter::FSwizzleAdd(const F32& a, const F32& b, const U32& swizzle, FpControl control) {
return Inst<F32>(Opcode::FSwizzleAdd, Flags{control}, a, b, swizzle); return Inst<F32>(Opcode::FSwizzleAdd, Flags{control}, a, b, swizzle);
} }

Some files were not shown because too many files have changed in this diff Show More