Compare commits

..

79 Commits

Author SHA1 Message Date
Valeri b095a0242d Remove LGTM config
LGTM.com is no longer available since it was superseded by CodeQL.
2023-05-04 15:36:47 +03:00
Fernando S 2506594c50 Merge pull request #10153 from FernandoS27/a-quickie-fixie
Memory manager: Fix possible softlock
2023-05-04 03:56:53 +02:00
Fernando S 7d5683c63c Merge pull request #10154 from liamwhite/optimistic
settings: remove pessimistic flushing
2023-05-04 01:55:51 +02:00
bunnei 055ee84024 Merge pull request #10142 from FernandoS27/missing-astc
GPU: implement missing ASTC
2023-05-03 16:49:27 -07:00
Liam ae59ffc56c settings: remove pessimistic flushing 2023-05-03 18:52:42 -04:00
Fernando Sahmkow 1c13c74295 Memory manager: Fix possible softlock 2023-05-04 00:15:21 +02:00
bunnei a661c547d8 Merge pull request #10088 from FernandoS27/100-gelato-flavor-test-builds-later
Y.F.C Implement Asynchronous Fence manager and Rework Query async downloads
2023-05-03 15:10:22 -07:00
bunnei 737e1ca101 Merge pull request #10117 from liamwhite/sync-register
kernel: match calls to Register and Unregister
2023-05-03 09:07:19 -07:00
Fernando Sahmkow 87a9be8dec GPU: implement missing ASTC 2023-05-03 11:33:28 -04:00
liamwhite ffeb5cdd8d Merge pull request #10151 from GPUCode/no-softlocks-please
Fix softlocks when disabling async present
2023-05-03 10:54:24 -04:00
Morph 3ba95402fd Merge pull request #10146 from liamwhite/catch3
catch2: update to 3.3.1
2023-05-03 10:53:12 -04:00
Morph 8dd3baa562 Merge pull request #10144 from liamwhite/dont-turbo
vulkan: disable turbo when debugging tool is attached
2023-05-03 10:53:03 -04:00
Morph daf7936095 Merge pull request #10143 from liamwhite/fruit-company-moment
video_core: fix build on Apple Clang
2023-05-03 10:52:56 -04:00
Morph 627022bef9 Merge pull request #10124 from liamwhite/pebkac
settings: rename extended memory layout to unsafe, move from general to system
2023-05-03 10:52:45 -04:00
GPUCode f3fcc15ad5 vk_present_manager: Fix softlocks when disabling async present 2023-05-03 07:50:10 +03:00
Liam d1dd54cbfa catch2: update to 3.3.1 2023-05-02 21:27:17 -04:00
bunnei 8f43b05d6b Merge pull request #9973 from GPUCode/async-present
Implement asynchronous presentation
2023-05-02 17:54:57 -07:00
liamwhite 451b1bba26 Merge pull request #10133 from lat9nq/clang-shadow-and-fallthrough
CMakeLists: Enable more checks on Clang
2023-05-02 18:18:46 -04:00
liamwhite 494cc992eb Merge pull request #10130 from liamwhite/keys
qt: warn on inoperable keys
2023-05-02 18:17:24 -04:00
liamwhite f603dc9763 Merge pull request #10123 from Kelebek1/sample_mask
Define SampleMask as an array
2023-05-02 18:17:15 -04:00
Liam 4df49631de vulkan: disable turbo when debugging tool is attached 2023-05-02 18:14:57 -04:00
Liam e1c74cea10 video_core: fix build on Apple Clang 2023-05-02 18:05:30 -04:00
lat9nq f7292c776b CMake: Enable type limits on Clang 2023-05-02 02:07:36 -04:00
lat9nq 2007d0e4a0 CMakeLists: Enable checks on Clang
Enables shadow-uncaptured-locals and implicit-fallthrough for Clang.
implicit-fallthrough is not enabled by default in -Wall or -Wextra, and
shadow-uncaptured-local doesn't seem to be enabled by default by
-Wshadow, even though GCC has both of these by their respective cases.
2023-05-02 01:57:22 -04:00
GPUCode f403d27941 vk_present_manager: Add toggle for async presentation 2023-05-01 23:13:24 +03:00
GPUCode 1d7abac84b vk_blit_screen: Recreate FSR when frame is recreated
* Depends on the layout dimentions and thus should be recreated as well
2023-05-01 23:13:24 +03:00
GPUCode 50791cb974 renderer_vulkan: Fix crashing when updating descriptors
* During pipeline configure the function would acquire some payload space from the descriptor update queue,
  write the descriptor data on the GPU thread and give the scheduler a pointer to the beginning of said space to update it later.
  TickFrame resets the payload cursor, used to track acquires, back to the beginning of the buffer.
  This wasn't a problem before since WaitWorker was called at the end of the frame but now it is.
  If a frame writes to a cursor before the scheduler catches up, it will crash

* To fix this the payload buffer has been increased to account for the in flight frames that are allowed to exist now.
  TickFrame will switch between the payload spaces instead of resetting
2023-05-01 23:13:24 +03:00
GPUCode 2ad9acf795 renderer_vulkan: Async presentation 2023-05-01 23:13:24 +03:00
Morph 47938541c2 Merge pull request #10084 from FernandoS27/yuzu-goes-broom-broom
Y.F.C Buffer Cache Revamp
2023-05-01 11:08:02 -04:00
zhaobot 1f079d9566 Update translations (2023-05-01) (#10129)
Co-authored-by: The yuzu Community <noreply-fake@community.yuzu-emu.org>
2023-05-01 16:17:51 +02:00
liamwhite 3f4315e4c6 Merge pull request #10116 from liamwhite/deboost
kernel: remove boost intrusive lists
2023-05-01 09:27:23 -04:00
Fernando Sahmkow d6f565e5da BufferCache: Fixes and address feedback 2023-05-01 11:43:26 +02:00
Liam 7ec66db22c qt: warn on inoperable keys 2023-04-30 23:47:42 -04:00
Liam 2cd9e1ecb6 settings: rename extended memory layout to unsafe, move from general to system 2023-04-30 14:24:22 -04:00
Kelebek1 b566c98258 Define SampleMask as an array 2023-04-30 18:37:37 +01:00
bunnei 689f9a75a7 Merge pull request #10110 from Morph1984/intel-disable-compute
vk_pipeline_cache: Skip compute pipelines on Intel proprietary drivers
2023-04-29 23:02:45 -07:00
Liam b143ce8134 kernel: remove general boost lists 2023-04-29 22:11:32 -04:00
Liam 1b5c87ab6a kernel: match calls to Register and Unregister 2023-04-29 21:52:26 -04:00
Liam 2afaa7aed7 common: add intrusive list type 2023-04-29 17:46:26 -04:00
bunnei fe57f39676 Merge pull request #10082 from FernandoS27/the-testers-really-love-chocolate
Refactor Accelerate DMA and do downloads through TC.
2023-04-29 11:46:01 -07:00
Fernando Sahmkow 4bc5469f52 Texture Cache: Release stagging buffers on tick frame 2023-04-29 15:31:38 +02:00
Fernando Sahmkow f8d31d1ae1 Buffer Cache: Release stagging buffers on tick frame 2023-04-29 00:46:31 +02:00
Fernando Sahmkow fff6155bc3 Tests: Add memory tracker tests. 2023-04-29 00:46:31 +02:00
Fernando Sahmkow 80480fe3f7 Clang: format and ficx compile errors. 2023-04-29 00:46:31 +02:00
Fernando Sahmkow ed4553806a Implement Async downloads in normal and fix a few issues. 2023-04-29 00:46:31 +02:00
Fernando Sahmkow f2d3212de9 Buffer Cache rework: Setup async downloads. 2023-04-29 00:46:31 +02:00
Fernando Sahmkow a16c261131 Buffer Cache: Fully rework the buffer cache. 2023-04-29 00:46:31 +02:00
Fernando Sahmkow 58d1c7c77a Address Feedback & Clang Format 2023-04-29 00:18:21 +02:00
Fernando Sahmkow 56c9730a16 Maxwell3D: only update parameters on High 2023-04-29 00:18:21 +02:00
Fernando Sahmkow e3a2ca96bd Accelerate DMA: Use texture cache async downloads to perform the copies
to host.

WIP
2023-04-29 00:18:21 +02:00
Fernando Sahmkow 3fbee093b2 TextureCache: refactor DMA downloads to allow multiple buffers. 2023-04-29 00:18:21 +02:00
Morph cb092af3f0 vk_pipeline_cache: Skip compute pipelines on Intel proprietary drivers
Intel's SPIR-V shader compiler is broken. For now, skip compiling any compute pipelines until they fix this issue.
This is not a perfect workaround, as there are a small subset of non-compute pipelines that still cause it to crash, but this should cover the majority of crashes.
It is unfortunate that even with a test case reported 6 months ago the issue has not been fixed in favor of fixing "the most popular games and apps".
Intel, you can do better than this.
2023-04-28 17:59:36 -04:00
Fernando Sahmkow 2f15876524 QueryCache: Fix write invalidation. 2023-04-28 23:53:46 +02:00
Fernando Sahmkow 9a7c172f76 MemoryManager: Fix race conditions. 2023-04-28 23:53:02 +02:00
Fernando Sahmkow e4dc73f61e Clang format and ddress feedback 2023-04-24 12:38:47 +02:00
Fernando S 9bf19b04f6 Merge pull request #10051 from liamwhite/surface-capabilities
vulkan: pick alpha composite flags based on available values
2023-04-24 12:37:13 +02:00
Fernando S 47cd0586ee Merge pull request #10056 from vonchenplus/audout_u
core: audio: return result when audio_out initialize failed
2023-04-24 12:36:52 +02:00
Fernando S 2311fa7c84 Merge pull request #10069 from liamwhite/log
maxwell_3d: fix out of bounds array access in size estimation
2023-04-24 12:36:24 +02:00
Fernando Sahmkow e29ced29fa QueryCache: rework async downloads. 2023-04-23 22:04:14 +02:00
Fernando Sahmkow 7e76c1642c Accuracy Normal: reduce accuracy further for perf improvements in Project Lime 2023-04-23 22:03:44 +02:00
Fernando Sahmkow fca72beb2d Fence Manager: implement async fence management in a sepparate thread. 2023-04-23 04:48:50 +02:00
Liam eb7c2314f6 maxwell_3d: fix out of bounds array access in size estimation 2023-04-22 10:35:26 -04:00
Fernando S d600183583 Merge pull request #10074 from Kelebek1/fermi_blit
Account for a pre-added offset when using Corner sample mode for 2D blits
2023-04-22 12:06:00 +02:00
bunnei 0f1ff5f34e Merge pull request #10076 from german77/TryPopMyFriend
core: am: Demote TryPopFromFriendInvitationStorageChannel Log level
2023-04-21 23:15:07 -07:00
bunnei d0e6eafe23 Merge pull request #10068 from twitchax/twitchax/dr_bind_address
Allow passing `--bind-address` to dedicated room.
2023-04-21 23:13:51 -07:00
bunnei 74d203fbe3 Merge pull request #10060 from german77/no_dead
core: hid: Remove deadzone of virtual controller
2023-04-21 23:13:21 -07:00
german77 7ffc42c397 core: am: Demote TryPopFromFriendInvitationStorageChannel Log level 2023-04-21 22:35:45 -06:00
Kelebek1 4e14b64bfc Account for a pre-added offset when using Corner sample mode for 2D blits 2023-04-21 19:08:21 +01:00
bunnei 0cfeb2e8d7 Merge pull request #10057 from liamwhite/its-not-in-the-timeline
vulkan: use plain fences when timeline semaphores are not available
2023-04-19 16:50:24 -07:00
Aaron Roney 79e32127b3 Run clang-format to fix all. 2023-04-19 17:52:09 +00:00
bunnei 799579c8d2 Merge pull request #10053 from german77/nfp_full
service: nfp: Implement all interfaces
2023-04-19 10:36:28 -07:00
Aaron Roney 3e7af5fbd7 Fix formatting. 2023-04-19 16:26:49 +00:00
Aaron Roney 34d0d94df0 Allow passing bind_address to dedicated room. 2023-04-19 05:37:30 +00:00
german77 70a97fb5c7 core: hid: Remove deadzone of virtual controller 2023-04-15 18:41:09 -06:00
Liam e3fb9b5e00 vulkan: use plain fences when timeline semaphores are not available 2023-04-14 22:53:37 -04:00
Narr the Reg 307371e01d service: nfp: Implement debug Interface 2023-04-14 17:03:58 -06:00
Narr the Reg 00800d5289 service: nfp: Implement system interface 2023-04-14 17:03:57 -06:00
Narr the Reg 101c0df79c service: nfp: Use an unique interface 2023-04-13 22:24:27 -06:00
Liam e37e1d24f9 vulkan: pick alpha composite flags based on available values 2023-04-13 16:38:20 -04:00
132 changed files with 6742 additions and 3518 deletions
-13
View File
@@ -1,13 +0,0 @@
# SPDX-FileCopyrightText: 2020 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
path_classifiers:
library: "externals"
extraction:
cpp:
prepare:
packages:
- "libsdl2-dev"
- "qtmultimedia5-dev"
- "libtbb-dev"
- "libjack-jackd2-dev"
+2 -2
View File
@@ -1372,8 +1372,8 @@ This would ban both their forum username and their IP address.</source>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Interfície de memòria ampliada (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+1 -1
View File
@@ -1364,7 +1364,7 @@ Tato možnost zlepšuje rychlost díky závislosti na sémantice cmpxchg pro zaj
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
+2 -2
View File
@@ -1380,8 +1380,8 @@ Dette vil bandlyse både vedkommendes forum-brugernavn og IP-adresse.</translati
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Udvidet hukommelsesopsætning (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+2 -2
View File
@@ -1360,8 +1360,8 @@ This would ban both their forum username and their IP address.</source>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Erweitertes Speicherlayout (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+2 -2
View File
@@ -1364,8 +1364,8 @@ This would ban both their forum username and their IP address.</source>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Διάταξη εκτεταμένης μνήμης (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+47 -47
View File
@@ -381,17 +381,17 @@ Esto banearía su nombre del foro y su dirección IP.</translation>
<message>
<location filename="../../src/yuzu/configuration/configure_audio.ui" line="42"/>
<source>Output Device:</source>
<translation type="unfinished"/>
<translation>Dispositivo de salida:</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_audio.ui" line="56"/>
<source>Input Device:</source>
<translation type="unfinished"/>
<translation>Dispositivo de entrada:</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_audio.ui" line="70"/>
<source>Sound Output Mode:</source>
<translation type="unfinished"/>
<translation>Método de salida de sonido:</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_audio.ui" line="78"/>
@@ -1383,8 +1383,8 @@ Esto banearía su nombre del foro y su dirección IP.</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Interfaz de memoria extendida (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation>Interfaz de memoria extendida (8GB DRAM)</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
@@ -1638,7 +1638,7 @@ Esto banearía su nombre del foro y su dirección IP.</translation>
<message>
<location filename="../../src/yuzu/configuration/configure_graphics.ui" line="463"/>
<source>AMD FidelityFX Super Resolution</source>
<translation type="unfinished"/>
<translation>AMD FidelityFX Super Resolution</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_graphics.ui" line="489"/>
@@ -1753,12 +1753,12 @@ Esto banearía su nombre del foro y su dirección IP.</translation>
<message>
<location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
<source>Enables asynchronous ASTC texture decoding, which may reduce load time stutter. This feature is experimental.</source>
<translation type="unfinished"/>
<translation>Activa la decodificación de texturas asíncrona de ASTC, lo cuál podría reducir la duración de los parones. Esta función es experimental.</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="98"/>
<source>Decode ASTC textures asynchronously (Hack)</source>
<translation type="unfinished"/>
<translation>Decodificar texturas ASTC de manera asíncrona (Hack)</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="105"/>
@@ -2268,12 +2268,12 @@ Esto banearía su nombre del foro y su dirección IP.</translation>
<message>
<location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2710"/>
<source>Enable direct JoyCon driver</source>
<translation type="unfinished"/>
<translation>Activar driver directo JoyCon</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2726"/>
<source>Enable direct Pro Controller driver [EXPERIMENTAL]</source>
<translation type="unfinished"/>
<translation>Activar driver directo Pro Controller [EXPERIMENTAL]</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2739"/>
@@ -2637,7 +2637,7 @@ Esto banearía su nombre del foro y su dirección IP.</translation>
<message>
<location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="399"/>
<source>Turbo button</source>
<translation type="unfinished"/>
<translation>Botón Turbo</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="407"/>
@@ -3339,7 +3339,7 @@ UUID: %2</translation>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="52"/>
<source>Virtual Ring Sensor Parameters</source>
<translation type="unfinished"/>
<translation>Parámetros del sensor Ring virtual</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="84"/>
@@ -3361,29 +3361,29 @@ UUID: %2</translation>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="233"/>
<source>Direct Joycon Driver</source>
<translation type="unfinished"/>
<translation>Driver directo del JoyCon</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="293"/>
<source>Enable Ring Input</source>
<translation type="unfinished"/>
<translation>Activar entrada del Ring</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="300"/>
<location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="314"/>
<source>Enable</source>
<translation type="unfinished"/>
<translation>Activar</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="307"/>
<source>Ring Sensor Value</source>
<translation type="unfinished"/>
<translation>Valor del sensor Ring</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="314"/>
<location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="282"/>
<source>Not connected</source>
<translation type="unfinished"/>
<translation>No conectado</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="344"/>
@@ -3414,12 +3414,12 @@ UUID: %2</translation>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="279"/>
<source>Error enabling ring input</source>
<translation type="unfinished"/>
<translation>Error al activar la entrada del Ring</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="285"/>
<source>Direct Joycon driver is not enabled</source>
<translation type="unfinished"/>
<translation>El driver directo JoyCon no está activo.</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="290"/>
@@ -3429,17 +3429,17 @@ UUID: %2</translation>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="302"/>
<source>The current mapped device doesn&apos;t support the ring controller</source>
<translation type="unfinished"/>
<translation>El dispositivo de entrada actual no soporta el control Ring.</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="306"/>
<source>The current mapped device doesn&apos;t have a ring attached</source>
<translation type="unfinished"/>
<translation>El dispositivo de entrada actual no tiene puesto el Ring</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="310"/>
<source>Unexpected driver result %1</source>
<translation type="unfinished"/>
<translation>Resultado inesperado del driver %1</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="333"/>
@@ -4498,12 +4498,12 @@ Arrastra los puntos para cambiar de posición, o haz doble clic en las celdas de
<message>
<location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="47"/>
<source>Server Address</source>
<translation type="unfinished"/>
<translation>Dirección del Servidor</translation>
</message>
<message>
<location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="54"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Server address of the host&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"/>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Dirección del servidor del anfitrión&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="64"/>
@@ -4617,12 +4617,12 @@ Arrastra los puntos para cambiar de posición, o haz doble clic en las celdas de
<message>
<location filename="../../src/yuzu/main.cpp" line="1230"/>
<source>Emulated mouse is enabled</source>
<translation type="unfinished"/>
<translation>El ratón emulado está activado</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="1231"/>
<source>Real mouse input and mouse panning are incompatible. Please disable the emulated mouse in input advanced settings to allow mouse panning.</source>
<translation type="unfinished"/>
<translation>La entrada de un ratón real y la panoramización del ratón son incompatibles. Por favor, desactive el ratón emulado en la configuración avanzada de entrada para permitir así la panoramización del ratón.</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="1453"/>
@@ -5489,13 +5489,13 @@ Por favor, utiliza esta función sólo para instalar actualizaciones y DLCs.</tr
<message>
<location filename="../../src/yuzu/main.cpp" line="4069"/>
<source>VOLUME: MUTE</source>
<translation type="unfinished"/>
<translation>VOLUMEN: SILENCIO</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="4072"/>
<source>VOLUME: %1%</source>
<comment>Volume percentage (e.g. 50%)</comment>
<translation type="unfinished"/>
<translation>VOLUMEN: %1%</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="4153"/>
@@ -6227,7 +6227,7 @@ Mensaje de depuración: </translation>
<message>
<location filename="../../src/yuzu/multiplayer/lobby.ui" line="83"/>
<source>Hide Empty Rooms</source>
<translation type="unfinished"/>
<translation>Ocultar salas vacías</translation>
</message>
<message>
<location filename="../../src/yuzu/multiplayer/lobby.ui" line="90"/>
@@ -7152,12 +7152,12 @@ p, li { white-space: pre-wrap; }
<message>
<location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="78"/>
<source>Stick L</source>
<translation type="unfinished"/>
<translation>Palanca L</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="80"/>
<source>Stick R</source>
<translation type="unfinished"/>
<translation>Palanca R</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
@@ -7214,19 +7214,19 @@ p, li { white-space: pre-wrap; }
<message>
<location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="191"/>
<source>%1%2%3%4</source>
<translation type="unfinished"/>
<translation>%1%2%3%4</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="205"/>
<location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="229"/>
<source>%1%2%3Hat %4</source>
<translation type="unfinished"/>
<translation>%1%2%3Rotación %4</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="223"/>
<location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="238"/>
<source>%1%2%3Button %4</source>
<translation type="unfinished"/>
<translation>%1%2%3Botón %4</translation>
</message>
</context>
<context>
@@ -7647,7 +7647,7 @@ Por favor, inténtalo de nuevo o contacta con el desarrollador del software.</tr
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="166"/>
<source>Profile Creator</source>
<translation type="unfinished"/>
<translation>Creador de perfil</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="169"/>
@@ -7658,57 +7658,57 @@ Por favor, inténtalo de nuevo o contacta con el desarrollador del software.</tr
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="172"/>
<source>Profile Icon Editor</source>
<translation type="unfinished"/>
<translation>Editor de icono de perfil</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="175"/>
<source>Profile Nickname Editor</source>
<translation type="unfinished"/>
<translation>Editor de nombre de perfil</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="197"/>
<source>Who will receive the points?</source>
<translation type="unfinished"/>
<translation>¿Quién recibirá los puntos?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="200"/>
<source>Who is using Nintendo eShop?</source>
<translation type="unfinished"/>
<translation>¿Quién va a utilizar Nintendo eShop?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="203"/>
<source>Who is making this purchase?</source>
<translation type="unfinished"/>
<translation>¿Quién está haciendo la compra?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="206"/>
<source>Who is posting?</source>
<translation type="unfinished"/>
<translation>¿Quién está publicando esto?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="209"/>
<source>Select a user to link to a Nintendo Account.</source>
<translation type="unfinished"/>
<translation>Elige un usuario para vincularlo a una Cuenta Nintendo.</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="212"/>
<source>Change settings for which user?</source>
<translation type="unfinished"/>
<translation>¿Para qué usuario desea cambiar la configuración?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="215"/>
<source>Format data for which user?</source>
<translation type="unfinished"/>
<translation>¿Para qué usuario se borrarán sus datos?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="218"/>
<source>Which user will be transferred to another console?</source>
<translation type="unfinished"/>
<translation>¿Qué usuario será transferido a otra consola?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="221"/>
<source>Send save data for which user?</source>
<translation type="unfinished"/>
<translation>¿A qué usuario se le enviarán los datos de guardado?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="225"/>
@@ -7774,7 +7774,7 @@ p, li { white-space: pre-wrap; }
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="158"/>
<source>[%1] %2</source>
<translation type="unfinished"/>
<translation>[%1] %2</translation>
</message>
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="184"/>
+14 -14
View File
@@ -1382,8 +1382,8 @@ Cette option améliore la vitesse en réduisant la précision des instructions f
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Disposition de la mémoire étendue (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation>Disposition de la mémoire étendue (8 Go de DRAM)</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
@@ -7637,7 +7637,7 @@ Veuillez essayer à nouveau ou contactez le développeur du logiciel.</translati
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="166"/>
<source>Profile Creator</source>
<translation type="unfinished"/>
<translation>Créateur de profil</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="169"/>
@@ -7648,57 +7648,57 @@ Veuillez essayer à nouveau ou contactez le développeur du logiciel.</translati
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="172"/>
<source>Profile Icon Editor</source>
<translation type="unfinished"/>
<translation>Éditeur d&apos;icônes de profil</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="175"/>
<source>Profile Nickname Editor</source>
<translation type="unfinished"/>
<translation>Éditeur de surnom de profil</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="197"/>
<source>Who will receive the points?</source>
<translation type="unfinished"/>
<translation>Qui recevra les points ?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="200"/>
<source>Who is using Nintendo eShop?</source>
<translation type="unfinished"/>
<translation>Qui utilise le Nintendo eShop ?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="203"/>
<source>Who is making this purchase?</source>
<translation type="unfinished"/>
<translation>Qui effectue cet achat ?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="206"/>
<source>Who is posting?</source>
<translation type="unfinished"/>
<translation>Qui publie ?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="209"/>
<source>Select a user to link to a Nintendo Account.</source>
<translation type="unfinished"/>
<translation>Sélectionnez un utilisateur à associer à un compte Nintendo.</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="212"/>
<source>Change settings for which user?</source>
<translation type="unfinished"/>
<translation>Modifier les paramètres pour quel utilisateur ?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="215"/>
<source>Format data for which user?</source>
<translation type="unfinished"/>
<translation>Formater les données pour quel utilisateur ?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="218"/>
<source>Which user will be transferred to another console?</source>
<translation type="unfinished"/>
<translation>Quel utilisateur sera transféré sur une autre console ?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="221"/>
<source>Send save data for which user?</source>
<translation type="unfinished"/>
<translation>Envoyer les données de sauvegarde pour quel utilisateur ?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="225"/>
+2 -2
View File
@@ -1339,8 +1339,8 @@ Memungkinkan berbagai macam optimasi IR.</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Tata letak memori yang diperluas (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+7 -7
View File
@@ -1368,8 +1368,8 @@ Questo bannerà sia il suo nome utente del forum che il suo indirizzo IP.</trans
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Layout di memoria esteso (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
@@ -1846,7 +1846,7 @@ Questo bannerà sia il suo nome utente del forum che il suo indirizzo IP.</trans
<message>
<location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="52"/>
<source>Restore Defaults</source>
<translation>Ripristina predefiniti</translation>
<translation>Ripristina predefinite</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="98"/>
@@ -1894,7 +1894,7 @@ Questo bannerà sia il suo nome utente del forum che il suo indirizzo IP.</trans
<message>
<location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="343"/>
<source>Restore Default</source>
<translation>Ripristina predefinito</translation>
<translation>Ripristina predefinita</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="344"/>
@@ -3886,7 +3886,7 @@ UUID: %2</translation>
<message>
<location filename="../../src/yuzu/configuration/configure_tas.ui" line="77"/>
<source>Pause execution during loads</source>
<translation type="unfinished"/>
<translation>Metti in pausa l&apos;esecuzione durante i caricamenti</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_tas.ui" line="91"/>
@@ -5352,7 +5352,7 @@ Configurazione &amp;gt; Web.</translation>
<location filename="../../src/yuzu/main.cpp" line="3949"/>
<source>Scale: %1x</source>
<comment>%1 is the resolution scaling factor</comment>
<translation type="unfinished"/>
<translation>Risoluzione: %1x</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3952"/>
@@ -5601,7 +5601,7 @@ Desideri uscire comunque?</translation>
<message>
<location filename="../../src/yuzu/bootmanager.cpp" line="992"/>
<source>yuzu has not been compiled with OpenGL support.</source>
<translation>yuzu non è stato compilato con il supporto OpenGL.</translation>
<translation>yuzu è stato compilato senza il supporto a OpenGL.</translation>
</message>
<message>
<location filename="../../src/yuzu/bootmanager.cpp" line="1016"/>
+2 -2
View File
@@ -1383,8 +1383,8 @@ This would ban both their forum username and their IP address.</source>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>6GB DRAM</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+2 -2
View File
@@ -1384,8 +1384,8 @@ This would ban both their forum username and their IP address.</source>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation> (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+2 -2
View File
@@ -1355,8 +1355,8 @@ This would ban both their forum username and their IP address.</source>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Utvidet minneutforming (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+979 -937
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1377,8 +1377,8 @@ Gdy ta opcja jest włączona, niedopasowanie jest uruchamiane tylko wtedy, gdy d
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Rozszerzony układ pamięci (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+2 -2
View File
@@ -1383,8 +1383,8 @@ Isto banirá tanto o nome de usuário do fórum como o endereço IP.</translatio
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Layout de memória extendida (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+2 -2
View File
@@ -1373,8 +1373,8 @@ Isto banirá tanto o nome de usuário do fórum como o endereço IP.</translatio
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Layout de memória extendida (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+106 -69
View File
@@ -381,17 +381,17 @@ This would ban both their forum username and their IP address.</source>
<message>
<location filename="../../src/yuzu/configuration/configure_audio.ui" line="42"/>
<source>Output Device:</source>
<translation type="unfinished"/>
<translation>Устройство вывода:</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_audio.ui" line="56"/>
<source>Input Device:</source>
<translation type="unfinished"/>
<translation>Устройство ввода:</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_audio.ui" line="70"/>
<source>Sound Output Mode:</source>
<translation type="unfinished"/>
<translation>Режим вывода звука:</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_audio.ui" line="78"/>
@@ -551,13 +551,13 @@ This would ban both their forum username and their IP address.</source>
&lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
</source>
<translation>
&lt;div&gt;Эта опция повышает скорость, уменьшая точность сложенных умноженных инструкций на ЦП без поддержки FMA.&lt;/div&gt;
&lt;div&gt;Эта опция повышает скорость за счет снижения точности инструкций fused-multiply-add на ЦП без встроенной поддержки FMA.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
<source>Unfuse FMA (improve performance on CPUs without FMA)</source>
<translation>Не использовать FMA (улучшает производительность на ЦП без FMA)</translation>
<translation>Отключить FMA (улучшает производительность на ЦП без FMA)</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu.ui" line="113"/>
@@ -565,7 +565,7 @@ This would ban both their forum username and their IP address.</source>
&lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
</source>
<translation>
&lt;div&gt;Эта опция повышает скорость некоторых аппроксимирующих функций с плавающей точкой за счет использования менее точных нативных приближений.&lt;/div&gt;
&lt;div&gt;Эта опция повышает скорость некоторых приближенных функций с плавающей точкой за счет использования менее точных нативных приближений&lt;/div&gt;
</translation>
</message>
<message>
@@ -579,13 +579,13 @@ This would ban both their forum username and their IP address.</source>
&lt;div&gt;This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes.&lt;/div&gt;
</source>
<translation>
&lt;div&gt;Эта опция улучшает скорость 32-битных ASIMD-функций с плавающей запятой путём работы с некорректными режимами округления.&lt;/div&gt;
&lt;div&gt;Эта опция повышает скорость работы 32-битных ASIMD-функций с плавающей запятой, работая с неправильными режимами округления.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu.ui" line="130"/>
<source>Faster ASIMD instructions (32 bits only)</source>
<translation>Более быстрые инструкции ASIMD (только 32 бит)</translation>
<translation>Ускоренные инструкции ASIMD (только 32 бит)</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu.ui" line="137"/>
@@ -593,20 +593,22 @@ This would ban both their forum username and their IP address.</source>
&lt;div&gt;This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.&lt;/div&gt;
</source>
<translation>
&lt;div&gt;Эта опция повышает скорость, убирая проверку на NaN. Обратите внимание, что это также снижает точность некоторых инструкций с плавающей точкой.&lt;/div&gt;
&lt;div&gt;Эта опция повышает скорость, удаляя проверку NaN. Обратите внимание, что это также снижает точность некоторых инструкций с плавающей точкой.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu.ui" line="142"/>
<source>Inaccurate NaN handling</source>
<translation>Неправильная обработка NaN</translation>
<translation>Неточная обработка NaN</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu.ui" line="149"/>
<source>
&lt;div&gt;This option improves speed by eliminating a safety check before every memory read/write in guest. Disabling it may allow a game to read/write the emulator's memory.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div&gt;Эта опция повышает скорость за счет исключения проверки безопасности перед каждым чтением/записью памяти в гостевом режиме. Отключение этой опции может позволить игре читать/записывать память эмулятора.
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu.ui" line="154"/>
@@ -618,12 +620,14 @@ This would ban both their forum username and their IP address.</source>
<source>
&lt;div&gt;This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div&gt;Эта опция повышает скорость, полагаясь только на семантику cmpxchg для обеспечения безопасности инструкций исключительного доступа. Обратите внимание, что это может привести к полным зависаниям и другим условиям гонки.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu.ui" line="166"/>
<source>Ignore global monitor</source>
<translation type="unfinished"/>
<translation>Игнорировать глобальный мониторинг</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu.ui" line="191"/>
@@ -660,7 +664,11 @@ This would ban both their forum username and their IP address.</source>
&lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div style=&quot;white-space: nowrap&quot;&gt;Эта оптимизация ускоряет доступ гостевой программы к памяти.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt; Включение этой оптимизации встраивает доступ к указателям PageTable::pointers в эмулируемый код.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Отключение этой функции заставляет все обращения к памяти проходить через функции Memory::Read/Memory::Write.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="48"/>
@@ -672,7 +680,9 @@ This would ban both their forum username and their IP address.</source>
<source>
&lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div&gt;Эта оптимизация позволяет избежать поиска диспетчера, позволяя эмитированным базовым блокам переходить непосредственно к другим базовым блокам, если конечный ПК статичен.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
@@ -684,7 +694,9 @@ This would ban both their forum username and their IP address.</source>
<source>
&lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div&gt;Эта оптимизация позволяет избежать поиска диспетчера, отслеживая потенциальные адреса возврата инструкций BL. Это приближено к тому, что происходит с буфером стека возврата на реальном ЦП.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
@@ -696,7 +708,9 @@ This would ban both their forum username and their IP address.</source>
<source>
&lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div&gt;Включите двухуровневую систему диспетчеризации. Сначала используется более быстрый диспетчер, написанный на ассемблере и имеющий небольшой кэш MRU для мест назначения переходов. Если он не справляется, диспетчеризация возвращается к более медленному диспетчеру на C++.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
@@ -708,7 +722,9 @@ This would ban both their forum username and their IP address.</source>
<source>
&lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div&gt;Включает IR-оптимизацию, которая уменьшает ненужные обращения к структуре контекста ЦП.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
@@ -720,19 +736,23 @@ This would ban both their forum username and their IP address.</source>
<source>
&lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div&gt;Включает IR-оптимизацию, которая включает распространение констант.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
<source>Enable constant propagation</source>
<translation>Включить постоянное распространение</translation>
<translation>Включить распространение констант</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="115"/>
<source>
&lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div&gt;Включает различные IR-оптимизации.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
@@ -745,12 +765,15 @@ This would ban both their forum username and their IP address.</source>
&lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div style=&quot;white-space: nowrap&quot;&gt;Если функция включена, смещение срабатывает только тогда, когда доступ пересекает границу страницы.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Если отключено, смещение срабатывает при всех смещенных доступах.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="133"/>
<source>Enable misalignment check reduction</source>
<translation type="unfinished"/>
<translation>Включить уменьшение проверки несоосности</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="140"/>
@@ -759,12 +782,16 @@ This would ban both their forum username and their IP address.</source>
&lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to use Software MMU Emulation.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div style=&quot;white-space: nowrap&quot;&gt;Эта оптимизация ускоряет доступ гостевой программы к памяти.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt; Включение этой оптимизации приводит к тому, что чтение/запись гостевой памяти производится непосредственно в память и использует MMU хоста.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Отключение этой функции заставляет все обращения к памяти использовать программную эмуляцию MMU.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="147"/>
<source>Enable Host MMU Emulation (general memory instructions)</source>
<translation type="unfinished"/>
<translation>Включить эмуляцию MMU хоста (инструкции общей памяти)</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="154"/>
@@ -773,12 +800,16 @@ This would ban both their forum username and their IP address.</source>
&lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it causes guest exclusive memory reads/writes to be done directly into memory and make use of Host's MMU.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all exclusive memory accesses to use Software MMU Emulation.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div style=&quot;white-space: nowrap&quot;&gt;Эта оптимизация ускоряет доступ гостевой программы к эксклюзивной памяти.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Включение этой оптимизации приводит к тому, что чтение/запись в эксклюзивную память гостя выполняется непосредственно в память и использует MMU хоста.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt; Отключение этой функции заставляет все эксклюзивные доступы к памяти использовать эмуляцию программного MMU.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="161"/>
<source>Enable Host MMU Emulation (exclusive memory instructions)</source>
<translation type="unfinished"/>
<translation>Включить эмуляцию MMU хоста (инструкции исключительной памяти)</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="168"/>
@@ -786,12 +817,15 @@ This would ban both their forum username and their IP address.</source>
&lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up exclusive memory accesses by the guest program.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it reduces the overhead of fastmem failure of exclusive memory accesses.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div style=&quot;white-space: nowrap&quot;&gt;Эта оптимизация ускоряет обращение гостевой программы к исключительной памяти.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Ее включение снижает накладные расходы, связанные с отказом fastmem при доступе к исключительной памяти.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="174"/>
<source>Enable recompilation of exclusive memory instructions</source>
<translation type="unfinished"/>
<translation>Разрешить перекомпиляцию инструкций исключительной памяти</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="181"/>
@@ -799,7 +833,10 @@ This would ban both their forum username and their IP address.</source>
&lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by allowing invalid memory accesses to succeed.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it reduces the overhead of all memory accesses and has no impact on programs that don't access invalid memory.&lt;/div&gt;
</source>
<translation type="unfinished"/>
<translation>
&lt;div style=&quot;white-space: nowrap&quot;&gt;Эта оптимизация ускоряет обращение к памяти, позволяя успешное обращение к недопустимой памяти.&lt;/div&gt;
&lt;div style=&quot;white-space: nowrap&quot;&gt;Включение этой оптимизации снижает накладные расходы на все обращения к памяти и не влияет на программы, которые не обращаются к недопустимой памяти.&lt;/div&gt;
</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="187"/>
@@ -917,17 +954,17 @@ This would ban both their forum username and their IP address.</source>
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="198"/>
<source>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</source>
<translation>Если включено, отключает компилятор макроса Just In Time. Включение опции делает игры медленнее</translation>
<translation>Если включено, отключает компилятор макроса Just In Time. Включение этого параметра замедляет работу игр</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
<source>Disable Macro JIT</source>
<translation>Отключить Macro JIT</translation>
<translation>Отключить макрос JIT</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="211"/>
<source>When checked, it disables the macro HLE functions. Enabling this makes games run slower</source>
<translation type="unfinished"/>
<translation>Если флажок установлен, он отключает функции макроса HLE. Включение этого параметра замедляет работу игр</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="214"/>
@@ -947,12 +984,12 @@ This would ban both their forum username and their IP address.</source>
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="231"/>
<source>When checked, it executes shaders without loop logic changes</source>
<translation type="unfinished"/>
<translation>Если включено, шейдеры выполняются без изменения логики цикла</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="234"/>
<source>Disable Loop safety checks</source>
<translation type="unfinished"/>
<translation>Отключить проверку безопасности цикла</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="244"/>
@@ -967,12 +1004,12 @@ This would ban both their forum username and their IP address.</source>
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="257"/>
<source>Enable FS Access Log</source>
<translation type="unfinished"/>
<translation>Включить журнал доступа к ФС</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="264"/>
<source>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</source>
<translation type="unfinished"/>
<translation>Включите эту опцию, чтобы вывести на консоль последний сгенерированный список аудиокоманд. Влияет только на игры, использующие аудио рендерер.</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="267"/>
@@ -1007,7 +1044,7 @@ This would ban both their forum username and their IP address.</source>
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="311"/>
<source>Enable Auto-Stub**</source>
<translation type="unfinished"/>
<translation>Включить автоподставку**</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="318"/>
@@ -1346,8 +1383,8 @@ This would ban both their forum username and their IP address.</source>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Расширенная компоновка памяти (6 ГБ DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation>Расширенная компоновка памяти (8 ГБ DRAM)</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
@@ -1716,12 +1753,12 @@ This would ban both their forum username and their IP address.</source>
<message>
<location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
<source>Enables asynchronous ASTC texture decoding, which may reduce load time stutter. This feature is experimental.</source>
<translation type="unfinished"/>
<translation>Включает асинхронное декодирование текстур ASTC, что может уменьшить фризы при загрузке. Эта функция является экспериментальной.</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="98"/>
<source>Decode ASTC textures asynchronously (Hack)</source>
<translation type="unfinished"/>
<translation>Асинхронное декодирование текстур ASTC (Хак)</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="105"/>
@@ -2236,7 +2273,7 @@ This would ban both their forum username and their IP address.</source>
<message>
<location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2726"/>
<source>Enable direct Pro Controller driver [EXPERIMENTAL]</source>
<translation type="unfinished"/>
<translation>Включить прямой драйвер Pro Controller [ЭКСПЕРИМЕНТАЛЬНО]</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2739"/>
@@ -4580,12 +4617,12 @@ Drag points to change position, or double-click table cells to edit values.</sou
<message>
<location filename="../../src/yuzu/main.cpp" line="1230"/>
<source>Emulated mouse is enabled</source>
<translation type="unfinished"/>
<translation>Эмулированная мышь включена</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="1231"/>
<source>Real mouse input and mouse panning are incompatible. Please disable the emulated mouse in input advanced settings to allow mouse panning.</source>
<translation type="unfinished"/>
<translation>Ввод реальной мыши и панорамирование мышью несовместимы. Пожалуйста, отключите эмулированную мышь в расширенных настройках ввода, чтобы разрешить панорамирование мышью.</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="1453"/>
@@ -7613,7 +7650,7 @@ Please try again or contact the developer of the software.</source>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="166"/>
<source>Profile Creator</source>
<translation type="unfinished"/>
<translation>Создатель профиля</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="169"/>
@@ -7624,57 +7661,57 @@ Please try again or contact the developer of the software.</source>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="172"/>
<source>Profile Icon Editor</source>
<translation type="unfinished"/>
<translation>Редактор иконки профиля</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="175"/>
<source>Profile Nickname Editor</source>
<translation type="unfinished"/>
<translation>Редактор никнейма профиля</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="197"/>
<source>Who will receive the points?</source>
<translation type="unfinished"/>
<translation>Кто будет получать очки?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="200"/>
<source>Who is using Nintendo eShop?</source>
<translation type="unfinished"/>
<translation>Кто использует Nintendo eShop?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="203"/>
<source>Who is making this purchase?</source>
<translation type="unfinished"/>
<translation>Кто совершает эту покупку?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="206"/>
<source>Who is posting?</source>
<translation type="unfinished"/>
<translation>Кто публикует?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="209"/>
<source>Select a user to link to a Nintendo Account.</source>
<translation type="unfinished"/>
<translation>Выберите пользователя для привязки к учетной записи Nintendo.</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="212"/>
<source>Change settings for which user?</source>
<translation type="unfinished"/>
<translation>Изменить настройки для какого пользователя?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="215"/>
<source>Format data for which user?</source>
<translation type="unfinished"/>
<translation>Форматировать данные для какого пользователя?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="218"/>
<source>Which user will be transferred to another console?</source>
<translation type="unfinished"/>
<translation>Какой пользователь будет переходить на другую консоль?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="221"/>
<source>Send save data for which user?</source>
<translation type="unfinished"/>
<translation>Отправить сохранение какому пользователю?</translation>
</message>
<message>
<location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="225"/>
@@ -7740,7 +7777,7 @@ p, li { white-space: pre-wrap; }
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="158"/>
<source>[%1] %2</source>
<translation type="unfinished"/>
<translation>[%1] %2</translation>
</message>
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="184"/>
@@ -7753,17 +7790,17 @@ p, li { white-space: pre-wrap; }
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="202"/>
<source>runnable</source>
<translation type="unfinished"/>
<translation>runnable</translation>
</message>
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="204"/>
<source>paused</source>
<translation type="unfinished"/>
<translation>paused</translation>
</message>
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="210"/>
<source>sleeping</source>
<translation type="unfinished"/>
<translation>sleeping</translation>
</message>
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="213"/>
@@ -7778,32 +7815,32 @@ p, li { white-space: pre-wrap; }
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="219"/>
<source>waiting for condition variable</source>
<translation type="unfinished"/>
<translation>waiting for condition variable</translation>
</message>
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="222"/>
<source>waiting for address arbiter</source>
<translation type="unfinished"/>
<translation>waiting for address arbiter</translation>
</message>
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="225"/>
<source>waiting for suspend resume</source>
<translation type="unfinished"/>
<translation>waiting for suspend resume</translation>
</message>
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="228"/>
<source>waiting</source>
<translation type="unfinished"/>
<translation>waiting</translation>
</message>
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="233"/>
<source>initialized</source>
<translation type="unfinished"/>
<translation>initialized</translation>
</message>
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="236"/>
<source>terminated</source>
<translation type="unfinished"/>
<translation>terminated</translation>
</message>
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="239"/>
@@ -7818,7 +7855,7 @@ p, li { white-space: pre-wrap; }
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="294"/>
<source>ideal</source>
<translation type="unfinished"/>
<translation>ideal</translation>
</message>
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="297"/>
@@ -7848,7 +7885,7 @@ p, li { white-space: pre-wrap; }
<message>
<location filename="../../src/yuzu/debugger/wait_tree.cpp" line="309"/>
<source>last running ticks = %1</source>
<translation type="unfinished"/>
<translation>last running ticks = %1</translation>
</message>
</context>
<context>
+35 -35
View File
@@ -122,7 +122,7 @@ p, li { white-space: pre-wrap; }
<message>
<location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="330"/>
<source>%1 has been unbanned</source>
<translation type="unfinished"/>
<translation>%1 har haft dess bannlysning upphävd.</translation>
</message>
<message>
<location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="446"/>
@@ -242,22 +242,22 @@ Detta kommer bannlysa både dennes användarnamn på forum samt IP-adress.</tran
<message>
<location filename="../../src/yuzu/compatdb.ui" line="77"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Does the game boot?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"/>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Startar Spelet? &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../../src/yuzu/compatdb.ui" line="100"/>
<source>Yes The game starts to output video or audio</source>
<translation type="unfinished"/>
<translation>Ja Spelet öppnar till utmatning av video eller audio</translation>
</message>
<message>
<location filename="../../src/yuzu/compatdb.ui" line="107"/>
<source>No The game doesn&apos;t get past the &quot;Launching...&quot; screen</source>
<translation type="unfinished"/>
<translation>Nej Spelet öppnar ej förbi &quot;Startar...&quot; skärmen</translation>
</message>
<message>
<location filename="../../src/yuzu/compatdb.ui" line="124"/>
<source>Yes The game gets past the intro/menu and into gameplay</source>
<translation type="unfinished"/>
<translation>Ja Spelet öppnar förbi introt/menyn och in i själva spelandet</translation>
</message>
<message>
<location filename="../../src/yuzu/compatdb.ui" line="131"/>
@@ -1031,7 +1031,7 @@ avgjord kod.&lt;/div&gt;
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="325"/>
<source>Disable Web Applet</source>
<translation type="unfinished"/>
<translation>Avaktivera Webbappletten</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_debug.ui" line="332"/>
@@ -1360,8 +1360,8 @@ avgjord kod.&lt;/div&gt;
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Utökad minnesöversikt (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
@@ -3320,13 +3320,13 @@ UUID: %2</source>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="84"/>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="123"/>
<source>Pull</source>
<translation type="unfinished"/>
<translation>Dra</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="133"/>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="172"/>
<source>Push</source>
<translation type="unfinished"/>
<translation>Knuff</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="206"/>
@@ -3347,7 +3347,7 @@ UUID: %2</source>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="300"/>
<location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="314"/>
<source>Enable</source>
<translation type="unfinished"/>
<translation>Aktivera</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="307"/>
@@ -4493,17 +4493,17 @@ Dra punkter för att ändra position, eller dubbelklicka tabellceller för att r
<message>
<location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="97"/>
<source>Nickname</source>
<translation type="unfinished"/>
<translation>Smeknamn</translation>
</message>
<message>
<location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="111"/>
<source>Password</source>
<translation type="unfinished"/>
<translation>Lösenord</translation>
</message>
<message>
<location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="153"/>
<source>Connect</source>
<translation type="unfinished"/>
<translation>Anslut</translation>
</message>
</context>
<context>
@@ -4511,12 +4511,12 @@ Dra punkter för att ändra position, eller dubbelklicka tabellceller för att r
<message>
<location filename="../../src/yuzu/multiplayer/direct_connect.cpp" line="120"/>
<source>Connecting</source>
<translation type="unfinished"/>
<translation>Ansluter</translation>
</message>
<message>
<location filename="../../src/yuzu/multiplayer/direct_connect.cpp" line="125"/>
<source>Connect</source>
<translation type="unfinished"/>
<translation>Anslut</translation>
</message>
</context>
<context>
@@ -4534,7 +4534,7 @@ Dra punkter för att ändra position, eller dubbelklicka tabellceller för att r
<message>
<location filename="../../src/yuzu/main.cpp" line="432"/>
<source>Broken Vulkan Installation Detected</source>
<translation type="unfinished"/>
<translation>Felaktig Vulkaninstallation Upptäckt</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="433"/>
@@ -4550,7 +4550,7 @@ Dra punkter för att ändra position, eller dubbelklicka tabellceller för att r
<location filename="../../src/yuzu/main.cpp" line="874"/>
<location filename="../../src/yuzu/main.cpp" line="877"/>
<source>Disable Web Applet</source>
<translation type="unfinished"/>
<translation>Avaktivera Webbappletten</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="878"/>
@@ -4591,7 +4591,7 @@ Dra punkter för att ändra position, eller dubbelklicka tabellceller för att r
<message>
<location filename="../../src/yuzu/main.cpp" line="1230"/>
<source>Emulated mouse is enabled</source>
<translation type="unfinished"/>
<translation>Emulerad datormus är aktiverad</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="1231"/>
@@ -5171,7 +5171,7 @@ Please, only use this feature to install updates and DLC.</source>
<location filename="../../src/yuzu/main.cpp" line="3182"/>
<location filename="../../src/yuzu/main.cpp" line="3201"/>
<source>Hardware requirements not met</source>
<translation type="unfinished"/>
<translation> Hårdvarukraven uppfylls ej</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3183"/>
@@ -5202,17 +5202,17 @@ Please, only use this feature to install updates and DLC.</source>
<message>
<location filename="../../src/yuzu/main.cpp" line="3514"/>
<source>TAS Recording</source>
<translation type="unfinished"/>
<translation>TAS Inspelning</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3515"/>
<source>Overwrite file of player 1?</source>
<translation type="unfinished"/>
<translation>Överskriv spelare 1:s fil?</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3541"/>
<source>Invalid config detected</source>
<translation type="unfinished"/>
<translation>Ogiltig konfiguration upptäckt</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3542"/>
@@ -5223,13 +5223,13 @@ Please, only use this feature to install updates and DLC.</source>
<location filename="../../src/yuzu/main.cpp" line="3712"/>
<location filename="../../src/yuzu/main.cpp" line="3740"/>
<source>Amiibo</source>
<translation type="unfinished"/>
<translation>Amiibo</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3712"/>
<location filename="../../src/yuzu/main.cpp" line="3740"/>
<source>The current amiibo has been removed</source>
<translation type="unfinished"/>
<translation>Den aktuella amiibon har avlägsnats</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3717"/>
@@ -5240,7 +5240,7 @@ Please, only use this feature to install updates and DLC.</source>
<location filename="../../src/yuzu/main.cpp" line="3717"/>
<location filename="../../src/yuzu/main.cpp" line="3752"/>
<source>The current game is not looking for amiibos</source>
<translation type="unfinished"/>
<translation>Det aktuella spelet letar ej efter amiibos</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3723"/>
@@ -5260,17 +5260,17 @@ Please, only use this feature to install updates and DLC.</source>
<message>
<location filename="../../src/yuzu/main.cpp" line="3746"/>
<source>The selected file is not a valid amiibo</source>
<translation type="unfinished"/>
<translation>Den valda filen är inte en giltig amiibo</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3749"/>
<source>The selected file is already on use</source>
<translation type="unfinished"/>
<translation>Den valda filen är redan använd</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3755"/>
<source>An unknown error occurred</source>
<translation type="unfinished"/>
<translation>Ett okänt fel har inträffat</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3807"/>
@@ -5285,22 +5285,22 @@ Please, only use this feature to install updates and DLC.</source>
<message>
<location filename="../../src/yuzu/main.cpp" line="3891"/>
<source>TAS state: Running %1/%2</source>
<translation type="unfinished"/>
<translation>TAStillstånd: pågående %1/%2</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3895"/>
<source>TAS state: Recording %1</source>
<translation type="unfinished"/>
<translation>TAStillstånd: spelar in %1</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3897"/>
<source>TAS state: Idle %1/%2</source>
<translation type="unfinished"/>
<translation>TAStillstånd: inaktiv %1/%2</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3901"/>
<source>TAS State: Invalid</source>
<translation type="unfinished"/>
<translation>TAStillstånd: ogiltigt</translation>
</message>
<message>
<location filename="../../src/yuzu/main.cpp" line="3915"/>
@@ -5902,7 +5902,7 @@ Vill du strunta i detta och avsluta ändå?</translation>
<message>
<location filename="../../src/yuzu/multiplayer/host_room.ui" line="118"/>
<source>Password</source>
<translation type="unfinished"/>
<translation>Lösenord</translation>
</message>
<message>
<location filename="../../src/yuzu/multiplayer/host_room.ui" line="125"/>
@@ -6169,7 +6169,7 @@ Debug Message: </source>
<location filename="../../src/yuzu/multiplayer/lobby.ui" line="32"/>
<location filename="../../src/yuzu/multiplayer/lobby.ui" line="39"/>
<source>Nickname</source>
<translation type="unfinished"/>
<translation>Smeknamn</translation>
</message>
<message>
<location filename="../../src/yuzu/multiplayer/lobby.ui" line="59"/>
+2 -2
View File
@@ -1368,8 +1368,8 @@ Bu seçenek belleğe yazma/okuma işlemlerindeki güvenlik kontrolünü kaldıra
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Artırılmış hafıza düzeni (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+2 -2
View File
@@ -1346,8 +1346,8 @@ This would ban both their forum username and their IP address.</source>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation>Розширене компонування пам&apos;яті (6 ГБ DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation type="unfinished"/>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+2 -2
View File
@@ -1380,8 +1380,8 @@ This would ban both their forum username and their IP address.</source>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation> (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation> (8GB DRAM)</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+2 -2
View File
@@ -1382,8 +1382,8 @@ This would ban both their forum username and their IP address.</source>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
<source>Extended memory layout (6GB DRAM)</source>
<translation> (6GB DRAM)</translation>
<source>Extended memory layout (8GB DRAM)</source>
<translation> (8GB DRAM)</translation>
</message>
<message>
<location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+3
View File
@@ -113,6 +113,9 @@ else()
$<$<CXX_COMPILER_ID:Clang>:-Wno-braced-scalar-init>
$<$<CXX_COMPILER_ID:Clang>:-Wno-unused-private-field>
$<$<CXX_COMPILER_ID:Clang>:-Werror=shadow-uncaptured-local>
$<$<CXX_COMPILER_ID:Clang>:-Werror=implicit-fallthrough>
$<$<CXX_COMPILER_ID:Clang>:-Werror=type-limits>
$<$<CXX_COMPILER_ID:AppleClang>:-Wno-braced-scalar-init>
$<$<CXX_COMPILER_ID:AppleClang>:-Wno-unused-private-field>
)
+631
View File
@@ -0,0 +1,631 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_funcs.h"
#include "common/parent_of_member.h"
namespace Common {
// Forward declare implementation class for Node.
namespace impl {
class IntrusiveListImpl;
}
class IntrusiveListNode {
YUZU_NON_COPYABLE(IntrusiveListNode);
private:
friend class impl::IntrusiveListImpl;
IntrusiveListNode* m_prev;
IntrusiveListNode* m_next;
public:
constexpr IntrusiveListNode() : m_prev(this), m_next(this) {}
constexpr bool IsLinked() const {
return m_next != this;
}
private:
constexpr void LinkPrev(IntrusiveListNode* node) {
// We can't link an already linked node.
ASSERT(!node->IsLinked());
this->SplicePrev(node, node);
}
constexpr void SplicePrev(IntrusiveListNode* first, IntrusiveListNode* last) {
// Splice a range into the list.
auto last_prev = last->m_prev;
first->m_prev = m_prev;
last_prev->m_next = this;
m_prev->m_next = first;
m_prev = last_prev;
}
constexpr void LinkNext(IntrusiveListNode* node) {
// We can't link an already linked node.
ASSERT(!node->IsLinked());
return this->SpliceNext(node, node);
}
constexpr void SpliceNext(IntrusiveListNode* first, IntrusiveListNode* last) {
// Splice a range into the list.
auto last_prev = last->m_prev;
first->m_prev = this;
last_prev->m_next = m_next;
m_next->m_prev = last_prev;
m_next = first;
}
constexpr void Unlink() {
this->Unlink(m_next);
}
constexpr void Unlink(IntrusiveListNode* last) {
// Unlink a node from a next node.
auto last_prev = last->m_prev;
m_prev->m_next = last;
last->m_prev = m_prev;
last_prev->m_next = this;
m_prev = last_prev;
}
constexpr IntrusiveListNode* GetPrev() {
return m_prev;
}
constexpr const IntrusiveListNode* GetPrev() const {
return m_prev;
}
constexpr IntrusiveListNode* GetNext() {
return m_next;
}
constexpr const IntrusiveListNode* GetNext() const {
return m_next;
}
};
// DEPRECATED: static_assert(std::is_literal_type<IntrusiveListNode>::value);
namespace impl {
class IntrusiveListImpl {
YUZU_NON_COPYABLE(IntrusiveListImpl);
private:
IntrusiveListNode m_root_node;
public:
template <bool Const>
class Iterator;
using value_type = IntrusiveListNode;
using size_type = size_t;
using difference_type = ptrdiff_t;
using pointer = value_type*;
using const_pointer = const value_type*;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = Iterator<false>;
using const_iterator = Iterator<true>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
template <bool Const>
class Iterator {
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = typename IntrusiveListImpl::value_type;
using difference_type = typename IntrusiveListImpl::difference_type;
using pointer =
std::conditional_t<Const, IntrusiveListImpl::const_pointer, IntrusiveListImpl::pointer>;
using reference = std::conditional_t<Const, IntrusiveListImpl::const_reference,
IntrusiveListImpl::reference>;
private:
pointer m_node;
public:
constexpr explicit Iterator(pointer n) : m_node(n) {}
constexpr bool operator==(const Iterator& rhs) const {
return m_node == rhs.m_node;
}
constexpr pointer operator->() const {
return m_node;
}
constexpr reference operator*() const {
return *m_node;
}
constexpr Iterator& operator++() {
m_node = m_node->m_next;
return *this;
}
constexpr Iterator& operator--() {
m_node = m_node->m_prev;
return *this;
}
constexpr Iterator operator++(int) {
const Iterator it{*this};
++(*this);
return it;
}
constexpr Iterator operator--(int) {
const Iterator it{*this};
--(*this);
return it;
}
constexpr operator Iterator<true>() const {
return Iterator<true>(m_node);
}
constexpr Iterator<false> GetNonConstIterator() const {
return Iterator<false>(const_cast<IntrusiveListImpl::pointer>(m_node));
}
};
public:
constexpr IntrusiveListImpl() : m_root_node() {}
// Iterator accessors.
constexpr iterator begin() {
return iterator(m_root_node.GetNext());
}
constexpr const_iterator begin() const {
return const_iterator(m_root_node.GetNext());
}
constexpr iterator end() {
return iterator(std::addressof(m_root_node));
}
constexpr const_iterator end() const {
return const_iterator(std::addressof(m_root_node));
}
constexpr iterator iterator_to(reference v) {
// Only allow iterator_to for values in lists.
ASSERT(v.IsLinked());
return iterator(std::addressof(v));
}
constexpr const_iterator iterator_to(const_reference v) const {
// Only allow iterator_to for values in lists.
ASSERT(v.IsLinked());
return const_iterator(std::addressof(v));
}
// Content management.
constexpr bool empty() const {
return !m_root_node.IsLinked();
}
constexpr size_type size() const {
return static_cast<size_type>(std::distance(this->begin(), this->end()));
}
constexpr reference back() {
return *m_root_node.GetPrev();
}
constexpr const_reference back() const {
return *m_root_node.GetPrev();
}
constexpr reference front() {
return *m_root_node.GetNext();
}
constexpr const_reference front() const {
return *m_root_node.GetNext();
}
constexpr void push_back(reference node) {
m_root_node.LinkPrev(std::addressof(node));
}
constexpr void push_front(reference node) {
m_root_node.LinkNext(std::addressof(node));
}
constexpr void pop_back() {
m_root_node.GetPrev()->Unlink();
}
constexpr void pop_front() {
m_root_node.GetNext()->Unlink();
}
constexpr iterator insert(const_iterator pos, reference node) {
pos.GetNonConstIterator()->LinkPrev(std::addressof(node));
return iterator(std::addressof(node));
}
constexpr void splice(const_iterator pos, IntrusiveListImpl& o) {
splice_impl(pos, o.begin(), o.end());
}
constexpr void splice(const_iterator pos, IntrusiveListImpl& o, const_iterator first) {
const_iterator last(first);
std::advance(last, 1);
splice_impl(pos, first, last);
}
constexpr void splice(const_iterator pos, IntrusiveListImpl& o, const_iterator first,
const_iterator last) {
splice_impl(pos, first, last);
}
constexpr iterator erase(const_iterator pos) {
if (pos == this->end()) {
return this->end();
}
iterator it(pos.GetNonConstIterator());
(it++)->Unlink();
return it;
}
constexpr void clear() {
while (!this->empty()) {
this->pop_front();
}
}
private:
constexpr void splice_impl(const_iterator _pos, const_iterator _first, const_iterator _last) {
if (_first == _last) {
return;
}
iterator pos(_pos.GetNonConstIterator());
iterator first(_first.GetNonConstIterator());
iterator last(_last.GetNonConstIterator());
first->Unlink(std::addressof(*last));
pos->SplicePrev(std::addressof(*first), std::addressof(*first));
}
};
} // namespace impl
template <class T, class Traits>
class IntrusiveList {
YUZU_NON_COPYABLE(IntrusiveList);
private:
impl::IntrusiveListImpl m_impl;
public:
template <bool Const>
class Iterator;
using value_type = T;
using size_type = size_t;
using difference_type = ptrdiff_t;
using pointer = value_type*;
using const_pointer = const value_type*;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = Iterator<false>;
using const_iterator = Iterator<true>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
template <bool Const>
class Iterator {
public:
friend class Common::IntrusiveList<T, Traits>;
using ImplIterator =
std::conditional_t<Const, Common::impl::IntrusiveListImpl::const_iterator,
Common::impl::IntrusiveListImpl::iterator>;
using iterator_category = std::bidirectional_iterator_tag;
using value_type = typename IntrusiveList::value_type;
using difference_type = typename IntrusiveList::difference_type;
using pointer =
std::conditional_t<Const, IntrusiveList::const_pointer, IntrusiveList::pointer>;
using reference =
std::conditional_t<Const, IntrusiveList::const_reference, IntrusiveList::reference>;
private:
ImplIterator m_iterator;
private:
constexpr explicit Iterator(ImplIterator it) : m_iterator(it) {}
constexpr ImplIterator GetImplIterator() const {
return m_iterator;
}
public:
constexpr bool operator==(const Iterator& rhs) const {
return m_iterator == rhs.m_iterator;
}
constexpr pointer operator->() const {
return std::addressof(Traits::GetParent(*m_iterator));
}
constexpr reference operator*() const {
return Traits::GetParent(*m_iterator);
}
constexpr Iterator& operator++() {
++m_iterator;
return *this;
}
constexpr Iterator& operator--() {
--m_iterator;
return *this;
}
constexpr Iterator operator++(int) {
const Iterator it{*this};
++m_iterator;
return it;
}
constexpr Iterator operator--(int) {
const Iterator it{*this};
--m_iterator;
return it;
}
constexpr operator Iterator<true>() const {
return Iterator<true>(m_iterator);
}
};
private:
static constexpr IntrusiveListNode& GetNode(reference ref) {
return Traits::GetNode(ref);
}
static constexpr IntrusiveListNode const& GetNode(const_reference ref) {
return Traits::GetNode(ref);
}
static constexpr reference GetParent(IntrusiveListNode& node) {
return Traits::GetParent(node);
}
static constexpr const_reference GetParent(IntrusiveListNode const& node) {
return Traits::GetParent(node);
}
public:
constexpr IntrusiveList() : m_impl() {}
// Iterator accessors.
constexpr iterator begin() {
return iterator(m_impl.begin());
}
constexpr const_iterator begin() const {
return const_iterator(m_impl.begin());
}
constexpr iterator end() {
return iterator(m_impl.end());
}
constexpr const_iterator end() const {
return const_iterator(m_impl.end());
}
constexpr const_iterator cbegin() const {
return this->begin();
}
constexpr const_iterator cend() const {
return this->end();
}
constexpr reverse_iterator rbegin() {
return reverse_iterator(this->end());
}
constexpr const_reverse_iterator rbegin() const {
return const_reverse_iterator(this->end());
}
constexpr reverse_iterator rend() {
return reverse_iterator(this->begin());
}
constexpr const_reverse_iterator rend() const {
return const_reverse_iterator(this->begin());
}
constexpr const_reverse_iterator crbegin() const {
return this->rbegin();
}
constexpr const_reverse_iterator crend() const {
return this->rend();
}
constexpr iterator iterator_to(reference v) {
return iterator(m_impl.iterator_to(GetNode(v)));
}
constexpr const_iterator iterator_to(const_reference v) const {
return const_iterator(m_impl.iterator_to(GetNode(v)));
}
// Content management.
constexpr bool empty() const {
return m_impl.empty();
}
constexpr size_type size() const {
return m_impl.size();
}
constexpr reference back() {
return GetParent(m_impl.back());
}
constexpr const_reference back() const {
return GetParent(m_impl.back());
}
constexpr reference front() {
return GetParent(m_impl.front());
}
constexpr const_reference front() const {
return GetParent(m_impl.front());
}
constexpr void push_back(reference ref) {
m_impl.push_back(GetNode(ref));
}
constexpr void push_front(reference ref) {
m_impl.push_front(GetNode(ref));
}
constexpr void pop_back() {
m_impl.pop_back();
}
constexpr void pop_front() {
m_impl.pop_front();
}
constexpr iterator insert(const_iterator pos, reference ref) {
return iterator(m_impl.insert(pos.GetImplIterator(), GetNode(ref)));
}
constexpr void splice(const_iterator pos, IntrusiveList& o) {
m_impl.splice(pos.GetImplIterator(), o.m_impl);
}
constexpr void splice(const_iterator pos, IntrusiveList& o, const_iterator first) {
m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator());
}
constexpr void splice(const_iterator pos, IntrusiveList& o, const_iterator first,
const_iterator last) {
m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator(),
last.GetImplIterator());
}
constexpr iterator erase(const_iterator pos) {
return iterator(m_impl.erase(pos.GetImplIterator()));
}
constexpr void clear() {
m_impl.clear();
}
};
template <auto T, class Derived = Common::impl::GetParentType<T>>
class IntrusiveListMemberTraits;
template <class Parent, IntrusiveListNode Parent::*Member, class Derived>
class IntrusiveListMemberTraits<Member, Derived> {
public:
using ListType = IntrusiveList<Derived, IntrusiveListMemberTraits>;
private:
friend class IntrusiveList<Derived, IntrusiveListMemberTraits>;
static constexpr IntrusiveListNode& GetNode(Derived& parent) {
return parent.*Member;
}
static constexpr IntrusiveListNode const& GetNode(Derived const& parent) {
return parent.*Member;
}
static Derived& GetParent(IntrusiveListNode& node) {
return Common::GetParentReference<Member, Derived>(std::addressof(node));
}
static Derived const& GetParent(IntrusiveListNode const& node) {
return Common::GetParentReference<Member, Derived>(std::addressof(node));
}
};
template <auto T, class Derived = Common::impl::GetParentType<T>>
class IntrusiveListMemberTraitsByNonConstexprOffsetOf;
template <class Parent, IntrusiveListNode Parent::*Member, class Derived>
class IntrusiveListMemberTraitsByNonConstexprOffsetOf<Member, Derived> {
public:
using ListType = IntrusiveList<Derived, IntrusiveListMemberTraitsByNonConstexprOffsetOf>;
private:
friend class IntrusiveList<Derived, IntrusiveListMemberTraitsByNonConstexprOffsetOf>;
static constexpr IntrusiveListNode& GetNode(Derived& parent) {
return parent.*Member;
}
static constexpr IntrusiveListNode const& GetNode(Derived const& parent) {
return parent.*Member;
}
static Derived& GetParent(IntrusiveListNode& node) {
return *reinterpret_cast<Derived*>(reinterpret_cast<char*>(std::addressof(node)) -
GetOffset());
}
static Derived const& GetParent(IntrusiveListNode const& node) {
return *reinterpret_cast<const Derived*>(
reinterpret_cast<const char*>(std::addressof(node)) - GetOffset());
}
static uintptr_t GetOffset() {
return reinterpret_cast<uintptr_t>(std::addressof(reinterpret_cast<Derived*>(0)->*Member));
}
};
template <class Derived>
class IntrusiveListBaseNode : public IntrusiveListNode {};
template <class Derived>
class IntrusiveListBaseTraits {
public:
using ListType = IntrusiveList<Derived, IntrusiveListBaseTraits>;
private:
friend class IntrusiveList<Derived, IntrusiveListBaseTraits>;
static constexpr IntrusiveListNode& GetNode(Derived& parent) {
return static_cast<IntrusiveListNode&>(
static_cast<IntrusiveListBaseNode<Derived>&>(parent));
}
static constexpr IntrusiveListNode const& GetNode(Derived const& parent) {
return static_cast<const IntrusiveListNode&>(
static_cast<const IntrusiveListBaseNode<Derived>&>(parent));
}
static constexpr Derived& GetParent(IntrusiveListNode& node) {
return static_cast<Derived&>(static_cast<IntrusiveListBaseNode<Derived>&>(node));
}
static constexpr Derived const& GetParent(IntrusiveListNode const& node) {
return static_cast<const Derived&>(
static_cast<const IntrusiveListBaseNode<Derived>&>(node));
}
};
} // namespace Common
+3 -2
View File
@@ -45,6 +45,7 @@ void LogSettings() {
log_setting("System_LanguageIndex", values.language_index.GetValue());
log_setting("System_RegionIndex", values.region_index.GetValue());
log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue());
log_setting("System_UnsafeMemoryLayout", values.use_unsafe_extended_memory_layout.GetValue());
log_setting("Core_UseMultiCore", values.use_multi_core.GetValue());
log_setting("CPU_Accuracy", values.cpu_accuracy.GetValue());
log_setting("Renderer_UseResolutionScaling", values.resolution_setup.GetValue());
@@ -191,7 +192,7 @@ void RestoreGlobalState(bool is_powered_on) {
// Core
values.use_multi_core.SetGlobal(true);
values.use_extended_memory_layout.SetGlobal(true);
values.use_unsafe_extended_memory_layout.SetGlobal(true);
// CPU
values.cpu_accuracy.SetGlobal(true);
@@ -205,6 +206,7 @@ void RestoreGlobalState(bool is_powered_on) {
// Renderer
values.fsr_sharpening_slider.SetGlobal(true);
values.renderer_backend.SetGlobal(true);
values.async_presentation.SetGlobal(true);
values.renderer_force_max_clock.SetGlobal(true);
values.vulkan_device.SetGlobal(true);
values.fullscreen_mode.SetGlobal(true);
@@ -225,7 +227,6 @@ void RestoreGlobalState(bool is_powered_on) {
values.shader_backend.SetGlobal(true);
values.use_asynchronous_shaders.SetGlobal(true);
values.use_fast_gpu_time.SetGlobal(true);
values.use_pessimistic_flushes.SetGlobal(true);
values.use_vulkan_driver_pipeline_cache.SetGlobal(true);
values.bg_red.SetGlobal(true);
values.bg_green.SetGlobal(true);
+3 -2
View File
@@ -388,7 +388,8 @@ struct Values {
// Core
SwitchableSetting<bool> use_multi_core{true, "use_multi_core"};
SwitchableSetting<bool> use_extended_memory_layout{false, "use_extended_memory_layout"};
SwitchableSetting<bool> use_unsafe_extended_memory_layout{false,
"use_unsafe_extended_memory_layout"};
// Cpu
SwitchableSetting<CPUAccuracy, true> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto,
@@ -422,6 +423,7 @@ struct Values {
// Renderer
SwitchableSetting<RendererBackend, true> renderer_backend{
RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Null, "backend"};
SwitchableSetting<bool> async_presentation{false, "async_presentation"};
SwitchableSetting<bool> renderer_force_max_clock{false, "force_max_clock"};
Setting<bool> renderer_debug{false, "debug"};
Setting<bool> renderer_shader_feedback{false, "shader_feedback"};
@@ -459,7 +461,6 @@ struct Values {
ShaderBackend::SPIRV, "shader_backend"};
SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"};
SwitchableSetting<bool> use_pessimistic_flushes{false, "use_pessimistic_flushes"};
SwitchableSetting<bool> use_vulkan_driver_pipeline_cache{true,
"use_vulkan_driver_pipeline_cache"};
+2 -2
View File
@@ -570,10 +570,10 @@ add_library(core STATIC
hle/service/nfp/nfp.h
hle/service/nfp/nfp_device.cpp
hle/service/nfp/nfp_device.h
hle/service/nfp/nfp_interface.cpp
hle/service/nfp/nfp_interface.h
hle/service/nfp/nfp_result.h
hle/service/nfp/nfp_types.h
hle/service/nfp/nfp_user.cpp
hle/service/nfp/nfp_user.h
hle/service/ngct/ngct.cpp
hle/service/ngct/ngct.h
hle/service/nifm/nifm.cpp
+4 -3
View File
@@ -137,7 +137,7 @@ struct System::Impl {
device_memory = std::make_unique<Core::DeviceMemory>();
is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout = Settings::values.use_extended_memory_layout.GetValue();
extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue();
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
@@ -169,7 +169,7 @@ struct System::Impl {
void ReinitializeIfNecessary(System& system) {
const bool must_reinitialize =
is_multicore != Settings::values.use_multi_core.GetValue() ||
extended_memory_layout != Settings::values.use_extended_memory_layout.GetValue();
extended_memory_layout != Settings::values.use_unsafe_extended_memory_layout.GetValue();
if (!must_reinitialize) {
return;
@@ -178,7 +178,7 @@ struct System::Impl {
LOG_DEBUG(Kernel, "Re-initializing");
is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout = Settings::values.use_extended_memory_layout.GetValue();
extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue();
Initialize(system);
}
@@ -293,6 +293,7 @@ struct System::Impl {
ASSERT(Kernel::KProcess::Initialize(main_process, system, "main",
Kernel::KProcess::ProcessType::Userland, resource_limit)
.IsSuccess());
Kernel::KProcess::Register(system.Kernel(), main_process);
kernel.MakeApplicationProcess(main_process);
const auto [load_result, load_parameters] = app_loader->Load(*main_process, system);
if (load_result != Loader::ResultStatus::Success) {
+4
View File
@@ -280,6 +280,10 @@ void EmulatedController::LoadVirtualGamepadParams() {
virtual_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1);
virtual_stick_params[Settings::NativeAnalog::RStick].Set("axis_x", 2);
virtual_stick_params[Settings::NativeAnalog::RStick].Set("axis_y", 3);
virtual_stick_params[Settings::NativeAnalog::LStick].Set("deadzone", 0.0f);
virtual_stick_params[Settings::NativeAnalog::LStick].Set("range", 1.0f);
virtual_stick_params[Settings::NativeAnalog::RStick].Set("deadzone", 0.0f);
virtual_stick_params[Settings::NativeAnalog::RStick].Set("range", 1.0f);
}
void EmulatedController::ReloadInput() {
@@ -35,12 +35,13 @@ namespace {
using namespace Common::Literals;
u32 GetMemorySizeForInit() {
return Settings::values.use_extended_memory_layout ? Smc::MemorySize_8GB : Smc::MemorySize_4GB;
return Settings::values.use_unsafe_extended_memory_layout ? Smc::MemorySize_8GB
: Smc::MemorySize_4GB;
}
Smc::MemoryArrangement GetMemoryArrangeForInit() {
return Settings::values.use_extended_memory_layout ? Smc::MemoryArrangement_8GB
: Smc::MemoryArrangement_4GB;
return Settings::values.use_unsafe_extended_memory_layout ? Smc::MemoryArrangement_8GB
: Smc::MemoryArrangement_4GB;
}
} // namespace
+2 -2
View File
@@ -182,8 +182,8 @@ public:
explicit KAutoObjectWithList(KernelCore& kernel) : KAutoObject(kernel) {}
static int Compare(const KAutoObjectWithList& lhs, const KAutoObjectWithList& rhs) {
const u64 lid = lhs.GetId();
const u64 rid = rhs.GetId();
const uintptr_t lid = reinterpret_cast<uintptr_t>(std::addressof(lhs));
const uintptr_t rid = reinterpret_cast<uintptr_t>(std::addressof(rhs));
if (lid < rid) {
return -1;
+3 -2
View File
@@ -5,14 +5,15 @@
#include <array>
#include <boost/intrusive/list.hpp>
#include "common/intrusive_list.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/kernel/svc_types.h"
namespace Kernel {
class KEventInfo : public KSlabAllocated<KEventInfo>, public boost::intrusive::list_base_hook<> {
class KEventInfo : public KSlabAllocated<KEventInfo>,
public Common::IntrusiveListBaseNode<KEventInfo> {
public:
struct InfoCreateThread {
u32 thread_id{};
+5 -3
View File
@@ -5,7 +5,8 @@
#include <array>
#include <memory>
#include <boost/intrusive/list.hpp>
#include "common/intrusive_list.h"
#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/slab_helpers.h"
@@ -15,13 +16,14 @@ namespace Kernel {
class KObjectNameGlobalData;
class KObjectName : public KSlabAllocated<KObjectName>, public boost::intrusive::list_base_hook<> {
class KObjectName : public KSlabAllocated<KObjectName>,
public Common::IntrusiveListBaseNode<KObjectName> {
public:
explicit KObjectName(KernelCore&) {}
virtual ~KObjectName() = default;
static constexpr size_t NameLengthMax = 12;
using List = boost::intrusive::list<KObjectName>;
using List = Common::IntrusiveListBaseTraits<KObjectName>::ListType;
static Result NewFromName(KernelCore& kernel, KAutoObject* obj, const char* name);
static Result Delete(KernelCore& kernel, KAutoObject* obj, const char* name);
+2 -2
View File
@@ -7,7 +7,7 @@
#include <string>
#include <utility>
#include <boost/intrusive/list.hpp>
#include "common/intrusive_list.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/k_synchronization_object.h"
@@ -42,7 +42,7 @@ public:
bool IsSignaled() const override;
private:
using SessionList = boost::intrusive::list<KServerSession>;
using SessionList = Common::IntrusiveListBaseTraits<KServerSession>::ListType;
void CleanupSessions();
+4 -3
View File
@@ -8,7 +8,7 @@
#include <string>
#include <utility>
#include <boost/intrusive/list.hpp>
#include "common/intrusive_list.h"
#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/k_session_request.h"
@@ -27,7 +27,7 @@ class KSession;
class KThread;
class KServerSession final : public KSynchronizationObject,
public boost::intrusive::list_base_hook<> {
public Common::IntrusiveListBaseNode<KServerSession> {
KERNEL_AUTOOBJECT_TRAITS(KServerSession, KSynchronizationObject);
friend class ServiceThread;
@@ -67,7 +67,8 @@ private:
KSession* m_parent{};
/// List of threads which are pending a reply.
boost::intrusive::list<KSessionRequest> m_request_list{};
using RequestList = Common::IntrusiveListBaseTraits<KSessionRequest>::ListType;
RequestList m_request_list{};
KSessionRequest* m_current_request{};
KLightLock m_lock;
+3 -1
View File
@@ -5,6 +5,8 @@
#include <array>
#include "common/intrusive_list.h"
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_memory_block.h"
@@ -16,7 +18,7 @@ namespace Kernel {
class KSessionRequest final : public KSlabAllocated<KSessionRequest>,
public KAutoObject,
public boost::intrusive::list_base_hook<> {
public Common::IntrusiveListBaseNode<KSessionRequest> {
KERNEL_AUTOOBJECT_TRAITS(KSessionRequest, KAutoObject);
public:
+2 -2
View File
@@ -3,7 +3,7 @@
#pragma once
#include <boost/intrusive/list.hpp>
#include "common/intrusive_list.h"
#include "core/hle/kernel/slab_helpers.h"
@@ -12,7 +12,7 @@ namespace Kernel {
class KSharedMemory;
class KSharedMemoryInfo final : public KSlabAllocated<KSharedMemoryInfo>,
public boost::intrusive::list_base_hook<> {
public Common::IntrusiveListBaseNode<KSharedMemoryInfo> {
public:
explicit KSharedMemoryInfo(KernelCore&) {}
+7 -6
View File
@@ -12,7 +12,7 @@
#include <utility>
#include <vector>
#include <boost/intrusive/list.hpp>
#include "common/intrusive_list.h"
#include "common/intrusive_red_black_tree.h"
#include "common/spin_lock.h"
@@ -119,7 +119,7 @@ s32 GetCurrentCoreId(KernelCore& kernel);
Core::Memory::Memory& GetCurrentMemory(KernelCore& kernel);
class KThread final : public KAutoObjectWithSlabHeapAndContainer<KThread, KWorkerTask>,
public boost::intrusive::list_base_hook<>,
public Common::IntrusiveListBaseNode<KThread>,
public KTimerTask {
KERNEL_AUTOOBJECT_TRAITS(KThread, KSynchronizationObject);
@@ -138,7 +138,7 @@ public:
public:
using ThreadContext32 = Core::ARM_Interface::ThreadContext32;
using ThreadContext64 = Core::ARM_Interface::ThreadContext64;
using WaiterList = boost::intrusive::list<KThread>;
using WaiterList = Common::IntrusiveListBaseTraits<KThread>::ListType;
/**
* Gets the thread's current priority
@@ -750,8 +750,9 @@ private:
ConditionVariableThreadTreeTraits::TreeType<LockWithPriorityInheritanceComparator>;
public:
class LockWithPriorityInheritanceInfo : public KSlabAllocated<LockWithPriorityInheritanceInfo>,
public boost::intrusive::list_base_hook<> {
class LockWithPriorityInheritanceInfo
: public KSlabAllocated<LockWithPriorityInheritanceInfo>,
public Common::IntrusiveListBaseNode<LockWithPriorityInheritanceInfo> {
public:
explicit LockWithPriorityInheritanceInfo(KernelCore&) {}
@@ -839,7 +840,7 @@ public:
private:
using LockWithPriorityInheritanceInfoList =
boost::intrusive::list<LockWithPriorityInheritanceInfo>;
Common::IntrusiveListBaseTraits<LockWithPriorityInheritanceInfo>::ListType;
ConditionVariableThreadTree* m_condvar_tree{};
u64 m_condvar_key{};
+28 -3
View File
@@ -95,7 +95,7 @@ struct KernelCore::Impl {
pt_heap_region.GetSize());
}
InitializeHackSharedMemory();
InitializeHackSharedMemory(kernel);
RegisterHostThread(nullptr);
}
@@ -216,10 +216,12 @@ struct KernelCore::Impl {
auto* main_thread{Kernel::KThread::Create(system.Kernel())};
main_thread->SetCurrentCore(core);
ASSERT(Kernel::KThread::InitializeMainThread(system, main_thread, core).IsSuccess());
KThread::Register(system.Kernel(), main_thread);
auto* idle_thread{Kernel::KThread::Create(system.Kernel())};
idle_thread->SetCurrentCore(core);
ASSERT(Kernel::KThread::InitializeIdleThread(system, idle_thread, core).IsSuccess());
KThread::Register(system.Kernel(), idle_thread);
schedulers[i]->Initialize(main_thread, idle_thread, core);
}
@@ -230,6 +232,7 @@ struct KernelCore::Impl {
const Core::Timing::CoreTiming& core_timing) {
system_resource_limit = KResourceLimit::Create(system.Kernel());
system_resource_limit->Initialize(&core_timing);
KResourceLimit::Register(kernel, system_resource_limit);
const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()};
const auto total_size{sizes.first};
@@ -355,6 +358,7 @@ struct KernelCore::Impl {
ASSERT(KThread::InitializeHighPriorityThread(system, shutdown_threads[core_id], {}, {},
core_id)
.IsSuccess());
KThread::Register(system.Kernel(), shutdown_threads[core_id]);
}
}
@@ -729,7 +733,7 @@ struct KernelCore::Impl {
memory_manager->Initialize(management_region.GetAddress(), management_region.GetSize());
}
void InitializeHackSharedMemory() {
void InitializeHackSharedMemory(KernelCore& kernel) {
// Setup memory regions for emulated processes
// TODO(bunnei): These should not be hardcoded regions initialized within the kernel
constexpr std::size_t hid_size{0x40000};
@@ -746,14 +750,23 @@ struct KernelCore::Impl {
hid_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, hid_size);
KSharedMemory::Register(kernel, hid_shared_mem);
font_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, font_size);
KSharedMemory::Register(kernel, font_shared_mem);
irs_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, irs_size);
KSharedMemory::Register(kernel, irs_shared_mem);
time_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, time_size);
KSharedMemory::Register(kernel, time_shared_mem);
hidbus_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, hidbus_size);
KSharedMemory::Register(kernel, hidbus_shared_mem);
}
std::mutex registered_objects_lock;
@@ -1072,12 +1085,15 @@ static std::jthread RunHostThreadFunc(KernelCore& kernel, KProcess* process,
// Commit the thread reservation.
thread_reservation.Commit();
// Register the thread.
KThread::Register(kernel, thread);
return std::jthread(
[&kernel, thread, thread_name{std::move(thread_name)}, func{std::move(func)}] {
// Set the thread name.
Common::SetCurrentThreadName(thread_name.c_str());
// Register the thread.
// Set the thread as current.
kernel.RegisterHostThread(thread);
// Run the callback.
@@ -1099,6 +1115,9 @@ std::jthread KernelCore::RunOnHostCoreProcess(std::string&& process_name,
// Ensure that we don't hold onto any extra references.
SCOPE_EXIT({ process->Close(); });
// Register the new process.
KProcess::Register(*this, process);
// Run the host thread.
return RunHostThreadFunc(*this, process, std::move(process_name), std::move(func));
}
@@ -1124,6 +1143,9 @@ void KernelCore::RunOnGuestCoreProcess(std::string&& process_name, std::function
// Ensure that we don't hold onto any extra references.
SCOPE_EXIT({ process->Close(); });
// Register the new process.
KProcess::Register(*this, process);
// Reserve a new thread from the process resource limit.
KScopedResourceReservation thread_reservation(process, LimitableResource::ThreadCountMax);
ASSERT(thread_reservation.Succeeded());
@@ -1136,6 +1158,9 @@ void KernelCore::RunOnGuestCoreProcess(std::string&& process_name, std::function
// Commit the thread reservation.
thread_reservation.Commit();
// Register the new thread.
KThread::Register(*this, thread);
// Begin running the thread.
ASSERT(R_SUCCEEDED(thread->Run()));
}
+1 -1
View File
@@ -1807,7 +1807,7 @@ void IApplicationFunctions::GetFriendInvitationStorageChannelEvent(HLERequestCon
}
void IApplicationFunctions::TryPopFromFriendInvitationStorageChannel(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
LOG_DEBUG(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(AM::ResultNoDataInChannel);
+1
View File
@@ -156,6 +156,7 @@ public:
auto* session = Kernel::KSession::Create(kernel);
session->Initialize(nullptr, 0);
Kernel::KSession::Register(kernel, session);
auto next_manager = std::make_shared<Service::SessionRequestManager>(
kernel, manager->GetServerManager());
+3
View File
@@ -25,6 +25,9 @@ ServiceContext::ServiceContext(Core::System& system_, std::string name_)
Kernel::KProcess::ProcessType::KernelInternal,
kernel.GetSystemResourceLimit())
.IsSuccess());
// Register the process.
Kernel::KProcess::Register(kernel, process);
process_created = true;
}
+3
View File
@@ -12,6 +12,9 @@ Mutex::Mutex(Core::System& system) : m_system(system) {
m_event = Kernel::KEvent::Create(system.Kernel());
m_event->Initialize(nullptr);
// Register the event.
Kernel::KEvent::Register(system.Kernel(), m_event);
ASSERT(R_SUCCEEDED(m_event->Signal()));
}
+186 -1
View File
@@ -4,11 +4,138 @@
#include "common/logging/log.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_user.h"
#include "core/hle/service/nfp/nfp_interface.h"
#include "core/hle/service/server_manager.h"
namespace Service::NFP {
class IUser final : public Interface {
public:
explicit IUser(Core::System& system_) : Interface(system_, "NFP:IUser") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IUser::Initialize, "Initialize"},
{1, &IUser::Finalize, "Finalize"},
{2, &IUser::ListDevices, "ListDevices"},
{3, &IUser::StartDetection, "StartDetection"},
{4, &IUser::StopDetection, "StopDetection"},
{5, &IUser::Mount, "Mount"},
{6, &IUser::Unmount, "Unmount"},
{7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
{8, &IUser::GetApplicationArea, "GetApplicationArea"},
{9, &IUser::SetApplicationArea, "SetApplicationArea"},
{10, &IUser::Flush, "Flush"},
{11, &IUser::Restore, "Restore"},
{12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
{13, &IUser::GetTagInfo, "GetTagInfo"},
{14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
{15, &IUser::GetCommonInfo, "GetCommonInfo"},
{16, &IUser::GetModelInfo, "GetModelInfo"},
{17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
{18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
{19, &IUser::GetState, "GetState"},
{20, &IUser::GetDeviceState, "GetDeviceState"},
{21, &IUser::GetNpadId, "GetNpadId"},
{22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
{23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class ISystem final : public Interface {
public:
explicit ISystem(Core::System& system_) : Interface(system_, "NFP:ISystem") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ISystem::InitializeSystem, "InitializeSystem"},
{1, &ISystem::FinalizeSystem, "FinalizeSystem"},
{2, &ISystem::ListDevices, "ListDevices"},
{3, &ISystem::StartDetection, "StartDetection"},
{4, &ISystem::StopDetection, "StopDetection"},
{5, &ISystem::Mount, "Mount"},
{6, &ISystem::Unmount, "Unmount"},
{10, &ISystem::Flush, "Flush"},
{11, &ISystem::Restore, "Restore"},
{12, &ISystem::CreateApplicationArea, "CreateApplicationArea"},
{13, &ISystem::GetTagInfo, "GetTagInfo"},
{14, &ISystem::GetRegisterInfo, "GetRegisterInfo"},
{15, &ISystem::GetCommonInfo, "GetCommonInfo"},
{16, &ISystem::GetModelInfo, "GetModelInfo"},
{17, &ISystem::AttachActivateEvent, "AttachActivateEvent"},
{18, &ISystem::AttachDeactivateEvent, "AttachDeactivateEvent"},
{19, &ISystem::GetState, "GetState"},
{20, &ISystem::GetDeviceState, "GetDeviceState"},
{21, &ISystem::GetNpadId, "GetNpadId"},
{23, &ISystem::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{100, &ISystem::Format, "Format"},
{101, &ISystem::GetAdminInfo, "GetAdminInfo"},
{102, &ISystem::GetRegisterInfoPrivate, "GetRegisterInfoPrivate"},
{103, &ISystem::SetRegisterInfoPrivate, "SetRegisterInfoPrivate"},
{104, &ISystem::DeleteRegisterInfo, "DeleteRegisterInfo"},
{105, &ISystem::DeleteApplicationArea, "DeleteApplicationArea"},
{106, &ISystem::ExistsApplicationArea, "ExistsApplicationArea"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IDebug final : public Interface {
public:
explicit IDebug(Core::System& system_) : Interface(system_, "NFP:IDebug") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDebug::InitializeDebug, "InitializeDebug"},
{1, &IDebug::FinalizeDebug, "FinalizeDebug"},
{2, &IDebug::ListDevices, "ListDevices"},
{3, &IDebug::StartDetection, "StartDetection"},
{4, &IDebug::StopDetection, "StopDetection"},
{5, &IDebug::Mount, "Mount"},
{6, &IDebug::Unmount, "Unmount"},
{7, &IDebug::OpenApplicationArea, "OpenApplicationArea"},
{8, &IDebug::GetApplicationArea, "GetApplicationArea"},
{9, &IDebug::SetApplicationArea, "SetApplicationArea"},
{10, &IDebug::Flush, "Flush"},
{11, &IDebug::Restore, "Restore"},
{12, &IDebug::CreateApplicationArea, "CreateApplicationArea"},
{13, &IDebug::GetTagInfo, "GetTagInfo"},
{14, &IDebug::GetRegisterInfo, "GetRegisterInfo"},
{15, &IDebug::GetCommonInfo, "GetCommonInfo"},
{16, &IDebug::GetModelInfo, "GetModelInfo"},
{17, &IDebug::AttachActivateEvent, "AttachActivateEvent"},
{18, &IDebug::AttachDeactivateEvent, "AttachDeactivateEvent"},
{19, &IDebug::GetState, "GetState"},
{20, &IDebug::GetDeviceState, "GetDeviceState"},
{21, &IDebug::GetNpadId, "GetNpadId"},
{22, &IDebug::GetApplicationAreaSize, "GetApplicationAreaSize"},
{23, &IDebug::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{24, &IDebug::RecreateApplicationArea, "RecreateApplicationArea"},
{100, &IDebug::Format, "Format"},
{101, &IDebug::GetAdminInfo, "GetAdminInfo"},
{102, &IDebug::GetRegisterInfoPrivate, "GetRegisterInfoPrivate"},
{103, &IDebug::SetRegisterInfoPrivate, "SetRegisterInfoPrivate"},
{104, &IDebug::DeleteRegisterInfo, "DeleteRegisterInfo"},
{105, &IDebug::DeleteApplicationArea, "DeleteApplicationArea"},
{106, &IDebug::ExistsApplicationArea, "ExistsApplicationArea"},
{200, &IDebug::GetAll, "GetAll"},
{201, &IDebug::SetAll, "SetAll"},
{202, &IDebug::FlushDebug, "FlushDebug"},
{203, &IDebug::BreakTag, "BreakTag"},
{204, nullptr, "ReadBackupData"},
{205, nullptr, "WriteBackupData"},
{206, nullptr, "WriteNtf"},
};
// clang-format on
RegisterHandlers(functions);
}
};
class IUserManager final : public ServiceFramework<IUserManager> {
public:
explicit IUserManager(Core::System& system_) : ServiceFramework{system_, "nfp:user"} {
@@ -37,10 +164,68 @@ private:
std::shared_ptr<IUser> user_interface;
};
class ISystemManager final : public ServiceFramework<ISystemManager> {
public:
explicit ISystemManager(Core::System& system_) : ServiceFramework{system_, "nfp:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ISystemManager::CreateSystemInterface, "CreateSystemInterface"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void CreateSystemInterface(HLERequestContext& ctx) {
LOG_DEBUG(Service_NFP, "called");
if (system_interface == nullptr) {
system_interface = std::make_shared<ISystem>(system);
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<ISystem>(system_interface);
}
std::shared_ptr<ISystem> system_interface;
};
class IDebugManager final : public ServiceFramework<IDebugManager> {
public:
explicit IDebugManager(Core::System& system_) : ServiceFramework{system_, "nfp:dbg"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDebugManager::CreateDebugInterface, "CreateDebugInterface"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void CreateDebugInterface(HLERequestContext& ctx) {
LOG_DEBUG(Service_NFP, "called");
if (system_interface == nullptr) {
system_interface = std::make_shared<IDebug>(system);
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IDebug>(system_interface);
}
std::shared_ptr<IDebug> system_interface;
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService("nfp:user", std::make_shared<IUserManager>(system));
server_manager->RegisterNamedService("nfp:sys", std::make_shared<ISystemManager>(system));
server_manager->RegisterNamedService("nfp:dbg", std::make_shared<IDebugManager>(system));
ServerManager::RunServer(std::move(server_manager));
}
+221 -3
View File
@@ -29,7 +29,6 @@
#include "core/hle/service/nfp/amiibo_crypto.h"
#include "core/hle/service/nfp/nfp_device.h"
#include "core/hle/service/nfp/nfp_result.h"
#include "core/hle/service/nfp/nfp_user.h"
#include "core/hle/service/time/time_manager.h"
#include "core/hle/service/time/time_zone_content_manager.h"
#include "core/hle/service/time/time_zone_types.h"
@@ -241,6 +240,42 @@ Result NfpDevice::Flush() {
tag_data.write_counter++;
FlushWithBreak(BreakType::Normal);
is_data_moddified = false;
return ResultSuccess;
}
Result NfpDevice::FlushDebug() {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return TagRemoved;
}
return WrongDeviceState;
}
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
return WrongDeviceState;
}
tag_data.write_counter++;
FlushWithBreak(BreakType::Normal);
is_data_moddified = false;
return ResultSuccess;
}
Result NfpDevice::FlushWithBreak(BreakType break_type) {
if (break_type != BreakType::Normal) {
LOG_ERROR(Service_NFC, "Break type not implemented {}", break_type);
return WrongDeviceState;
}
std::vector<u8> data(sizeof(EncryptedNTAG215File));
if (is_plain_amiibo) {
memcpy(data.data(), &tag_data, sizeof(tag_data));
@@ -258,8 +293,6 @@ Result NfpDevice::Flush() {
return WriteAmiiboFailed;
}
is_data_moddified = false;
return ResultSuccess;
}
@@ -417,6 +450,38 @@ Result NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const {
return ResultSuccess;
}
Result NfpDevice::GetRegisterInfoPrivate(RegisterInfoPrivate& register_info) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return TagRemoved;
}
return WrongDeviceState;
}
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
return WrongDeviceState;
}
if (tag_data.settings.settings.amiibo_initialized == 0) {
return RegistrationIsNotInitialized;
}
Service::Mii::MiiManager manager;
const auto& settings = tag_data.settings;
// TODO: Validate and complete this data
register_info = {
.mii_store_data = {},
.creation_date = settings.init_date.GetWriteDate(),
.amiibo_name = GetAmiiboName(settings),
.font_region = settings.settings.font_region,
};
return ResultSuccess;
}
Result NfpDevice::GetAdminInfo(AdminInfo& admin_info) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
@@ -807,6 +872,159 @@ Result NfpDevice::DeleteApplicationArea() {
return Flush();
}
Result NfpDevice::ExistApplicationArea(bool& has_application_area) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return TagRemoved;
}
return WrongDeviceState;
}
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
return WrongDeviceState;
}
has_application_area = tag_data.settings.settings.appdata_initialized.Value() != 0;
return ResultSuccess;
}
Result NfpDevice::GetAll(NfpData& data) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return TagRemoved;
}
return WrongDeviceState;
}
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
return WrongDeviceState;
}
CommonInfo common_info{};
Service::Mii::MiiManager manager;
const u64 application_id = tag_data.application_id;
GetCommonInfo(common_info);
data = {
.magic = tag_data.constant_value,
.write_counter = tag_data.write_counter,
.settings_crc = tag_data.settings.crc,
.common_info = common_info,
.mii_char_info = tag_data.owner_mii,
.mii_store_data_extension = tag_data.mii_extension,
.creation_date = tag_data.settings.init_date.GetWriteDate(),
.amiibo_name = tag_data.settings.amiibo_name,
.amiibo_name_null_terminated = 0,
.settings = tag_data.settings.settings,
.unknown1 = tag_data.unknown,
.register_info_crc = tag_data.register_info_crc,
.unknown2 = tag_data.unknown2,
.application_id = application_id,
.access_id = tag_data.application_area_id,
.settings_crc_counter = tag_data.settings.crc_counter,
.font_region = tag_data.settings.settings.font_region,
.tag_type = PackedTagType::Type2,
.console_type =
static_cast<AppAreaVersion>(application_id >> application_id_version_offset & 0xf),
.application_id_byte = tag_data.application_id_byte,
.application_area = tag_data.application_area,
};
return ResultSuccess;
}
Result NfpDevice::SetAll(const NfpData& data) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return TagRemoved;
}
return WrongDeviceState;
}
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
return WrongDeviceState;
}
tag_data.constant_value = data.magic;
tag_data.write_counter = data.write_counter;
tag_data.settings.crc = data.settings_crc;
tag_data.settings.write_date.SetWriteDate(data.common_info.last_write_date);
tag_data.write_counter = data.common_info.write_counter;
tag_data.amiibo_version = data.common_info.version;
tag_data.owner_mii = data.mii_char_info;
tag_data.mii_extension = data.mii_store_data_extension;
tag_data.settings.init_date.SetWriteDate(data.creation_date);
tag_data.settings.amiibo_name = data.amiibo_name;
tag_data.settings.settings = data.settings;
tag_data.unknown = data.unknown1;
tag_data.register_info_crc = data.register_info_crc;
tag_data.unknown2 = data.unknown2;
tag_data.application_id = data.application_id;
tag_data.application_area_id = data.access_id;
tag_data.settings.crc_counter = data.settings_crc_counter;
tag_data.settings.settings.font_region.Assign(data.font_region);
tag_data.application_id_byte = data.application_id_byte;
tag_data.application_area = data.application_area;
return ResultSuccess;
}
Result NfpDevice::BreakTag(BreakType break_type) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return TagRemoved;
}
return WrongDeviceState;
}
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
return WrongDeviceState;
}
// TODO: Complete this implementation
return FlushWithBreak(break_type);
}
Result NfpDevice::ReadBackupData() {
// Not implemented
return ResultSuccess;
}
Result NfpDevice::WriteBackupData() {
// Not implemented
return ResultSuccess;
}
Result NfpDevice::WriteNtf() {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return TagRemoved;
}
return WrongDeviceState;
}
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
return WrongDeviceState;
}
// Not implemented
return ResultSuccess;
}
u64 NfpDevice::GetHandle() const {
// Generate a handle based of the npad id
return static_cast<u64>(npad_id);
+12
View File
@@ -41,12 +41,16 @@ public:
Result StopDetection();
Result Mount(MountTarget mount_target);
Result Unmount();
Result Flush();
Result FlushDebug();
Result FlushWithBreak(BreakType break_type);
Result GetTagInfo(TagInfo& tag_info) const;
Result GetCommonInfo(CommonInfo& common_info) const;
Result GetModelInfo(ModelInfo& model_info) const;
Result GetRegisterInfo(RegisterInfo& register_info) const;
Result GetRegisterInfoPrivate(RegisterInfoPrivate& register_info) const;
Result GetAdminInfo(AdminInfo& admin_info) const;
Result DeleteRegisterInfo();
@@ -61,6 +65,14 @@ public:
Result CreateApplicationArea(u32 access_id, std::span<const u8> data);
Result RecreateApplicationArea(u32 access_id, std::span<const u8> data);
Result DeleteApplicationArea();
Result ExistApplicationArea(bool& has_application_area);
Result GetAll(NfpData& data) const;
Result SetAll(const NfpData& data);
Result BreakTag(BreakType break_type);
Result ReadBackupData();
Result WriteBackupData();
Result WriteNtf();
u64 GetHandle() const;
u32 GetApplicationAreaSize() const;
@@ -7,42 +7,13 @@
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/nfp/nfp_device.h"
#include "core/hle/service/nfp/nfp_interface.h"
#include "core/hle/service/nfp/nfp_result.h"
#include "core/hle/service/nfp/nfp_user.h"
namespace Service::NFP {
IUser::IUser(Core::System& system_)
: ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name} {
static const FunctionInfo functions[] = {
{0, &IUser::Initialize, "Initialize"},
{1, &IUser::Finalize, "Finalize"},
{2, &IUser::ListDevices, "ListDevices"},
{3, &IUser::StartDetection, "StartDetection"},
{4, &IUser::StopDetection, "StopDetection"},
{5, &IUser::Mount, "Mount"},
{6, &IUser::Unmount, "Unmount"},
{7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
{8, &IUser::GetApplicationArea, "GetApplicationArea"},
{9, &IUser::SetApplicationArea, "SetApplicationArea"},
{10, &IUser::Flush, "Flush"},
{11, &IUser::Restore, "Restore"},
{12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
{13, &IUser::GetTagInfo, "GetTagInfo"},
{14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
{15, &IUser::GetCommonInfo, "GetCommonInfo"},
{16, &IUser::GetModelInfo, "GetModelInfo"},
{17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
{18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
{19, &IUser::GetState, "GetState"},
{20, &IUser::GetDeviceState, "GetDeviceState"},
{21, &IUser::GetNpadId, "GetNpadId"},
{22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
{23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
};
RegisterHandlers(functions);
Interface::Interface(Core::System& system_, const char* name)
: ServiceFramework{system_, name}, service_context{system_, service_name} {
availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
for (u32 device_index = 0; device_index < 10; device_index++) {
@@ -52,11 +23,11 @@ IUser::IUser(Core::System& system_)
}
}
IUser ::~IUser() {
Interface::~Interface() {
availability_change_event->Close();
}
void IUser::Initialize(HLERequestContext& ctx) {
void Interface::Initialize(HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called");
state = State::Initialized;
@@ -69,7 +40,33 @@ void IUser::Initialize(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
void IUser::Finalize(HLERequestContext& ctx) {
void Interface::InitializeSystem(HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called");
state = State::Initialized;
for (auto& device : devices) {
device->Initialize();
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Interface::InitializeDebug(HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called");
state = State::Initialized;
for (auto& device : devices) {
device->Initialize();
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Interface::Finalize(HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called");
state = State::NonInitialized;
@@ -82,7 +79,33 @@ void IUser::Finalize(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
void IUser::ListDevices(HLERequestContext& ctx) {
void Interface::FinalizeSystem(HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called");
state = State::NonInitialized;
for (auto& device : devices) {
device->Finalize();
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Interface::FinalizeDebug(HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called");
state = State::NonInitialized;
for (auto& device : devices) {
device->Finalize();
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void Interface::ListDevices(HLERequestContext& ctx) {
LOG_DEBUG(Service_NFP, "called");
if (state == State::NonInitialized) {
@@ -128,7 +151,7 @@ void IUser::ListDevices(HLERequestContext& ctx) {
rb.Push(static_cast<s32>(nfp_devices.size()));
}
void IUser::StartDetection(HLERequestContext& ctx) {
void Interface::StartDetection(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto nfp_protocol{rp.PopEnum<TagProtocol>()};
@@ -153,7 +176,7 @@ void IUser::StartDetection(HLERequestContext& ctx) {
rb.Push(result);
}
void IUser::StopDetection(HLERequestContext& ctx) {
void Interface::StopDetection(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
@@ -177,7 +200,7 @@ void IUser::StopDetection(HLERequestContext& ctx) {
rb.Push(result);
}
void IUser::Mount(HLERequestContext& ctx) {
void Interface::Mount(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto model_type{rp.PopEnum<ModelType>()};
@@ -204,7 +227,7 @@ void IUser::Mount(HLERequestContext& ctx) {
rb.Push(result);
}
void IUser::Unmount(HLERequestContext& ctx) {
void Interface::Unmount(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
@@ -228,7 +251,7 @@ void IUser::Unmount(HLERequestContext& ctx) {
rb.Push(result);
}
void IUser::OpenApplicationArea(HLERequestContext& ctx) {
void Interface::OpenApplicationArea(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto access_id{rp.Pop<u32>()};
@@ -253,7 +276,7 @@ void IUser::OpenApplicationArea(HLERequestContext& ctx) {
rb.Push(result);
}
void IUser::GetApplicationArea(HLERequestContext& ctx) {
void Interface::GetApplicationArea(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto data_size = ctx.GetWriteBufferSize();
@@ -287,7 +310,7 @@ void IUser::GetApplicationArea(HLERequestContext& ctx) {
rb.Push(static_cast<u32>(data_size));
}
void IUser::SetApplicationArea(HLERequestContext& ctx) {
void Interface::SetApplicationArea(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto data{ctx.ReadBuffer()};
@@ -318,7 +341,7 @@ void IUser::SetApplicationArea(HLERequestContext& ctx) {
rb.Push(result);
}
void IUser::Flush(HLERequestContext& ctx) {
void Interface::Flush(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
@@ -342,7 +365,7 @@ void IUser::Flush(HLERequestContext& ctx) {
rb.Push(result);
}
void IUser::Restore(HLERequestContext& ctx) {
void Interface::Restore(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
@@ -366,7 +389,7 @@ void IUser::Restore(HLERequestContext& ctx) {
rb.Push(result);
}
void IUser::CreateApplicationArea(HLERequestContext& ctx) {
void Interface::CreateApplicationArea(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto access_id{rp.Pop<u32>()};
@@ -399,7 +422,7 @@ void IUser::CreateApplicationArea(HLERequestContext& ctx) {
rb.Push(result);
}
void IUser::GetTagInfo(HLERequestContext& ctx) {
void Interface::GetTagInfo(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
@@ -425,7 +448,7 @@ void IUser::GetTagInfo(HLERequestContext& ctx) {
rb.Push(result);
}
void IUser::GetRegisterInfo(HLERequestContext& ctx) {
void Interface::GetRegisterInfo(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
@@ -451,7 +474,7 @@ void IUser::GetRegisterInfo(HLERequestContext& ctx) {
rb.Push(result);
}
void IUser::GetCommonInfo(HLERequestContext& ctx) {
void Interface::GetCommonInfo(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
@@ -477,7 +500,7 @@ void IUser::GetCommonInfo(HLERequestContext& ctx) {
rb.Push(result);
}
void IUser::GetModelInfo(HLERequestContext& ctx) {
void Interface::GetModelInfo(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
@@ -503,7 +526,7 @@ void IUser::GetModelInfo(HLERequestContext& ctx) {
rb.Push(result);
}
void IUser::AttachActivateEvent(HLERequestContext& ctx) {
void Interface::AttachActivateEvent(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
@@ -527,7 +550,7 @@ void IUser::AttachActivateEvent(HLERequestContext& ctx) {
rb.PushCopyObjects(device.value()->GetActivateEvent());
}
void IUser::AttachDeactivateEvent(HLERequestContext& ctx) {
void Interface::AttachDeactivateEvent(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
@@ -551,7 +574,7 @@ void IUser::AttachDeactivateEvent(HLERequestContext& ctx) {
rb.PushCopyObjects(device.value()->GetDeactivateEvent());
}
void IUser::GetState(HLERequestContext& ctx) {
void Interface::GetState(HLERequestContext& ctx) {
LOG_DEBUG(Service_NFP, "called");
IPC::ResponseBuilder rb{ctx, 3};
@@ -559,7 +582,7 @@ void IUser::GetState(HLERequestContext& ctx) {
rb.PushEnum(state);
}
void IUser::GetDeviceState(HLERequestContext& ctx) {
void Interface::GetDeviceState(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
@@ -577,7 +600,7 @@ void IUser::GetDeviceState(HLERequestContext& ctx) {
rb.PushEnum(device.value()->GetCurrentState());
}
void IUser::GetNpadId(HLERequestContext& ctx) {
void Interface::GetNpadId(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
@@ -601,7 +624,7 @@ void IUser::GetNpadId(HLERequestContext& ctx) {
rb.PushEnum(device.value()->GetNpadId());
}
void IUser::GetApplicationAreaSize(HLERequestContext& ctx) {
void Interface::GetApplicationAreaSize(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
@@ -619,7 +642,7 @@ void IUser::GetApplicationAreaSize(HLERequestContext& ctx) {
rb.Push(device.value()->GetApplicationAreaSize());
}
void IUser::AttachAvailabilityChangeEvent(HLERequestContext& ctx) {
void Interface::AttachAvailabilityChangeEvent(HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called");
if (state == State::NonInitialized) {
@@ -633,7 +656,7 @@ void IUser::AttachAvailabilityChangeEvent(HLERequestContext& ctx) {
rb.PushCopyObjects(availability_change_event->GetReadableEvent());
}
void IUser::RecreateApplicationArea(HLERequestContext& ctx) {
void Interface::RecreateApplicationArea(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto access_id{rp.Pop<u32>()};
@@ -660,7 +683,361 @@ void IUser::RecreateApplicationArea(HLERequestContext& ctx) {
rb.Push(result);
}
std::optional<std::shared_ptr<NfpDevice>> IUser::GetNfpDevice(u64 handle) {
void Interface::Format(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
const auto result = device.value()->Format();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Interface::GetAdminInfo(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
AdminInfo admin_info{};
const auto result = device.value()->GetAdminInfo(admin_info);
ctx.WriteBuffer(admin_info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Interface::GetRegisterInfoPrivate(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
RegisterInfoPrivate register_info{};
const auto result = device.value()->GetRegisterInfoPrivate(register_info);
ctx.WriteBuffer(register_info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Interface::SetRegisterInfoPrivate(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto buffer{ctx.ReadBuffer()};
LOG_DEBUG(Service_NFP, "called, device_handle={}, buffer_size={}", device_handle,
buffer.size());
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
const auto result = device.value()->SetRegisterInfoPrivate({});
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Interface::DeleteRegisterInfo(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
const auto result = device.value()->DeleteRegisterInfo();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Interface::DeleteApplicationArea(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
const auto result = device.value()->DeleteApplicationArea();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Interface::ExistsApplicationArea(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
bool has_application_area = false;
const auto result = device.value()->ExistApplicationArea(has_application_area);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push(has_application_area);
}
void Interface::GetAll(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
NfpData data{};
const auto result = device.value()->GetAll(data);
ctx.WriteBuffer(data);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Interface::SetAll(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto nfp_data{ctx.ReadBuffer()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
NfpData data{};
memcpy(&data, nfp_data.data(), sizeof(NfpData));
const auto result = device.value()->SetAll(data);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Interface::FlushDebug(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
const auto result = device.value()->FlushDebug();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Interface::BreakTag(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto break_type{rp.PopEnum<BreakType>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}, break_type={}", device_handle, break_type);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
const auto result = device.value()->BreakTag(break_type);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Interface::ReadBackupData(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
const auto result = device.value()->ReadBackupData();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Interface::WriteBackupData(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
const auto result = device.value()->WriteBackupData();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Interface::WriteNtf(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(NfcDisabled);
return;
}
auto device = GetNfpDevice(device_handle);
if (!device.has_value()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(DeviceNotFound);
return;
}
const auto result = device.value()->WriteNtf();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
std::optional<std::shared_ptr<NfpDevice>> Interface::GetNfpDevice(u64 handle) {
for (auto& device : devices) {
if (device->GetHandle() == handle) {
return device;
@@ -13,19 +13,17 @@
namespace Service::NFP {
class NfpDevice;
class IUser final : public ServiceFramework<IUser> {
class Interface : public ServiceFramework<Interface> {
public:
explicit IUser(Core::System& system_);
~IUser();
private:
enum class State : u32 {
NonInitialized,
Initialized,
};
explicit Interface(Core::System& system_, const char* name);
~Interface() override;
void Initialize(HLERequestContext& ctx);
void InitializeSystem(HLERequestContext& ctx);
void InitializeDebug(HLERequestContext& ctx);
void Finalize(HLERequestContext& ctx);
void FinalizeSystem(HLERequestContext& ctx);
void FinalizeDebug(HLERequestContext& ctx);
void ListDevices(HLERequestContext& ctx);
void StartDetection(HLERequestContext& ctx);
void StopDetection(HLERequestContext& ctx);
@@ -49,6 +47,26 @@ private:
void GetApplicationAreaSize(HLERequestContext& ctx);
void AttachAvailabilityChangeEvent(HLERequestContext& ctx);
void RecreateApplicationArea(HLERequestContext& ctx);
void Format(HLERequestContext& ctx);
void GetAdminInfo(HLERequestContext& ctx);
void GetRegisterInfoPrivate(HLERequestContext& ctx);
void SetRegisterInfoPrivate(HLERequestContext& ctx);
void DeleteRegisterInfo(HLERequestContext& ctx);
void DeleteApplicationArea(HLERequestContext& ctx);
void ExistsApplicationArea(HLERequestContext& ctx);
void GetAll(HLERequestContext& ctx);
void SetAll(HLERequestContext& ctx);
void FlushDebug(HLERequestContext& ctx);
void BreakTag(HLERequestContext& ctx);
void ReadBackupData(HLERequestContext& ctx);
void WriteBackupData(HLERequestContext& ctx);
void WriteNtf(HLERequestContext& ctx);
private:
enum class State : u32 {
NonInitialized,
Initialized,
};
std::optional<std::shared_ptr<NfpDevice>> GetNfpDevice(u64 handle);
+54
View File
@@ -109,6 +109,12 @@ enum class AppAreaVersion : u8 {
NotSet = 0xFF,
};
enum class BreakType : u32 {
Normal,
Unknown1,
Unknown2,
};
enum class CabinetMode : u8 {
StartNicknameAndOwnerSettings,
StartGameDataEraser,
@@ -181,6 +187,12 @@ struct AmiiboDate {
};
}
void SetWriteDate(const WriteDate& write_date) {
SetYear(write_date.year);
SetMonth(write_date.month);
SetDay(write_date.day);
}
void SetYear(u16 year) {
const u16 year_converted = static_cast<u16>((year - 2000) << 9);
raw_date = Common::swap16((GetValue() & ~0xFE00) | year_converted);
@@ -354,6 +366,15 @@ struct RegisterInfo {
};
static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
struct RegisterInfoPrivate {
Service::Mii::MiiStoreData mii_store_data;
WriteDate creation_date;
AmiiboName amiibo_name;
u8 font_region;
INSERT_PADDING_BYTES(0x8E);
};
static_assert(sizeof(RegisterInfoPrivate) == 0x100, "RegisterInfoPrivate is an invalid size");
struct AdminInfo {
u64 application_id;
u32 application_area_id;
@@ -366,6 +387,39 @@ struct AdminInfo {
};
static_assert(sizeof(AdminInfo) == 0x40, "AdminInfo is an invalid size");
#pragma pack(1)
// This is nn::nfp::NfpData
struct NfpData {
u8 magic;
INSERT_PADDING_BYTES(0x1);
u8 write_counter;
INSERT_PADDING_BYTES(0x1);
u32 settings_crc;
INSERT_PADDING_BYTES(0x38);
CommonInfo common_info;
Service::Mii::Ver3StoreData mii_char_info;
Service::Mii::NfpStoreDataExtension mii_store_data_extension;
WriteDate creation_date;
std::array<u16_be, amiibo_name_length> amiibo_name;
u16 amiibo_name_null_terminated;
Settings settings;
u8 unknown1;
u32 register_info_crc;
std::array<u32, 5> unknown2;
INSERT_PADDING_BYTES(0x64);
u64 application_id;
u32 access_id;
u16 settings_crc_counter;
u8 font_region;
PackedTagType tag_type;
AppAreaVersion console_type;
u8 application_id_byte;
INSERT_PADDING_BYTES(0x2E);
ApplicationArea application_area;
};
static_assert(sizeof(NfpData) == 0x298, "NfpData is an invalid size");
#pragma pack()
struct SectorKey {
MifareCmd command;
u8 unknown; // Usually 1
+6
View File
@@ -33,6 +33,9 @@ ServerManager::ServerManager(Core::System& system) : m_system{system}, m_serve_m
// Initialize event.
m_event = Kernel::KEvent::Create(system.Kernel());
m_event->Initialize(nullptr);
// Register event.
Kernel::KEvent::Register(system.Kernel(), m_event);
}
ServerManager::~ServerManager() {
@@ -160,6 +163,9 @@ Result ServerManager::ManageDeferral(Kernel::KEvent** out_event) {
// Initialize the event.
m_deferral_event->Initialize(nullptr);
// Register the event.
Kernel::KEvent::Register(m_system.Kernel(), m_deferral_event);
// Set the output.
*out_event = m_deferral_event;
+3
View File
@@ -64,6 +64,9 @@ Result ServiceManager::RegisterService(std::string name, u32 max_sessions,
auto* port = Kernel::KPort::Create(kernel);
port->Initialize(ServerSessionCountMax, false, 0);
// Register the port.
Kernel::KPort::Register(kernel, port);
service_ports.emplace(name, port);
registered_services.emplace(name, handler);
if (deferral_event) {
@@ -49,6 +49,9 @@ void Controller::CloneCurrentObject(HLERequestContext& ctx) {
// Commit the session reservation.
session_reservation.Commit();
// Register the session.
Kernel::KSession::Register(system.Kernel(), session);
// Register with server manager.
session_manager->GetServerManager().RegisterSession(&session->GetServerSession(),
session_manager);
+1 -1
View File
@@ -462,7 +462,7 @@ struct Memory::Impl {
}
if (Settings::IsFastmemEnabled()) {
const bool is_read_enable = Settings::IsGPULevelHigh() || !cached;
const bool is_read_enable = !Settings::IsGPULevelExtreme() || !cached;
system.DeviceMemory().buffer.Protect(vaddr, size, is_read_enable, !cached);
}
+13 -3
View File
@@ -49,6 +49,7 @@ static void PrintHelp(const char* argv0) {
" [options] <filename>\n"
"--room-name The name of the room\n"
"--room-description The room description\n"
"--bind-address The bind address for the room\n"
"--port The port used for the room\n"
"--max_members The maximum number of players for this room\n"
"--password The password for the room\n"
@@ -195,6 +196,7 @@ int main(int argc, char** argv) {
std::string web_api_url;
std::string ban_list_file;
std::string log_file = "yuzu-room.log";
std::string bind_address;
u64 preferred_game_id = 0;
u32 port = Network::DefaultRoomPort;
u32 max_members = 16;
@@ -203,6 +205,7 @@ int main(int argc, char** argv) {
static struct option long_options[] = {
{"room-name", required_argument, 0, 'n'},
{"room-description", required_argument, 0, 'd'},
{"bind-address", required_argument, 0, 's'},
{"port", required_argument, 0, 'p'},
{"max_members", required_argument, 0, 'm'},
{"password", required_argument, 0, 'w'},
@@ -222,7 +225,8 @@ int main(int argc, char** argv) {
InitializeLogging(log_file);
while (optind < argc) {
int arg = getopt_long(argc, argv, "n:d:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index);
int arg =
getopt_long(argc, argv, "n:d:s:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index);
if (arg != -1) {
switch (static_cast<char>(arg)) {
case 'n':
@@ -231,6 +235,9 @@ int main(int argc, char** argv) {
case 'd':
room_description.assign(optarg);
break;
case 's':
bind_address.assign(optarg);
break;
case 'p':
port = strtoul(optarg, &endarg, 0);
break;
@@ -295,6 +302,9 @@ int main(int argc, char** argv) {
PrintHelp(argv[0]);
return -1;
}
if (bind_address.empty()) {
LOG_INFO(Network, "Bind address is empty: defaulting to 0.0.0.0");
}
if (port > UINT16_MAX) {
LOG_ERROR(Network, "Port needs to be in the range 0 - 65535!");
PrintHelp(argv[0]);
@@ -358,8 +368,8 @@ int main(int argc, char** argv) {
if (auto room = network.GetRoom().lock()) {
AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game,
.id = preferred_game_id};
if (!room->Create(room_name, room_description, "", port, password, max_members, username,
preferred_game_info, std::move(verify_backend), ban_list,
if (!room->Create(room_name, room_description, bind_address, port, password, max_members,
username, preferred_game_info, std::move(verify_backend), ban_list,
enable_yuzu_mods)) {
LOG_INFO(Network, "Failed to create room: ");
return -1;
@@ -473,7 +473,8 @@ void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value) {
}
void EmitSetSampleMask(EmitContext& ctx, Id value) {
ctx.OpStore(ctx.sample_mask, value);
const Id pointer{ctx.OpAccessChain(ctx.output_u32, ctx.sample_mask, ctx.u32_zero_value)};
ctx.OpStore(pointer, value);
}
void EmitSetFragDepth(EmitContext& ctx, Id value) {
@@ -1572,7 +1572,8 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
Decorate(frag_depth, spv::Decoration::BuiltIn, spv::BuiltIn::FragDepth);
}
if (info.stores_sample_mask) {
sample_mask = DefineOutput(*this, U32[1], std::nullopt);
const Id array_type{TypeArray(U32[1], Const(1U))};
sample_mask = DefineOutput(*this, array_type, std::nullopt);
Decorate(sample_mask, spv::Decoration::BuiltIn, spv::BuiltIn::SampleMask);
}
break;
+1 -1
View File
@@ -15,7 +15,7 @@ add_executable(tests
core/core_timing.cpp
core/internal_network/network.cpp
precompiled_headers.h
video_core/buffer_base.cpp
video_core/memory_tracker.cpp
input_common/calibration_configuration_job.cpp
)
-549
View File
@@ -1,549 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <stdexcept>
#include <unordered_map>
#include <catch2/catch_test_macros.hpp>
#include "common/alignment.h"
#include "common/common_types.h"
#include "video_core/buffer_cache/buffer_base.h"
namespace {
using VideoCommon::BufferBase;
using Range = std::pair<u64, u64>;
constexpr u64 PAGE = 4096;
constexpr u64 WORD = 4096 * 64;
constexpr VAddr c = 0x1328914000;
class RasterizerInterface {
public:
void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
const u64 page_start{addr >> Core::Memory::YUZU_PAGEBITS};
const u64 page_end{(addr + size + Core::Memory::YUZU_PAGESIZE - 1) >>
Core::Memory::YUZU_PAGEBITS};
for (u64 page = page_start; page < page_end; ++page) {
int& value = page_table[page];
value += delta;
if (value < 0) {
throw std::logic_error{"negative page"};
}
if (value == 0) {
page_table.erase(page);
}
}
}
[[nodiscard]] int Count(VAddr addr) const noexcept {
const auto it = page_table.find(addr >> Core::Memory::YUZU_PAGEBITS);
return it == page_table.end() ? 0 : it->second;
}
[[nodiscard]] unsigned Count() const noexcept {
unsigned count = 0;
for (const auto& [index, value] : page_table) {
count += value;
}
return count;
}
private:
std::unordered_map<u64, int> page_table;
};
} // Anonymous namespace
TEST_CASE("BufferBase: Small buffer", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD);
REQUIRE(rasterizer.Count() == 0);
buffer.UnmarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == WORD / PAGE);
REQUIRE(buffer.ModifiedCpuRegion(c, WORD) == Range{0, 0});
buffer.MarkRegionAsCpuModified(c + PAGE, 1);
REQUIRE(buffer.ModifiedCpuRegion(c, WORD) == Range{PAGE * 1, PAGE * 2});
}
TEST_CASE("BufferBase: Large buffer", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD * 32);
buffer.UnmarkRegionAsCpuModified(c, WORD * 32);
buffer.MarkRegionAsCpuModified(c + 4096, WORD * 4);
REQUIRE(buffer.ModifiedCpuRegion(c, WORD + PAGE * 2) == Range{PAGE, WORD + PAGE * 2});
REQUIRE(buffer.ModifiedCpuRegion(c + PAGE * 2, PAGE * 6) == Range{PAGE * 2, PAGE * 8});
REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{PAGE, WORD * 4 + PAGE});
REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 4, PAGE) == Range{WORD * 4, WORD * 4 + PAGE});
REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 3 + PAGE * 63, PAGE) ==
Range{WORD * 3 + PAGE * 63, WORD * 4});
buffer.MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 6, PAGE);
buffer.MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 5, WORD) ==
Range{WORD * 5 + PAGE * 6, WORD * 5 + PAGE * 9});
buffer.UnmarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 5, WORD) ==
Range{WORD * 5 + PAGE * 6, WORD * 5 + PAGE * 7});
buffer.MarkRegionAsCpuModified(c + PAGE, WORD * 31 + PAGE * 63);
REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{PAGE, WORD * 32});
buffer.UnmarkRegionAsCpuModified(c + PAGE * 4, PAGE);
buffer.UnmarkRegionAsCpuModified(c + PAGE * 6, PAGE);
buffer.UnmarkRegionAsCpuModified(c, WORD * 32);
REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{0, 0});
}
TEST_CASE("BufferBase: Rasterizer counting", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, PAGE * 2);
REQUIRE(rasterizer.Count() == 0);
buffer.UnmarkRegionAsCpuModified(c, PAGE);
REQUIRE(rasterizer.Count() == 1);
buffer.MarkRegionAsCpuModified(c, PAGE * 2);
REQUIRE(rasterizer.Count() == 0);
buffer.UnmarkRegionAsCpuModified(c, PAGE);
buffer.UnmarkRegionAsCpuModified(c + PAGE, PAGE);
REQUIRE(rasterizer.Count() == 2);
buffer.MarkRegionAsCpuModified(c, PAGE * 2);
REQUIRE(rasterizer.Count() == 0);
}
TEST_CASE("BufferBase: Basic range", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD);
buffer.UnmarkRegionAsCpuModified(c, WORD);
buffer.MarkRegionAsCpuModified(c, PAGE);
int num = 0;
buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
REQUIRE(offset == 0U);
REQUIRE(size == PAGE);
++num;
});
REQUIRE(num == 1U);
}
TEST_CASE("BufferBase: Border upload", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD * 2);
buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
buffer.ForEachUploadRange(c, WORD * 2, [](u64 offset, u64 size) {
REQUIRE(offset == WORD - PAGE);
REQUIRE(size == PAGE * 2);
});
}
TEST_CASE("BufferBase: Border upload range", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD * 2);
buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
buffer.ForEachUploadRange(c + WORD - PAGE, PAGE * 2, [](u64 offset, u64 size) {
REQUIRE(offset == WORD - PAGE);
REQUIRE(size == PAGE * 2);
});
buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
buffer.ForEachUploadRange(c + WORD - PAGE, PAGE, [](u64 offset, u64 size) {
REQUIRE(offset == WORD - PAGE);
REQUIRE(size == PAGE);
});
buffer.ForEachUploadRange(c + WORD, PAGE, [](u64 offset, u64 size) {
REQUIRE(offset == WORD);
REQUIRE(size == PAGE);
});
}
TEST_CASE("BufferBase: Border upload partial range", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD * 2);
buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
buffer.ForEachUploadRange(c + WORD - 1, 2, [](u64 offset, u64 size) {
REQUIRE(offset == WORD - PAGE);
REQUIRE(size == PAGE * 2);
});
buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
buffer.ForEachUploadRange(c + WORD - 1, 1, [](u64 offset, u64 size) {
REQUIRE(offset == WORD - PAGE);
REQUIRE(size == PAGE);
});
buffer.ForEachUploadRange(c + WORD + 50, 1, [](u64 offset, u64 size) {
REQUIRE(offset == WORD);
REQUIRE(size == PAGE);
});
}
TEST_CASE("BufferBase: Partial word uploads", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, 0x9d000);
int num = 0;
buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
REQUIRE(offset == 0U);
REQUIRE(size == WORD);
++num;
});
REQUIRE(num == 1);
buffer.ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) {
REQUIRE(offset == WORD);
REQUIRE(size == WORD);
++num;
});
REQUIRE(num == 2);
buffer.ForEachUploadRange(c + 0x79000, 0x24000, [&](u64 offset, u64 size) {
REQUIRE(offset == WORD * 2);
REQUIRE(size == PAGE * 0x1d);
++num;
});
REQUIRE(num == 3);
}
TEST_CASE("BufferBase: Partial page upload", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD);
buffer.UnmarkRegionAsCpuModified(c, WORD);
int num = 0;
buffer.MarkRegionAsCpuModified(c + PAGE * 2, PAGE);
buffer.MarkRegionAsCpuModified(c + PAGE * 9, PAGE);
buffer.ForEachUploadRange(c, PAGE * 3, [&](u64 offset, u64 size) {
REQUIRE(offset == PAGE * 2);
REQUIRE(size == PAGE);
++num;
});
REQUIRE(num == 1);
buffer.ForEachUploadRange(c + PAGE * 7, PAGE * 3, [&](u64 offset, u64 size) {
REQUIRE(offset == PAGE * 9);
REQUIRE(size == PAGE);
++num;
});
REQUIRE(num == 2);
}
TEST_CASE("BufferBase: Partial page upload with multiple words on the right") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD * 8);
buffer.UnmarkRegionAsCpuModified(c, WORD * 8);
buffer.MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
int num = 0;
buffer.ForEachUploadRange(c + PAGE * 10, WORD * 7, [&](u64 offset, u64 size) {
REQUIRE(offset == PAGE * 13);
REQUIRE(size == WORD * 7 - PAGE * 3);
++num;
});
REQUIRE(num == 1);
buffer.ForEachUploadRange(c + PAGE, WORD * 8, [&](u64 offset, u64 size) {
REQUIRE(offset == WORD * 7 + PAGE * 10);
REQUIRE(size == PAGE * 3);
++num;
});
REQUIRE(num == 2);
}
TEST_CASE("BufferBase: Partial page upload with multiple words on the left", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD * 8);
buffer.UnmarkRegionAsCpuModified(c, WORD * 8);
buffer.MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
int num = 0;
buffer.ForEachUploadRange(c + PAGE * 16, WORD * 7, [&](u64 offset, u64 size) {
REQUIRE(offset == PAGE * 16);
REQUIRE(size == WORD * 7 - PAGE * 3);
++num;
});
REQUIRE(num == 1);
buffer.ForEachUploadRange(c + PAGE, WORD, [&](u64 offset, u64 size) {
REQUIRE(offset == PAGE * 13);
REQUIRE(size == PAGE * 3);
++num;
});
REQUIRE(num == 2);
}
TEST_CASE("BufferBase: Partial page upload with multiple words in the middle", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD * 8);
buffer.UnmarkRegionAsCpuModified(c, WORD * 8);
buffer.MarkRegionAsCpuModified(c + PAGE * 13, PAGE * 140);
int num = 0;
buffer.ForEachUploadRange(c + PAGE * 16, WORD, [&](u64 offset, u64 size) {
REQUIRE(offset == PAGE * 16);
REQUIRE(size == WORD);
++num;
});
REQUIRE(num == 1);
buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
REQUIRE(offset == PAGE * 13);
REQUIRE(size == PAGE * 3);
++num;
});
REQUIRE(num == 2);
buffer.ForEachUploadRange(c, WORD * 8, [&](u64 offset, u64 size) {
REQUIRE(offset == WORD + PAGE * 16);
REQUIRE(size == PAGE * 73);
++num;
});
REQUIRE(num == 3);
}
TEST_CASE("BufferBase: Empty right bits", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD * 2048);
buffer.UnmarkRegionAsCpuModified(c, WORD * 2048);
buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
buffer.ForEachUploadRange(c, WORD * 2048, [](u64 offset, u64 size) {
REQUIRE(offset == WORD - PAGE);
REQUIRE(size == PAGE * 2);
});
}
TEST_CASE("BufferBase: Out of bound ranges 1", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD);
buffer.UnmarkRegionAsCpuModified(c, WORD);
buffer.MarkRegionAsCpuModified(c, PAGE);
int num = 0;
buffer.ForEachUploadRange(c - WORD, WORD, [&](u64 offset, u64 size) { ++num; });
buffer.ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) { ++num; });
buffer.ForEachUploadRange(c - PAGE, PAGE, [&](u64 offset, u64 size) { ++num; });
REQUIRE(num == 0);
buffer.ForEachUploadRange(c - PAGE, PAGE * 2, [&](u64 offset, u64 size) { ++num; });
REQUIRE(num == 1);
buffer.MarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 0);
}
TEST_CASE("BufferBase: Out of bound ranges 2", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, 0x22000);
REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x22000, PAGE));
REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x28000, PAGE));
REQUIRE(rasterizer.Count() == 0);
REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x21100, PAGE - 0x100));
REQUIRE(rasterizer.Count() == 1);
REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c - 0x1000, PAGE * 2));
buffer.UnmarkRegionAsCpuModified(c - 0x3000, PAGE * 2);
buffer.UnmarkRegionAsCpuModified(c - 0x2000, PAGE * 2);
REQUIRE(rasterizer.Count() == 2);
}
TEST_CASE("BufferBase: Out of bound ranges 3", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, 0x310720);
buffer.UnmarkRegionAsCpuModified(c, 0x310720);
REQUIRE(rasterizer.Count(c) == 1);
REQUIRE(rasterizer.Count(c + PAGE) == 1);
REQUIRE(rasterizer.Count(c + WORD) == 1);
REQUIRE(rasterizer.Count(c + WORD + PAGE) == 1);
}
TEST_CASE("BufferBase: Sparse regions 1", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD);
buffer.UnmarkRegionAsCpuModified(c, WORD);
buffer.MarkRegionAsCpuModified(c + PAGE * 1, PAGE);
buffer.MarkRegionAsCpuModified(c + PAGE * 3, PAGE * 4);
buffer.ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable {
static constexpr std::array<u64, 2> offsets{PAGE, PAGE * 3};
static constexpr std::array<u64, 2> sizes{PAGE, PAGE * 4};
REQUIRE(offset == offsets.at(i));
REQUIRE(size == sizes.at(i));
++i;
});
}
TEST_CASE("BufferBase: Sparse regions 2", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, 0x22000);
buffer.UnmarkRegionAsCpuModified(c, 0x22000);
REQUIRE(rasterizer.Count() == 0x22);
buffer.MarkRegionAsCpuModified(c + PAGE * 0x1B, PAGE);
buffer.MarkRegionAsCpuModified(c + PAGE * 0x21, PAGE);
buffer.ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable {
static constexpr std::array<u64, 2> offsets{PAGE * 0x1B, PAGE * 0x21};
static constexpr std::array<u64, 2> sizes{PAGE, PAGE};
REQUIRE(offset == offsets.at(i));
REQUIRE(size == sizes.at(i));
++i;
});
}
TEST_CASE("BufferBase: Single page modified range", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, PAGE);
REQUIRE(buffer.IsRegionCpuModified(c, PAGE));
buffer.UnmarkRegionAsCpuModified(c, PAGE);
REQUIRE(!buffer.IsRegionCpuModified(c, PAGE));
}
TEST_CASE("BufferBase: Two page modified range", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, PAGE * 2);
REQUIRE(buffer.IsRegionCpuModified(c, PAGE));
REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
REQUIRE(buffer.IsRegionCpuModified(c, PAGE * 2));
buffer.UnmarkRegionAsCpuModified(c, PAGE);
REQUIRE(!buffer.IsRegionCpuModified(c, PAGE));
}
TEST_CASE("BufferBase: Multi word modified ranges", "[video_core]") {
for (int offset = 0; offset < 4; ++offset) {
const VAddr address = c + WORD * offset;
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, address, WORD * 4);
REQUIRE(buffer.IsRegionCpuModified(address, PAGE));
REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 48, PAGE));
REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 56, PAGE));
buffer.UnmarkRegionAsCpuModified(address + PAGE * 32, PAGE);
REQUIRE(buffer.IsRegionCpuModified(address + PAGE, WORD));
REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 31, PAGE));
REQUIRE(!buffer.IsRegionCpuModified(address + PAGE * 32, PAGE));
REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 33, PAGE));
REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 31, PAGE * 2));
REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
buffer.UnmarkRegionAsCpuModified(address + PAGE * 33, PAGE);
REQUIRE(!buffer.IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
}
}
TEST_CASE("BufferBase: Single page in large buffer", "[video_core]") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD * 16);
buffer.UnmarkRegionAsCpuModified(c, WORD * 16);
REQUIRE(!buffer.IsRegionCpuModified(c, WORD * 16));
buffer.MarkRegionAsCpuModified(c + WORD * 12 + PAGE * 8, PAGE);
REQUIRE(buffer.IsRegionCpuModified(c, WORD * 16));
REQUIRE(buffer.IsRegionCpuModified(c + WORD * 10, WORD * 2));
REQUIRE(buffer.IsRegionCpuModified(c + WORD * 11, WORD * 2));
REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12, WORD * 2));
REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 4, PAGE * 8));
REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE * 8));
REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE));
REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 7, PAGE * 2));
REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 8, PAGE * 2));
}
TEST_CASE("BufferBase: Out of bounds region query") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD * 16);
REQUIRE(!buffer.IsRegionCpuModified(c - PAGE, PAGE));
REQUIRE(!buffer.IsRegionCpuModified(c - PAGE * 2, PAGE));
REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 16, PAGE));
REQUIRE(buffer.IsRegionCpuModified(c + WORD * 16 - PAGE, WORD * 64));
REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 16, WORD * 64));
}
TEST_CASE("BufferBase: Wrap word regions") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD * 2);
buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
buffer.MarkRegionAsCpuModified(c + PAGE * 63, PAGE * 2);
REQUIRE(buffer.IsRegionCpuModified(c, WORD * 2));
REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 62, PAGE));
REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE));
REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 64, PAGE));
REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE * 2));
REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE * 8));
REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 60, PAGE * 8));
REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 127, WORD * 16));
buffer.MarkRegionAsCpuModified(c + PAGE * 127, PAGE);
REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 127, WORD * 16));
REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 127, PAGE));
REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 126, PAGE));
REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 126, PAGE * 2));
REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 128, WORD * 16));
}
TEST_CASE("BufferBase: Unaligned page region query") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD);
buffer.UnmarkRegionAsCpuModified(c, WORD);
buffer.MarkRegionAsCpuModified(c + 4000, 1000);
REQUIRE(buffer.IsRegionCpuModified(c, PAGE));
REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
REQUIRE(buffer.IsRegionCpuModified(c + 4000, 1000));
REQUIRE(buffer.IsRegionCpuModified(c + 4000, 1));
}
TEST_CASE("BufferBase: Cached write") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD);
buffer.UnmarkRegionAsCpuModified(c, WORD);
buffer.CachedCpuWrite(c + PAGE, PAGE);
REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
buffer.FlushCachedWrites();
REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
buffer.MarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 0);
}
TEST_CASE("BufferBase: Multiple cached write") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD);
buffer.UnmarkRegionAsCpuModified(c, WORD);
buffer.CachedCpuWrite(c + PAGE, PAGE);
buffer.CachedCpuWrite(c + PAGE * 3, PAGE);
REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 3, PAGE));
buffer.FlushCachedWrites();
REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 3, PAGE));
buffer.MarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 0);
}
TEST_CASE("BufferBase: Cached write unmarked") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD);
buffer.UnmarkRegionAsCpuModified(c, WORD);
buffer.CachedCpuWrite(c + PAGE, PAGE);
buffer.UnmarkRegionAsCpuModified(c + PAGE, PAGE);
REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
buffer.FlushCachedWrites();
REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
buffer.MarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 0);
}
TEST_CASE("BufferBase: Cached write iterated") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD);
buffer.UnmarkRegionAsCpuModified(c, WORD);
buffer.CachedCpuWrite(c + PAGE, PAGE);
int num = 0;
buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
REQUIRE(num == 0);
REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
buffer.FlushCachedWrites();
REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
buffer.MarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 0);
}
TEST_CASE("BufferBase: Cached write downloads") {
RasterizerInterface rasterizer;
BufferBase buffer(rasterizer, c, WORD);
buffer.UnmarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 64);
buffer.CachedCpuWrite(c + PAGE, PAGE);
REQUIRE(rasterizer.Count() == 63);
buffer.MarkRegionAsGpuModified(c + PAGE, PAGE);
int num = 0;
buffer.ForEachDownloadRangeAndClear(c, WORD, [&](u64 offset, u64 size) { ++num; });
buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
REQUIRE(num == 0);
REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE));
buffer.FlushCachedWrites();
REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE));
buffer.MarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 0);
}
+549
View File
@@ -0,0 +1,549 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <memory>
#include <stdexcept>
#include <unordered_map>
#include <catch2/catch_test_macros.hpp>
#include "common/alignment.h"
#include "common/common_types.h"
#include "video_core/buffer_cache/memory_tracker_base.h"
namespace {
using Range = std::pair<u64, u64>;
constexpr u64 PAGE = 4096;
constexpr u64 WORD = 4096 * 64;
constexpr u64 HIGH_PAGE_BITS = 22;
constexpr u64 HIGH_PAGE_SIZE = 1ULL << HIGH_PAGE_BITS;
constexpr VAddr c = 16 * HIGH_PAGE_SIZE;
class RasterizerInterface {
public:
void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
const u64 page_start{addr >> Core::Memory::YUZU_PAGEBITS};
const u64 page_end{(addr + size + Core::Memory::YUZU_PAGESIZE - 1) >>
Core::Memory::YUZU_PAGEBITS};
for (u64 page = page_start; page < page_end; ++page) {
int& value = page_table[page];
value += delta;
if (value < 0) {
throw std::logic_error{"negative page"};
}
if (value == 0) {
page_table.erase(page);
}
}
}
[[nodiscard]] int Count(VAddr addr) const noexcept {
const auto it = page_table.find(addr >> Core::Memory::YUZU_PAGEBITS);
return it == page_table.end() ? 0 : it->second;
}
[[nodiscard]] unsigned Count() const noexcept {
unsigned count = 0;
for (const auto& [index, value] : page_table) {
count += value;
}
return count;
}
private:
std::unordered_map<u64, int> page_table;
};
} // Anonymous namespace
using MemoryTracker = VideoCommon::MemoryTrackerBase<RasterizerInterface>;
TEST_CASE("MemoryTracker: Small region", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
REQUIRE(rasterizer.Count() == 0);
memory_track->UnmarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == WORD / PAGE);
REQUIRE(memory_track->ModifiedCpuRegion(c, WORD) == Range{0, 0});
memory_track->MarkRegionAsCpuModified(c + PAGE, 1);
REQUIRE(memory_track->ModifiedCpuRegion(c, WORD) == Range{c + PAGE * 1, c + PAGE * 2});
}
TEST_CASE("MemoryTracker: Large region", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD * 32);
memory_track->MarkRegionAsCpuModified(c + 4096, WORD * 4);
REQUIRE(memory_track->ModifiedCpuRegion(c, WORD + PAGE * 2) ==
Range{c + PAGE, c + WORD + PAGE * 2});
REQUIRE(memory_track->ModifiedCpuRegion(c + PAGE * 2, PAGE * 6) ==
Range{c + PAGE * 2, c + PAGE * 8});
REQUIRE(memory_track->ModifiedCpuRegion(c, WORD * 32) == Range{c + PAGE, c + WORD * 4 + PAGE});
REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 4, PAGE) ==
Range{c + WORD * 4, c + WORD * 4 + PAGE});
REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 3 + PAGE * 63, PAGE) ==
Range{c + WORD * 3 + PAGE * 63, c + WORD * 4});
memory_track->MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 6, PAGE);
memory_track->MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 5, WORD) ==
Range{c + WORD * 5 + PAGE * 6, c + WORD * 5 + PAGE * 9});
memory_track->UnmarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 5, WORD) ==
Range{c + WORD * 5 + PAGE * 6, c + WORD * 5 + PAGE * 7});
memory_track->MarkRegionAsCpuModified(c + PAGE, WORD * 31 + PAGE * 63);
REQUIRE(memory_track->ModifiedCpuRegion(c, WORD * 32) == Range{c + PAGE, c + WORD * 32});
memory_track->UnmarkRegionAsCpuModified(c + PAGE * 4, PAGE);
memory_track->UnmarkRegionAsCpuModified(c + PAGE * 6, PAGE);
memory_track->UnmarkRegionAsCpuModified(c, WORD * 32);
REQUIRE(memory_track->ModifiedCpuRegion(c, WORD * 32) == Range{0, 0});
}
TEST_CASE("MemoryTracker: Rasterizer counting", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
REQUIRE(rasterizer.Count() == 0);
memory_track->UnmarkRegionAsCpuModified(c, PAGE);
REQUIRE(rasterizer.Count() == 1);
memory_track->MarkRegionAsCpuModified(c, PAGE * 2);
REQUIRE(rasterizer.Count() == 0);
memory_track->UnmarkRegionAsCpuModified(c, PAGE);
memory_track->UnmarkRegionAsCpuModified(c + PAGE, PAGE);
REQUIRE(rasterizer.Count() == 2);
memory_track->MarkRegionAsCpuModified(c, PAGE * 2);
REQUIRE(rasterizer.Count() == 0);
}
TEST_CASE("MemoryTracker: Basic range", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD);
memory_track->MarkRegionAsCpuModified(c, PAGE);
int num = 0;
memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
REQUIRE(offset == c);
REQUIRE(size == PAGE);
++num;
});
REQUIRE(num == 1U);
}
TEST_CASE("MemoryTracker: Border upload", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD * 2);
memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
memory_track->ForEachUploadRange(c, WORD * 2, [](u64 offset, u64 size) {
REQUIRE(offset == c + WORD - PAGE);
REQUIRE(size == PAGE * 2);
});
}
TEST_CASE("MemoryTracker: Border upload range", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD * 2);
memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
memory_track->ForEachUploadRange(c + WORD - PAGE, PAGE * 2, [](u64 offset, u64 size) {
REQUIRE(offset == c + WORD - PAGE);
REQUIRE(size == PAGE * 2);
});
memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
memory_track->ForEachUploadRange(c + WORD - PAGE, PAGE, [](u64 offset, u64 size) {
REQUIRE(offset == c + WORD - PAGE);
REQUIRE(size == PAGE);
});
memory_track->ForEachUploadRange(c + WORD, PAGE, [](u64 offset, u64 size) {
REQUIRE(offset == c + WORD);
REQUIRE(size == PAGE);
});
}
TEST_CASE("MemoryTracker: Border upload partial range", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD * 2);
memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
memory_track->ForEachUploadRange(c + WORD - 1, 2, [](u64 offset, u64 size) {
REQUIRE(offset == c + WORD - PAGE);
REQUIRE(size == PAGE * 2);
});
memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
memory_track->ForEachUploadRange(c + WORD - 1, 1, [](u64 offset, u64 size) {
REQUIRE(offset == c + WORD - PAGE);
REQUIRE(size == PAGE);
});
memory_track->ForEachUploadRange(c + WORD + 50, 1, [](u64 offset, u64 size) {
REQUIRE(offset == c + WORD);
REQUIRE(size == PAGE);
});
}
TEST_CASE("MemoryTracker: Partial word uploads", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
int num = 0;
memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
REQUIRE(offset == c);
REQUIRE(size == WORD);
++num;
});
REQUIRE(num == 1);
memory_track->ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) {
REQUIRE(offset == c + WORD);
REQUIRE(size == WORD);
++num;
});
REQUIRE(num == 2);
memory_track->ForEachUploadRange(c + 0x79000, 0x24000, [&](u64 offset, u64 size) {
REQUIRE(offset == c + WORD * 2);
REQUIRE(size == PAGE * 0x1d);
++num;
});
REQUIRE(num == 3);
}
TEST_CASE("MemoryTracker: Partial page upload", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD);
int num = 0;
memory_track->MarkRegionAsCpuModified(c + PAGE * 2, PAGE);
memory_track->MarkRegionAsCpuModified(c + PAGE * 9, PAGE);
memory_track->ForEachUploadRange(c, PAGE * 3, [&](u64 offset, u64 size) {
REQUIRE(offset == c + PAGE * 2);
REQUIRE(size == PAGE);
++num;
});
REQUIRE(num == 1);
memory_track->ForEachUploadRange(c + PAGE * 7, PAGE * 3, [&](u64 offset, u64 size) {
REQUIRE(offset == c + PAGE * 9);
REQUIRE(size == PAGE);
++num;
});
REQUIRE(num == 2);
}
TEST_CASE("MemoryTracker: Partial page upload with multiple words on the right") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD * 9);
memory_track->MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
int num = 0;
memory_track->ForEachUploadRange(c + PAGE * 10, WORD * 7, [&](u64 offset, u64 size) {
REQUIRE(offset == c + PAGE * 13);
REQUIRE(size == WORD * 7 - PAGE * 3);
++num;
});
REQUIRE(num == 1);
memory_track->ForEachUploadRange(c + PAGE, WORD * 8, [&](u64 offset, u64 size) {
REQUIRE(offset == c + WORD * 7 + PAGE * 10);
REQUIRE(size == PAGE * 3);
++num;
});
REQUIRE(num == 2);
}
TEST_CASE("MemoryTracker: Partial page upload with multiple words on the left", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD * 8);
memory_track->MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
int num = 0;
memory_track->ForEachUploadRange(c + PAGE * 16, WORD * 7, [&](u64 offset, u64 size) {
REQUIRE(offset == c + PAGE * 16);
REQUIRE(size == WORD * 7 - PAGE * 3);
++num;
});
REQUIRE(num == 1);
memory_track->ForEachUploadRange(c + PAGE, WORD, [&](u64 offset, u64 size) {
REQUIRE(offset == c + PAGE * 13);
REQUIRE(size == PAGE * 3);
++num;
});
REQUIRE(num == 2);
}
TEST_CASE("MemoryTracker: Partial page upload with multiple words in the middle", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD * 8);
memory_track->MarkRegionAsCpuModified(c + PAGE * 13, PAGE * 140);
int num = 0;
memory_track->ForEachUploadRange(c + PAGE * 16, WORD, [&](u64 offset, u64 size) {
REQUIRE(offset == c + PAGE * 16);
REQUIRE(size == WORD);
++num;
});
REQUIRE(num == 1);
memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
REQUIRE(offset == c + PAGE * 13);
REQUIRE(size == PAGE * 3);
++num;
});
REQUIRE(num == 2);
memory_track->ForEachUploadRange(c, WORD * 8, [&](u64 offset, u64 size) {
REQUIRE(offset == c + WORD + PAGE * 16);
REQUIRE(size == PAGE * 73);
++num;
});
REQUIRE(num == 3);
}
TEST_CASE("MemoryTracker: Empty right bits", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD * 2048);
memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
memory_track->ForEachUploadRange(c, WORD * 2048, [](u64 offset, u64 size) {
REQUIRE(offset == c + WORD - PAGE);
REQUIRE(size == PAGE * 2);
});
}
TEST_CASE("MemoryTracker: Out of bound ranges 1", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c - WORD, 3 * WORD);
memory_track->MarkRegionAsCpuModified(c, PAGE);
REQUIRE(rasterizer.Count() == (3 * WORD - PAGE) / PAGE);
int num = 0;
memory_track->ForEachUploadRange(c - WORD, WORD, [&](u64 offset, u64 size) { ++num; });
memory_track->ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) { ++num; });
memory_track->ForEachUploadRange(c - PAGE, PAGE, [&](u64 offset, u64 size) { ++num; });
REQUIRE(num == 0);
memory_track->ForEachUploadRange(c - PAGE, PAGE * 2, [&](u64 offset, u64 size) { ++num; });
REQUIRE(num == 1);
memory_track->MarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 2 * WORD / PAGE);
}
TEST_CASE("MemoryTracker: Out of bound ranges 2", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c + 0x22000, PAGE));
REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c + 0x28000, PAGE));
REQUIRE(rasterizer.Count() == 2);
REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c + 0x21100, PAGE - 0x100));
REQUIRE(rasterizer.Count() == 3);
REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c - PAGE, PAGE * 2));
memory_track->UnmarkRegionAsCpuModified(c - PAGE * 3, PAGE * 2);
memory_track->UnmarkRegionAsCpuModified(c - PAGE * 2, PAGE * 2);
REQUIRE(rasterizer.Count() == 7);
}
TEST_CASE("MemoryTracker: Out of bound ranges 3", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, 0x310720);
REQUIRE(rasterizer.Count(c) == 1);
REQUIRE(rasterizer.Count(c + PAGE) == 1);
REQUIRE(rasterizer.Count(c + WORD) == 1);
REQUIRE(rasterizer.Count(c + WORD + PAGE) == 1);
}
TEST_CASE("MemoryTracker: Sparse regions 1", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD);
memory_track->MarkRegionAsCpuModified(c + PAGE * 1, PAGE);
memory_track->MarkRegionAsCpuModified(c + PAGE * 3, PAGE * 4);
memory_track->ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable {
static constexpr std::array<u64, 2> offsets{c + PAGE, c + PAGE * 3};
static constexpr std::array<u64, 2> sizes{PAGE, PAGE * 4};
REQUIRE(offset == offsets.at(i));
REQUIRE(size == sizes.at(i));
++i;
});
}
TEST_CASE("MemoryTracker: Sparse regions 2", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, PAGE * 0x23);
REQUIRE(rasterizer.Count() == 0x23);
memory_track->MarkRegionAsCpuModified(c + PAGE * 0x1B, PAGE);
memory_track->MarkRegionAsCpuModified(c + PAGE * 0x21, PAGE);
memory_track->ForEachUploadRange(c, PAGE * 0x23, [i = 0](u64 offset, u64 size) mutable {
static constexpr std::array<u64, 3> offsets{c + PAGE * 0x1B, c + PAGE * 0x21};
static constexpr std::array<u64, 3> sizes{PAGE, PAGE};
REQUIRE(offset == offsets.at(i));
REQUIRE(size == sizes.at(i));
++i;
});
}
TEST_CASE("MemoryTracker: Single page modified range", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
REQUIRE(memory_track->IsRegionCpuModified(c, PAGE));
memory_track->UnmarkRegionAsCpuModified(c, PAGE);
REQUIRE(!memory_track->IsRegionCpuModified(c, PAGE));
}
TEST_CASE("MemoryTracker: Two page modified range", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
REQUIRE(memory_track->IsRegionCpuModified(c, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(c, PAGE * 2));
memory_track->UnmarkRegionAsCpuModified(c, PAGE);
REQUIRE(!memory_track->IsRegionCpuModified(c, PAGE));
}
TEST_CASE("MemoryTracker: Multi word modified ranges", "[video_core]") {
for (int offset = 0; offset < 4; ++offset) {
const VAddr address = c + WORD * offset;
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
REQUIRE(memory_track->IsRegionCpuModified(address, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 48, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 56, PAGE));
memory_track->UnmarkRegionAsCpuModified(address + PAGE * 32, PAGE);
REQUIRE(memory_track->IsRegionCpuModified(address + PAGE, WORD));
REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 31, PAGE));
REQUIRE(!memory_track->IsRegionCpuModified(address + PAGE * 32, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 33, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 31, PAGE * 2));
REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
memory_track->UnmarkRegionAsCpuModified(address + PAGE * 33, PAGE);
REQUIRE(!memory_track->IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
}
}
TEST_CASE("MemoryTracker: Single page in large region", "[video_core]") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD * 16);
REQUIRE(!memory_track->IsRegionCpuModified(c, WORD * 16));
memory_track->MarkRegionAsCpuModified(c + WORD * 12 + PAGE * 8, PAGE);
REQUIRE(memory_track->IsRegionCpuModified(c, WORD * 16));
REQUIRE(!memory_track->IsRegionCpuModified(c + WORD * 10, WORD * 2));
REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 11, WORD * 2));
REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12, WORD * 2));
REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 4, PAGE * 8));
REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE * 8));
REQUIRE(!memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 7, PAGE * 2));
REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 8, PAGE * 2));
}
TEST_CASE("MemoryTracker: Wrap word regions") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD * 32);
memory_track->MarkRegionAsCpuModified(c + PAGE * 63, PAGE * 2);
REQUIRE(memory_track->IsRegionCpuModified(c, WORD * 2));
REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 62, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 63, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 64, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 63, PAGE * 2));
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 63, PAGE * 8));
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 60, PAGE * 8));
REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 127, WORD * 16));
memory_track->MarkRegionAsCpuModified(c + PAGE * 127, PAGE);
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 127, WORD * 16));
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 127, PAGE));
REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 126, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 126, PAGE * 2));
REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 128, WORD * 16));
}
TEST_CASE("MemoryTracker: Unaligned page region query") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD);
memory_track->MarkRegionAsCpuModified(c + 4000, 1000);
REQUIRE(memory_track->IsRegionCpuModified(c, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(c + 4000, 1000));
REQUIRE(memory_track->IsRegionCpuModified(c + 4000, 1));
}
TEST_CASE("MemoryTracker: Cached write") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD);
memory_track->CachedCpuWrite(c + PAGE, c + PAGE);
REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
memory_track->FlushCachedWrites();
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
memory_track->MarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 0);
}
TEST_CASE("MemoryTracker: Multiple cached write") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD);
memory_track->CachedCpuWrite(c + PAGE, PAGE);
memory_track->CachedCpuWrite(c + PAGE * 3, PAGE);
REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 3, PAGE));
memory_track->FlushCachedWrites();
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 3, PAGE));
memory_track->MarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 0);
}
TEST_CASE("MemoryTracker: Cached write unmarked") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD);
memory_track->CachedCpuWrite(c + PAGE, PAGE);
memory_track->UnmarkRegionAsCpuModified(c + PAGE, PAGE);
REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
memory_track->FlushCachedWrites();
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
memory_track->MarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 0);
}
TEST_CASE("MemoryTracker: Cached write iterated") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD);
memory_track->CachedCpuWrite(c + PAGE, PAGE);
int num = 0;
memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
REQUIRE(num == 0);
REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
memory_track->FlushCachedWrites();
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
memory_track->MarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 0);
}
TEST_CASE("MemoryTracker: Cached write downloads") {
RasterizerInterface rasterizer;
std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
memory_track->UnmarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 64);
memory_track->CachedCpuWrite(c + PAGE, PAGE);
REQUIRE(rasterizer.Count() == 63);
memory_track->MarkRegionAsGpuModified(c + PAGE, PAGE);
int num = 0;
memory_track->ForEachDownloadRangeAndClear(c, WORD, [&](u64 offset, u64 size) { ++num; });
REQUIRE(num == 1);
num = 0;
memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
REQUIRE(num == 0);
REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
REQUIRE(!memory_track->IsRegionGpuModified(c + PAGE, PAGE));
memory_track->FlushCachedWrites();
REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
REQUIRE(!memory_track->IsRegionGpuModified(c + PAGE, PAGE));
memory_track->MarkRegionAsCpuModified(c, WORD);
REQUIRE(rasterizer.Count() == 0);
}
+7
View File
@@ -11,8 +11,11 @@ endif()
add_library(video_core STATIC
buffer_cache/buffer_base.h
buffer_cache/buffer_cache_base.h
buffer_cache/buffer_cache.cpp
buffer_cache/buffer_cache.h
buffer_cache/memory_tracker_base.h
buffer_cache/word_manager.h
cache_types.h
cdma_pusher.cpp
cdma_pusher.h
@@ -104,6 +107,7 @@ add_library(video_core STATIC
renderer_null/renderer_null.h
renderer_opengl/blit_image.cpp
renderer_opengl/blit_image.h
renderer_opengl/gl_buffer_cache_base.cpp
renderer_opengl/gl_buffer_cache.cpp
renderer_opengl/gl_buffer_cache.h
renderer_opengl/gl_compute_pipeline.cpp
@@ -154,6 +158,7 @@ add_library(video_core STATIC
renderer_vulkan/renderer_vulkan.cpp
renderer_vulkan/vk_blit_screen.cpp
renderer_vulkan/vk_blit_screen.h
renderer_vulkan/vk_buffer_cache_base.cpp
renderer_vulkan/vk_buffer_cache.cpp
renderer_vulkan/vk_buffer_cache.h
renderer_vulkan/vk_command_pool.cpp
@@ -174,6 +179,8 @@ add_library(video_core STATIC
renderer_vulkan/vk_master_semaphore.h
renderer_vulkan/vk_pipeline_cache.cpp
renderer_vulkan/vk_pipeline_cache.h
renderer_vulkan/vk_present_manager.cpp
renderer_vulkan/vk_present_manager.h
renderer_vulkan/vk_query_cache.cpp
renderer_vulkan/vk_query_cache.h
renderer_vulkan/vk_rasterizer.cpp
+13 -507
View File
@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -11,9 +11,7 @@
#include "common/alignment.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/div_ceil.h"
#include "common/settings.h"
#include "core/memory.h"
#include "video_core/buffer_cache/word_manager.h"
namespace VideoCommon {
@@ -36,116 +34,12 @@ struct NullBufferParams {};
*/
template <class RasterizerInterface>
class BufferBase {
static constexpr u64 PAGES_PER_WORD = 64;
static constexpr u64 BYTES_PER_PAGE = Core::Memory::YUZU_PAGESIZE;
static constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE;
/// Vector tracking modified pages tightly packed with small vector optimization
union WordsArray {
/// Returns the pointer to the words state
[[nodiscard]] const u64* Pointer(bool is_short) const noexcept {
return is_short ? &stack : heap;
}
/// Returns the pointer to the words state
[[nodiscard]] u64* Pointer(bool is_short) noexcept {
return is_short ? &stack : heap;
}
u64 stack = 0; ///< Small buffers storage
u64* heap; ///< Not-small buffers pointer to the storage
};
struct Words {
explicit Words() = default;
explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} {
if (IsShort()) {
cpu.stack = ~u64{0};
gpu.stack = 0;
cached_cpu.stack = 0;
untracked.stack = ~u64{0};
} else {
// Share allocation between CPU and GPU pages and set their default values
const size_t num_words = NumWords();
u64* const alloc = new u64[num_words * 4];
cpu.heap = alloc;
gpu.heap = alloc + num_words;
cached_cpu.heap = alloc + num_words * 2;
untracked.heap = alloc + num_words * 3;
std::fill_n(cpu.heap, num_words, ~u64{0});
std::fill_n(gpu.heap, num_words, 0);
std::fill_n(cached_cpu.heap, num_words, 0);
std::fill_n(untracked.heap, num_words, ~u64{0});
}
// Clean up tailing bits
const u64 last_word_size = size_bytes % BYTES_PER_WORD;
const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
const u64 last_word = (~u64{0} << shift) >> shift;
cpu.Pointer(IsShort())[NumWords() - 1] = last_word;
untracked.Pointer(IsShort())[NumWords() - 1] = last_word;
}
~Words() {
Release();
}
Words& operator=(Words&& rhs) noexcept {
Release();
size_bytes = rhs.size_bytes;
cpu = rhs.cpu;
gpu = rhs.gpu;
cached_cpu = rhs.cached_cpu;
untracked = rhs.untracked;
rhs.cpu.heap = nullptr;
return *this;
}
Words(Words&& rhs) noexcept
: size_bytes{rhs.size_bytes}, cpu{rhs.cpu}, gpu{rhs.gpu},
cached_cpu{rhs.cached_cpu}, untracked{rhs.untracked} {
rhs.cpu.heap = nullptr;
}
Words& operator=(const Words&) = delete;
Words(const Words&) = delete;
/// Returns true when the buffer fits in the small vector optimization
[[nodiscard]] bool IsShort() const noexcept {
return size_bytes <= BYTES_PER_WORD;
}
/// Returns the number of words of the buffer
[[nodiscard]] size_t NumWords() const noexcept {
return Common::DivCeil(size_bytes, BYTES_PER_WORD);
}
/// Release buffer resources
void Release() {
if (!IsShort()) {
// CPU written words is the base for the heap allocation
delete[] cpu.heap;
}
}
u64 size_bytes = 0;
WordsArray cpu;
WordsArray gpu;
WordsArray cached_cpu;
WordsArray untracked;
};
enum class Type {
CPU,
GPU,
CachedCPU,
Untracked,
};
public:
explicit BufferBase(RasterizerInterface& rasterizer_, VAddr cpu_addr_, u64 size_bytes)
: rasterizer{&rasterizer_}, cpu_addr{Common::AlignDown(cpu_addr_, BYTES_PER_PAGE)},
words(Common::AlignUp(size_bytes + (cpu_addr_ - cpu_addr), BYTES_PER_PAGE)) {}
static constexpr u64 BASE_PAGE_BITS = 16;
static constexpr u64 BASE_PAGE_SIZE = 1ULL << BASE_PAGE_BITS;
explicit BufferBase(RasterizerInterface& rasterizer_, VAddr cpu_addr_, u64 size_bytes_)
: cpu_addr{cpu_addr_}, size_bytes{size_bytes_} {}
explicit BufferBase(NullBufferParams) {}
@@ -155,100 +49,6 @@ public:
BufferBase& operator=(BufferBase&&) = default;
BufferBase(BufferBase&&) = default;
/// Returns the inclusive CPU modified range in a begin end pair
[[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr,
u64 query_size) const noexcept {
const u64 offset = query_cpu_addr - cpu_addr;
return ModifiedRegion<Type::CPU>(offset, query_size);
}
/// Returns the inclusive GPU modified range in a begin end pair
[[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr,
u64 query_size) const noexcept {
const u64 offset = query_cpu_addr - cpu_addr;
return ModifiedRegion<Type::GPU>(offset, query_size);
}
/// Returns true if a region has been modified from the CPU
[[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) const noexcept {
const u64 offset = query_cpu_addr - cpu_addr;
return IsRegionModified<Type::CPU>(offset, query_size);
}
/// Returns true if a region has been modified from the GPU
[[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) const noexcept {
const u64 offset = query_cpu_addr - cpu_addr;
return IsRegionModified<Type::GPU>(offset, query_size);
}
/// Mark region as CPU modified, notifying the rasterizer about this change
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 size) {
ChangeRegionState<Type::CPU, true>(dirty_cpu_addr, size);
}
/// Unmark region as CPU modified, notifying the rasterizer about this change
void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 size) {
ChangeRegionState<Type::CPU, false>(dirty_cpu_addr, size);
}
/// Mark region as modified from the host GPU
void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 size) noexcept {
ChangeRegionState<Type::GPU, true>(dirty_cpu_addr, size);
}
/// Unmark region as modified from the host GPU
void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 size) noexcept {
ChangeRegionState<Type::GPU, false>(dirty_cpu_addr, size);
}
/// Mark region as modified from the CPU
/// but don't mark it as modified until FlusHCachedWrites is called.
void CachedCpuWrite(VAddr dirty_cpu_addr, u64 size) {
flags |= BufferFlagBits::CachedWrites;
ChangeRegionState<Type::CachedCPU, true>(dirty_cpu_addr, size);
}
/// Flushes cached CPU writes, and notify the rasterizer about the deltas
void FlushCachedWrites() noexcept {
flags &= ~BufferFlagBits::CachedWrites;
const u64 num_words = NumWords();
u64* const cached_words = Array<Type::CachedCPU>();
u64* const untracked_words = Array<Type::Untracked>();
u64* const cpu_words = Array<Type::CPU>();
for (u64 word_index = 0; word_index < num_words; ++word_index) {
const u64 cached_bits = cached_words[word_index];
NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits);
untracked_words[word_index] |= cached_bits;
cpu_words[word_index] |= cached_bits;
if (!Settings::values.use_pessimistic_flushes) {
cached_words[word_index] = 0;
}
}
}
/// Call 'func' for each CPU modified range and unmark those pages as CPU modified
template <typename Func>
void ForEachUploadRange(VAddr query_cpu_range, u64 size, Func&& func) {
ForEachModifiedRange<Type::CPU>(query_cpu_range, size, true, func);
}
/// Call 'func' for each GPU modified range and unmark those pages as GPU modified
template <typename Func>
void ForEachDownloadRange(VAddr query_cpu_range, u64 size, bool clear, Func&& func) {
ForEachModifiedRange<Type::GPU>(query_cpu_range, size, clear, func);
}
template <typename Func>
void ForEachDownloadRangeAndClear(VAddr query_cpu_range, u64 size, Func&& func) {
ForEachModifiedRange<Type::GPU>(query_cpu_range, size, true, func);
}
/// Call 'func' for each GPU modified range and unmark those pages as GPU modified
template <typename Func>
void ForEachDownloadRange(Func&& func) {
ForEachModifiedRange<Type::GPU>(cpu_addr, SizeBytes(), true, func);
}
/// Mark buffer as picked
void Pick() noexcept {
flags |= BufferFlagBits::Picked;
@@ -295,11 +95,6 @@ public:
return static_cast<u32>(other_cpu_addr - cpu_addr);
}
/// Returns the size in bytes of the buffer
[[nodiscard]] u64 SizeBytes() const noexcept {
return words.size_bytes;
}
size_t getLRUID() const noexcept {
return lru_id;
}
@@ -308,305 +103,16 @@ public:
lru_id = lru_id_;
}
size_t SizeBytes() const {
return size_bytes;
}
private:
template <Type type>
u64* Array() noexcept {
if constexpr (type == Type::CPU) {
return words.cpu.Pointer(IsShort());
} else if constexpr (type == Type::GPU) {
return words.gpu.Pointer(IsShort());
} else if constexpr (type == Type::CachedCPU) {
return words.cached_cpu.Pointer(IsShort());
} else if constexpr (type == Type::Untracked) {
return words.untracked.Pointer(IsShort());
}
}
template <Type type>
const u64* Array() const noexcept {
if constexpr (type == Type::CPU) {
return words.cpu.Pointer(IsShort());
} else if constexpr (type == Type::GPU) {
return words.gpu.Pointer(IsShort());
} else if constexpr (type == Type::CachedCPU) {
return words.cached_cpu.Pointer(IsShort());
} else if constexpr (type == Type::Untracked) {
return words.untracked.Pointer(IsShort());
}
}
/**
* Change the state of a range of pages
*
* @param dirty_addr Base address to mark or unmark as modified
* @param size Size in bytes to mark or unmark as modified
*/
template <Type type, bool enable>
void ChangeRegionState(u64 dirty_addr, s64 size) noexcept(type == Type::GPU) {
const s64 difference = dirty_addr - cpu_addr;
const u64 offset = std::max<s64>(difference, 0);
size += std::min<s64>(difference, 0);
if (offset >= SizeBytes() || size < 0) {
return;
}
u64* const untracked_words = Array<Type::Untracked>();
u64* const state_words = Array<type>();
const u64 offset_end = std::min(offset + size, SizeBytes());
const u64 begin_page_index = offset / BYTES_PER_PAGE;
const u64 begin_word_index = begin_page_index / PAGES_PER_WORD;
const u64 end_page_index = Common::DivCeil(offset_end, BYTES_PER_PAGE);
const u64 end_word_index = Common::DivCeil(end_page_index, PAGES_PER_WORD);
u64 page_index = begin_page_index % PAGES_PER_WORD;
u64 word_index = begin_word_index;
while (word_index < end_word_index) {
const u64 next_word_first_page = (word_index + 1) * PAGES_PER_WORD;
const u64 left_offset =
std::min(next_word_first_page - end_page_index, PAGES_PER_WORD) % PAGES_PER_WORD;
const u64 right_offset = page_index;
u64 bits = ~u64{0};
bits = (bits >> right_offset) << right_offset;
bits = (bits << left_offset) >> left_offset;
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
NotifyRasterizer<!enable>(word_index, untracked_words[word_index], bits);
}
if constexpr (enable) {
state_words[word_index] |= bits;
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
untracked_words[word_index] |= bits;
}
} else {
state_words[word_index] &= ~bits;
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
untracked_words[word_index] &= ~bits;
}
}
page_index = 0;
++word_index;
}
}
/**
* Notify rasterizer about changes in the CPU tracking state of a word in the buffer
*
* @param word_index Index to the word to notify to the rasterizer
* @param current_bits Current state of the word
* @param new_bits New state of the word
*
* @tparam add_to_rasterizer True when the rasterizer should start tracking the new pages
*/
template <bool add_to_rasterizer>
void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const {
u64 changed_bits = (add_to_rasterizer ? current_bits : ~current_bits) & new_bits;
VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
while (changed_bits != 0) {
const int empty_bits = std::countr_zero(changed_bits);
addr += empty_bits * BYTES_PER_PAGE;
changed_bits >>= empty_bits;
const u32 continuous_bits = std::countr_one(changed_bits);
const u64 size = continuous_bits * BYTES_PER_PAGE;
const VAddr begin_addr = addr;
addr += size;
changed_bits = continuous_bits < PAGES_PER_WORD ? (changed_bits >> continuous_bits) : 0;
rasterizer->UpdatePagesCachedCount(begin_addr, size, add_to_rasterizer ? 1 : -1);
}
}
/**
* Loop over each page in the given range, turn off those bits and notify the rasterizer if
* needed. Call the given function on each turned off range.
*
* @param query_cpu_range Base CPU address to loop over
* @param size Size in bytes of the CPU range to loop over
* @param func Function to call for each turned off region
*/
template <Type type, typename Func>
void ForEachModifiedRange(VAddr query_cpu_range, s64 size, bool clear, Func&& func) {
static_assert(type != Type::Untracked);
const s64 difference = query_cpu_range - cpu_addr;
const u64 query_begin = std::max<s64>(difference, 0);
size += std::min<s64>(difference, 0);
if (query_begin >= SizeBytes() || size < 0) {
return;
}
u64* const untracked_words = Array<Type::Untracked>();
u64* const state_words = Array<type>();
const u64 query_end = query_begin + std::min(static_cast<u64>(size), SizeBytes());
u64* const words_begin = state_words + query_begin / BYTES_PER_WORD;
u64* const words_end = state_words + Common::DivCeil(query_end, BYTES_PER_WORD);
const auto modified = [](u64 word) { return word != 0; };
const auto first_modified_word = std::find_if(words_begin, words_end, modified);
if (first_modified_word == words_end) {
// Exit early when the buffer is not modified
return;
}
const auto last_modified_word = std::find_if_not(first_modified_word, words_end, modified);
const u64 word_index_begin = std::distance(state_words, first_modified_word);
const u64 word_index_end = std::distance(state_words, last_modified_word);
const unsigned local_page_begin = std::countr_zero(*first_modified_word);
const unsigned local_page_end =
static_cast<unsigned>(PAGES_PER_WORD) - std::countl_zero(last_modified_word[-1]);
const u64 word_page_begin = word_index_begin * PAGES_PER_WORD;
const u64 word_page_end = (word_index_end - 1) * PAGES_PER_WORD;
const u64 query_page_begin = query_begin / BYTES_PER_PAGE;
const u64 query_page_end = Common::DivCeil(query_end, BYTES_PER_PAGE);
const u64 page_index_begin = std::max(word_page_begin + local_page_begin, query_page_begin);
const u64 page_index_end = std::min(word_page_end + local_page_end, query_page_end);
const u64 first_word_page_begin = page_index_begin % PAGES_PER_WORD;
const u64 last_word_page_end = (page_index_end - 1) % PAGES_PER_WORD + 1;
u64 page_begin = first_word_page_begin;
u64 current_base = 0;
u64 current_size = 0;
bool on_going = false;
for (u64 word_index = word_index_begin; word_index < word_index_end; ++word_index) {
const bool is_last_word = word_index + 1 == word_index_end;
const u64 page_end = is_last_word ? last_word_page_end : PAGES_PER_WORD;
const u64 right_offset = page_begin;
const u64 left_offset = PAGES_PER_WORD - page_end;
u64 bits = ~u64{0};
bits = (bits >> right_offset) << right_offset;
bits = (bits << left_offset) >> left_offset;
const u64 current_word = state_words[word_index] & bits;
if (clear) {
state_words[word_index] &= ~bits;
}
if constexpr (type == Type::CPU) {
const u64 current_bits = untracked_words[word_index] & bits;
untracked_words[word_index] &= ~bits;
NotifyRasterizer<true>(word_index, current_bits, ~u64{0});
}
// Exclude CPU modified pages when visiting GPU pages
const u64 word = current_word & ~(type == Type::GPU ? untracked_words[word_index] : 0);
u64 page = page_begin;
page_begin = 0;
while (page < page_end) {
const int empty_bits = std::countr_zero(word >> page);
if (on_going && empty_bits != 0) {
InvokeModifiedRange(func, current_size, current_base);
current_size = 0;
on_going = false;
}
if (empty_bits == PAGES_PER_WORD) {
break;
}
page += empty_bits;
const int continuous_bits = std::countr_one(word >> page);
if (!on_going && continuous_bits != 0) {
current_base = word_index * PAGES_PER_WORD + page;
on_going = true;
}
current_size += continuous_bits;
page += continuous_bits;
}
}
if (on_going && current_size > 0) {
InvokeModifiedRange(func, current_size, current_base);
}
}
template <typename Func>
void InvokeModifiedRange(Func&& func, u64 current_size, u64 current_base) {
const u64 current_size_bytes = current_size * BYTES_PER_PAGE;
const u64 offset_begin = current_base * BYTES_PER_PAGE;
const u64 offset_end = std::min(offset_begin + current_size_bytes, SizeBytes());
func(offset_begin, offset_end - offset_begin);
}
/**
* Returns true when a region has been modified
*
* @param offset Offset in bytes from the start of the buffer
* @param size Size in bytes of the region to query for modifications
*/
template <Type type>
[[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
static_assert(type != Type::Untracked);
const u64* const untracked_words = Array<Type::Untracked>();
const u64* const state_words = Array<type>();
const u64 num_query_words = size / BYTES_PER_WORD + 1;
const u64 word_begin = offset / BYTES_PER_WORD;
const u64 word_end = std::min<u64>(word_begin + num_query_words, NumWords());
const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE);
u64 page_index = (offset / BYTES_PER_PAGE) % PAGES_PER_WORD;
for (u64 word_index = word_begin; word_index < word_end; ++word_index, page_index = 0) {
const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0;
const u64 word = state_words[word_index] & ~off_word;
if (word == 0) {
continue;
}
const u64 page_end = std::min((word_index + 1) * PAGES_PER_WORD, page_limit);
const u64 local_page_end = page_end % PAGES_PER_WORD;
const u64 page_end_shift = (PAGES_PER_WORD - local_page_end) % PAGES_PER_WORD;
if (((word >> page_index) << page_index) << page_end_shift != 0) {
return true;
}
}
return false;
}
/**
* Returns a begin end pair with the inclusive modified region
*
* @param offset Offset in bytes from the start of the buffer
* @param size Size in bytes of the region to query for modifications
*/
template <Type type>
[[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept {
static_assert(type != Type::Untracked);
const u64* const untracked_words = Array<Type::Untracked>();
const u64* const state_words = Array<type>();
const u64 num_query_words = size / BYTES_PER_WORD + 1;
const u64 word_begin = offset / BYTES_PER_WORD;
const u64 word_end = std::min<u64>(word_begin + num_query_words, NumWords());
const u64 page_base = offset / BYTES_PER_PAGE;
const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE);
u64 begin = std::numeric_limits<u64>::max();
u64 end = 0;
for (u64 word_index = word_begin; word_index < word_end; ++word_index) {
const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0;
const u64 word = state_words[word_index] & ~off_word;
if (word == 0) {
continue;
}
const u64 local_page_begin = std::countr_zero(word);
const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
const u64 page_index = word_index * PAGES_PER_WORD;
const u64 page_begin = std::max(page_index + local_page_begin, page_base);
const u64 page_end = std::min(page_index + local_page_end, page_limit);
begin = std::min(begin, page_begin);
end = std::max(end, page_end);
}
static constexpr std::pair<u64, u64> EMPTY{0, 0};
return begin < end ? std::make_pair(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE) : EMPTY;
}
/// Returns the number of words of the buffer
[[nodiscard]] size_t NumWords() const noexcept {
return words.NumWords();
}
/// Returns true when the buffer fits in the small vector optimization
[[nodiscard]] bool IsShort() const noexcept {
return words.IsShort();
}
RasterizerInterface* rasterizer = nullptr;
VAddr cpu_addr = 0;
Words words;
BufferFlagBits flags{};
int stream_score = 0;
size_t lru_id = SIZE_MAX;
size_t size_bytes = 0;
};
} // namespace VideoCommon
+2 -2
View File
@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "common/microprofile.h"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,580 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <algorithm>
#include <array>
#include <functional>
#include <memory>
#include <mutex>
#include <numeric>
#include <span>
#include <unordered_map>
#include <vector>
#include <boost/container/small_vector.hpp>
#define BOOST_NO_MT
#include <boost/pool/detail/mutex.hpp>
#undef BOOST_NO_MT
#include <boost/icl/interval.hpp>
#include <boost/icl/interval_base_set.hpp>
#include <boost/icl/interval_set.hpp>
#include <boost/icl/split_interval_map.hpp>
#include <boost/pool/pool.hpp>
#include <boost/pool/pool_alloc.hpp>
#include <boost/pool/poolfwd.hpp>
#include "common/common_types.h"
#include "common/div_ceil.h"
#include "common/literals.h"
#include "common/lru_cache.h"
#include "common/microprofile.h"
#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/memory.h"
#include "video_core/buffer_cache/buffer_base.h"
#include "video_core/control/channel_state_cache.h"
#include "video_core/delayed_destruction_ring.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/draw_manager.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/slot_vector.h"
#include "video_core/texture_cache/types.h"
namespace boost {
template <typename T>
class fast_pool_allocator<T, default_user_allocator_new_delete, details::pool::null_mutex, 4096, 0>;
}
namespace VideoCommon {
MICROPROFILE_DECLARE(GPU_PrepareBuffers);
MICROPROFILE_DECLARE(GPU_BindUploadBuffers);
MICROPROFILE_DECLARE(GPU_DownloadMemory);
using BufferId = SlotId;
using VideoCore::Surface::PixelFormat;
using namespace Common::Literals;
constexpr u32 NUM_VERTEX_BUFFERS = 32;
constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4;
constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18;
constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8;
constexpr u32 NUM_STORAGE_BUFFERS = 16;
constexpr u32 NUM_TEXTURE_BUFFERS = 16;
constexpr u32 NUM_STAGES = 5;
using UniformBufferSizes = std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES>;
using ComputeUniformBufferSizes = std::array<u32, NUM_COMPUTE_UNIFORM_BUFFERS>;
enum class ObtainBufferSynchronize : u32 {
NoSynchronize = 0,
FullSynchronize = 1,
SynchronizeNoDirty = 2,
};
enum class ObtainBufferOperation : u32 {
DoNothing = 0,
MarkAsWritten = 1,
DiscardWrite = 2,
MarkQuery = 3,
};
template <typename P>
class BufferCache : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
// Page size for caching purposes.
// This is unrelated to the CPU page size and it can be changed as it seems optimal.
static constexpr u32 CACHING_PAGEBITS = 16;
static constexpr u64 CACHING_PAGESIZE = u64{1} << CACHING_PAGEBITS;
static constexpr bool IS_OPENGL = P::IS_OPENGL;
static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS =
P::HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS;
static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT =
P::HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT;
static constexpr bool NEEDS_BIND_UNIFORM_INDEX = P::NEEDS_BIND_UNIFORM_INDEX;
static constexpr bool NEEDS_BIND_STORAGE_INDEX = P::NEEDS_BIND_STORAGE_INDEX;
static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = P::IMPLEMENTS_ASYNC_DOWNLOADS;
static constexpr BufferId NULL_BUFFER_ID{0};
static constexpr s64 DEFAULT_EXPECTED_MEMORY = 512_MiB;
static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB;
static constexpr s64 TARGET_THRESHOLD = 4_GiB;
// Debug Flags.
static constexpr bool DISABLE_DOWNLOADS = true;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
using Runtime = typename P::Runtime;
using Buffer = typename P::Buffer;
using Async_Buffer = typename P::Async_Buffer;
using MemoryTracker = typename P::MemoryTracker;
using IntervalCompare = std::less<VAddr>;
using IntervalInstance = boost::icl::interval_type_default<VAddr, std::less>;
using IntervalAllocator = boost::fast_pool_allocator<VAddr>;
using IntervalSet = boost::icl::interval_set<VAddr>;
using IntervalType = typename IntervalSet::interval_type;
template <typename Type>
struct counter_add_functor : public boost::icl::identity_based_inplace_combine<Type> {
// types
typedef counter_add_functor<Type> type;
typedef boost::icl::identity_based_inplace_combine<Type> base_type;
// public member functions
void operator()(Type& current, const Type& added) const {
current += added;
if (current < base_type::identity_element()) {
current = base_type::identity_element();
}
}
// public static functions
static void version(Type&){};
};
using OverlapCombine = counter_add_functor<int>;
using OverlapSection = boost::icl::inter_section<int>;
using OverlapCounter = boost::icl::split_interval_map<VAddr, int>;
struct Empty {};
struct OverlapResult {
std::vector<BufferId> ids;
VAddr begin;
VAddr end;
bool has_stream_leap = false;
};
struct Binding {
VAddr cpu_addr{};
u32 size{};
BufferId buffer_id;
};
struct TextureBufferBinding : Binding {
PixelFormat format;
};
static constexpr Binding NULL_BINDING{
.cpu_addr = 0,
.size = 0,
.buffer_id = NULL_BUFFER_ID,
};
public:
static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
Core::Memory::Memory& cpu_memory_, Runtime& runtime_);
void TickFrame();
void WriteMemory(VAddr cpu_addr, u64 size);
void CachedWriteMemory(VAddr cpu_addr, u64 size);
void DownloadMemory(VAddr cpu_addr, u64 size);
bool InlineMemory(VAddr dest_address, size_t copy_size, std::span<const u8> inlined_buffer);
void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size);
void DisableGraphicsUniformBuffer(size_t stage, u32 index);
void UpdateGraphicsBuffers(bool is_indexed);
void UpdateComputeBuffers();
void BindHostGeometryBuffers(bool is_indexed);
void BindHostStageBuffers(size_t stage);
void BindHostComputeBuffers();
void SetUniformBuffersState(const std::array<u32, NUM_STAGES>& mask,
const UniformBufferSizes* sizes);
void SetComputeUniformBufferState(u32 mask, const ComputeUniformBufferSizes* sizes);
void UnbindGraphicsStorageBuffers(size_t stage);
void BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
bool is_written);
void UnbindGraphicsTextureBuffers(size_t stage);
void BindGraphicsTextureBuffer(size_t stage, size_t tbo_index, GPUVAddr gpu_addr, u32 size,
PixelFormat format, bool is_written, bool is_image);
void UnbindComputeStorageBuffers();
void BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
bool is_written);
void UnbindComputeTextureBuffers();
void BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_addr, u32 size, PixelFormat format,
bool is_written, bool is_image);
[[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(GPUVAddr gpu_addr, u32 size,
ObtainBufferSynchronize sync_info,
ObtainBufferOperation post_op);
void FlushCachedWrites();
/// Return true when there are uncommitted buffers to be downloaded
[[nodiscard]] bool HasUncommittedFlushes() const noexcept;
void AccumulateFlushes();
/// Return true when the caller should wait for async downloads
[[nodiscard]] bool ShouldWaitAsyncFlushes() const noexcept;
/// Commit asynchronous downloads
void CommitAsyncFlushes();
void CommitAsyncFlushesHigh();
/// Pop asynchronous downloads
void PopAsyncFlushes();
void PopAsyncBuffers();
bool DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount);
bool DMAClear(GPUVAddr src_address, u64 amount, u32 value);
/// Return true when a CPU region is modified from the GPU
[[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size);
/// Return true when a region is registered on the cache
[[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size);
/// Return true when a CPU region is modified from the CPU
[[nodiscard]] bool IsRegionCpuModified(VAddr addr, size_t size);
void SetDrawIndirect(
const Tegra::Engines::DrawManager::IndirectParams* current_draw_indirect_) {
current_draw_indirect = current_draw_indirect_;
}
[[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectCount();
[[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer();
std::recursive_mutex mutex;
Runtime& runtime;
private:
template <typename Func>
static void ForEachEnabledBit(u32 enabled_mask, Func&& func) {
for (u32 index = 0; enabled_mask != 0; ++index, enabled_mask >>= 1) {
const int disabled_bits = std::countr_zero(enabled_mask);
index += disabled_bits;
enabled_mask >>= disabled_bits;
func(index);
}
}
template <typename Func>
void ForEachBufferInRange(VAddr cpu_addr, u64 size, Func&& func) {
const u64 page_end = Common::DivCeil(cpu_addr + size, CACHING_PAGESIZE);
for (u64 page = cpu_addr >> CACHING_PAGEBITS; page < page_end;) {
const BufferId buffer_id = page_table[page];
if (!buffer_id) {
++page;
continue;
}
Buffer& buffer = slot_buffers[buffer_id];
func(buffer_id, buffer);
const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes();
page = Common::DivCeil(end_addr, CACHING_PAGESIZE);
}
}
template <typename Func>
void ForEachInRangeSet(IntervalSet& current_range, VAddr cpu_addr, u64 size, Func&& func) {
const VAddr start_address = cpu_addr;
const VAddr end_address = start_address + size;
const IntervalType search_interval{start_address, end_address};
auto it = current_range.lower_bound(search_interval);
if (it == current_range.end()) {
return;
}
auto end_it = current_range.upper_bound(search_interval);
for (; it != end_it; it++) {
VAddr inter_addr_end = it->upper();
VAddr inter_addr = it->lower();
if (inter_addr_end > end_address) {
inter_addr_end = end_address;
}
if (inter_addr < start_address) {
inter_addr = start_address;
}
func(inter_addr, inter_addr_end);
}
}
template <typename Func>
void ForEachInOverlapCounter(OverlapCounter& current_range, VAddr cpu_addr, u64 size,
Func&& func) {
const VAddr start_address = cpu_addr;
const VAddr end_address = start_address + size;
const IntervalType search_interval{start_address, end_address};
auto it = current_range.lower_bound(search_interval);
if (it == current_range.end()) {
return;
}
auto end_it = current_range.upper_bound(search_interval);
for (; it != end_it; it++) {
auto& inter = it->first;
VAddr inter_addr_end = inter.upper();
VAddr inter_addr = inter.lower();
if (inter_addr_end > end_address) {
inter_addr_end = end_address;
}
if (inter_addr < start_address) {
inter_addr = start_address;
}
func(inter_addr, inter_addr_end, it->second);
}
}
void RemoveEachInOverlapCounter(OverlapCounter& current_range,
const IntervalType search_interval, int subtract_value) {
bool any_removals = false;
current_range.add(std::make_pair(search_interval, subtract_value));
do {
any_removals = false;
auto it = current_range.lower_bound(search_interval);
if (it == current_range.end()) {
return;
}
auto end_it = current_range.upper_bound(search_interval);
for (; it != end_it; it++) {
if (it->second <= 0) {
any_removals = true;
current_range.erase(it);
break;
}
}
} while (any_removals);
}
static bool IsRangeGranular(VAddr cpu_addr, size_t size) {
return (cpu_addr & ~Core::Memory::YUZU_PAGEMASK) ==
((cpu_addr + size) & ~Core::Memory::YUZU_PAGEMASK);
}
void RunGarbageCollector();
void WaitOnAsyncFlushes(VAddr cpu_addr, u64 size);
void BindHostIndexBuffer();
void BindHostVertexBuffers();
void BindHostDrawIndirectBuffers();
void BindHostGraphicsUniformBuffers(size_t stage);
void BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index, bool needs_bind);
void BindHostGraphicsStorageBuffers(size_t stage);
void BindHostGraphicsTextureBuffers(size_t stage);
void BindHostTransformFeedbackBuffers();
void BindHostComputeUniformBuffers();
void BindHostComputeStorageBuffers();
void BindHostComputeTextureBuffers();
void DoUpdateGraphicsBuffers(bool is_indexed);
void DoUpdateComputeBuffers();
void UpdateIndexBuffer();
void UpdateVertexBuffers();
void UpdateVertexBuffer(u32 index);
void UpdateDrawIndirect();
void UpdateUniformBuffers(size_t stage);
void UpdateStorageBuffers(size_t stage);
void UpdateTextureBuffers(size_t stage);
void UpdateTransformFeedbackBuffers();
void UpdateTransformFeedbackBuffer(u32 index);
void UpdateComputeUniformBuffers();
void UpdateComputeStorageBuffers();
void UpdateComputeTextureBuffers();
void MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size);
[[nodiscard]] BufferId FindBuffer(VAddr cpu_addr, u32 size);
[[nodiscard]] OverlapResult ResolveOverlaps(VAddr cpu_addr, u32 wanted_size);
void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score);
[[nodiscard]] BufferId CreateBuffer(VAddr cpu_addr, u32 wanted_size);
void Register(BufferId buffer_id);
void Unregister(BufferId buffer_id);
template <bool insert>
void ChangeRegister(BufferId buffer_id);
void TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept;
bool SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size);
bool SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size);
bool SynchronizeBufferNoModified(Buffer& buffer, VAddr cpu_addr, u32 size);
void UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy,
std::span<BufferCopy> copies);
void ImmediateUploadMemory(Buffer& buffer, u64 largest_copy,
std::span<const BufferCopy> copies);
void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
void DownloadBufferMemory(Buffer& buffer_id);
void DownloadBufferMemory(Buffer& buffer_id, VAddr cpu_addr, u64 size);
void DeleteBuffer(BufferId buffer_id, bool do_not_mark = false);
void NotifyBufferDeletion();
[[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
bool is_written) const;
[[nodiscard]] TextureBufferBinding GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size,
PixelFormat format);
[[nodiscard]] std::span<const u8> ImmediateBufferWithData(VAddr cpu_addr, size_t size);
[[nodiscard]] std::span<u8> ImmediateBuffer(size_t wanted_capacity);
[[nodiscard]] bool HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept;
void ClearDownload(IntervalType subtract_interval);
VideoCore::RasterizerInterface& rasterizer;
Core::Memory::Memory& cpu_memory;
SlotVector<Buffer> slot_buffers;
DelayedDestructionRing<Buffer, 8> delayed_destruction_ring;
const Tegra::Engines::DrawManager::IndirectParams* current_draw_indirect{};
u32 last_index_count = 0;
Binding index_buffer;
std::array<Binding, NUM_VERTEX_BUFFERS> vertex_buffers;
std::array<std::array<Binding, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES> uniform_buffers;
std::array<std::array<Binding, NUM_STORAGE_BUFFERS>, NUM_STAGES> storage_buffers;
std::array<std::array<TextureBufferBinding, NUM_TEXTURE_BUFFERS>, NUM_STAGES> texture_buffers;
std::array<Binding, NUM_TRANSFORM_FEEDBACK_BUFFERS> transform_feedback_buffers;
Binding count_buffer_binding;
Binding indirect_buffer_binding;
std::array<Binding, NUM_COMPUTE_UNIFORM_BUFFERS> compute_uniform_buffers;
std::array<Binding, NUM_STORAGE_BUFFERS> compute_storage_buffers;
std::array<TextureBufferBinding, NUM_TEXTURE_BUFFERS> compute_texture_buffers;
std::array<u32, NUM_STAGES> enabled_uniform_buffer_masks{};
u32 enabled_compute_uniform_buffer_mask = 0;
const UniformBufferSizes* uniform_buffer_sizes{};
const ComputeUniformBufferSizes* compute_uniform_buffer_sizes{};
std::array<u32, NUM_STAGES> enabled_storage_buffers{};
std::array<u32, NUM_STAGES> written_storage_buffers{};
u32 enabled_compute_storage_buffers = 0;
u32 written_compute_storage_buffers = 0;
std::array<u32, NUM_STAGES> enabled_texture_buffers{};
std::array<u32, NUM_STAGES> written_texture_buffers{};
std::array<u32, NUM_STAGES> image_texture_buffers{};
u32 enabled_compute_texture_buffers = 0;
u32 written_compute_texture_buffers = 0;
u32 image_compute_texture_buffers = 0;
std::array<u32, 16> uniform_cache_hits{};
std::array<u32, 16> uniform_cache_shots{};
u32 uniform_buffer_skip_cache_size = DEFAULT_SKIP_CACHE_SIZE;
bool has_deleted_buffers = false;
std::conditional_t<HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS, std::array<u32, NUM_STAGES>, Empty>
dirty_uniform_buffers{};
std::conditional_t<IS_OPENGL, std::array<u32, NUM_STAGES>, Empty> fast_bound_uniform_buffers{};
std::conditional_t<HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS,
std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES>, Empty>
uniform_buffer_binding_sizes{};
std::vector<BufferId> cached_write_buffer_ids;
MemoryTracker memory_tracker;
IntervalSet uncommitted_ranges;
IntervalSet common_ranges;
IntervalSet cached_ranges;
IntervalSet pending_ranges;
std::deque<IntervalSet> committed_ranges;
// Async Buffers
OverlapCounter async_downloads;
std::deque<std::optional<Async_Buffer>> async_buffers;
std::deque<boost::container::small_vector<BufferCopy, 4>> pending_downloads;
std::optional<Async_Buffer> current_buffer;
std::deque<Async_Buffer> async_buffers_death_ring;
size_t immediate_buffer_capacity = 0;
Common::ScratchBuffer<u8> immediate_buffer_alloc;
struct LRUItemParams {
using ObjectType = BufferId;
using TickType = u64;
};
Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache;
u64 frame_tick = 0;
u64 total_used_memory = 0;
u64 minimum_memory = 0;
u64 critical_memory = 0;
BufferId inline_buffer_id;
bool active_async_buffers = false;
std::array<BufferId, ((1ULL << 39) >> CACHING_PAGEBITS)> page_table;
};
} // namespace VideoCommon
@@ -0,0 +1,273 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <algorithm>
#include <bit>
#include <deque>
#include <limits>
#include <type_traits>
#include <unordered_set>
#include <utility>
#include "common/alignment.h"
#include "common/common_types.h"
#include "video_core/buffer_cache/word_manager.h"
namespace VideoCommon {
template <class RasterizerInterface>
class MemoryTrackerBase {
static constexpr size_t MAX_CPU_PAGE_BITS = 39;
static constexpr size_t HIGHER_PAGE_BITS = 22;
static constexpr size_t HIGHER_PAGE_SIZE = 1ULL << HIGHER_PAGE_BITS;
static constexpr size_t HIGHER_PAGE_MASK = HIGHER_PAGE_SIZE - 1ULL;
static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - HIGHER_PAGE_BITS);
static constexpr size_t MANAGER_POOL_SIZE = 32;
static constexpr size_t WORDS_STACK_NEEDED = HIGHER_PAGE_SIZE / BYTES_PER_WORD;
using Manager = WordManager<RasterizerInterface, WORDS_STACK_NEEDED>;
public:
MemoryTrackerBase(RasterizerInterface& rasterizer_) : rasterizer{&rasterizer_} {}
~MemoryTrackerBase() = default;
/// Returns the inclusive CPU modified range in a begin end pair
[[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr,
u64 query_size) noexcept {
return IteratePairs<true>(
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->template ModifiedRegion<Type::CPU>(offset, size);
});
}
/// Returns the inclusive GPU modified range in a begin end pair
[[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr,
u64 query_size) noexcept {
return IteratePairs<false>(
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->template ModifiedRegion<Type::GPU>(offset, size);
});
}
/// Returns true if a region has been modified from the CPU
[[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
return IteratePages<true>(
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->template IsRegionModified<Type::CPU>(offset, size);
});
}
/// Returns true if a region has been modified from the GPU
[[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
return IteratePages<false>(
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->template IsRegionModified<Type::GPU>(offset, size);
});
}
/// Mark region as CPU modified, notifying the rasterizer about this change
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::CPU, true>(
manager->GetCpuAddr() + offset, size);
});
}
/// Unmark region as CPU modified, notifying the rasterizer about this change
void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::CPU, false>(
manager->GetCpuAddr() + offset, size);
});
}
/// Mark region as modified from the host GPU
void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::GPU, true>(
manager->GetCpuAddr() + offset, size);
});
}
/// Unmark region as modified from the host GPU
void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::GPU, false>(
manager->GetCpuAddr() + offset, size);
});
}
/// Mark region as modified from the CPU
/// but don't mark it as modified until FlusHCachedWrites is called.
void CachedCpuWrite(VAddr dirty_cpu_addr, u64 query_size) {
IteratePages<true>(
dirty_cpu_addr, query_size, [this](Manager* manager, u64 offset, size_t size) {
const VAddr cpu_address = manager->GetCpuAddr() + offset;
manager->template ChangeRegionState<Type::CachedCPU, true>(cpu_address, size);
cached_pages.insert(static_cast<u32>(cpu_address >> HIGHER_PAGE_BITS));
});
}
/// Flushes cached CPU writes, and notify the rasterizer about the deltas
void FlushCachedWrites(VAddr query_cpu_addr, u64 query_size) noexcept {
IteratePages<false>(query_cpu_addr, query_size,
[](Manager* manager, [[maybe_unused]] u64 offset,
[[maybe_unused]] size_t size) { manager->FlushCachedWrites(); });
}
void FlushCachedWrites() noexcept {
for (auto id : cached_pages) {
top_tier[id]->FlushCachedWrites();
}
cached_pages.clear();
}
/// Call 'func' for each CPU modified range and unmark those pages as CPU modified
template <typename Func>
void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, Func&& func) {
IteratePages<true>(query_cpu_range, query_size,
[&func](Manager* manager, u64 offset, size_t size) {
manager->template ForEachModifiedRange<Type::CPU, true>(
manager->GetCpuAddr() + offset, size, func);
});
}
/// Call 'func' for each GPU modified range and unmark those pages as GPU modified
template <typename Func>
void ForEachDownloadRange(VAddr query_cpu_range, u64 query_size, bool clear, Func&& func) {
IteratePages<false>(query_cpu_range, query_size,
[&func, clear](Manager* manager, u64 offset, size_t size) {
if (clear) {
manager->template ForEachModifiedRange<Type::GPU, true>(
manager->GetCpuAddr() + offset, size, func);
} else {
manager->template ForEachModifiedRange<Type::GPU, false>(
manager->GetCpuAddr() + offset, size, func);
}
});
}
template <typename Func>
void ForEachDownloadRangeAndClear(VAddr query_cpu_range, u64 query_size, Func&& func) {
IteratePages<false>(query_cpu_range, query_size,
[&func](Manager* manager, u64 offset, size_t size) {
manager->template ForEachModifiedRange<Type::GPU, true>(
manager->GetCpuAddr() + offset, size, func);
});
}
private:
template <bool create_region_on_fail, typename Func>
bool IteratePages(VAddr cpu_address, size_t size, Func&& func) {
using FuncReturn = typename std::invoke_result<Func, Manager*, u64, size_t>::type;
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
std::size_t remaining_size{size};
std::size_t page_index{cpu_address >> HIGHER_PAGE_BITS};
u64 page_offset{cpu_address & HIGHER_PAGE_MASK};
while (remaining_size > 0) {
const std::size_t copy_amount{
std::min<std::size_t>(HIGHER_PAGE_SIZE - page_offset, remaining_size)};
auto* manager{top_tier[page_index]};
if (manager) {
if constexpr (BOOL_BREAK) {
if (func(manager, page_offset, copy_amount)) {
return true;
}
} else {
func(manager, page_offset, copy_amount);
}
} else if constexpr (create_region_on_fail) {
CreateRegion(page_index);
manager = top_tier[page_index];
if constexpr (BOOL_BREAK) {
if (func(manager, page_offset, copy_amount)) {
return true;
}
} else {
func(manager, page_offset, copy_amount);
}
}
page_index++;
page_offset = 0;
remaining_size -= copy_amount;
}
return false;
}
template <bool create_region_on_fail, typename Func>
std::pair<u64, u64> IteratePairs(VAddr cpu_address, size_t size, Func&& func) {
std::size_t remaining_size{size};
std::size_t page_index{cpu_address >> HIGHER_PAGE_BITS};
u64 page_offset{cpu_address & HIGHER_PAGE_MASK};
u64 begin = std::numeric_limits<u64>::max();
u64 end = 0;
while (remaining_size > 0) {
const std::size_t copy_amount{
std::min<std::size_t>(HIGHER_PAGE_SIZE - page_offset, remaining_size)};
auto* manager{top_tier[page_index]};
const auto execute = [&] {
auto [new_begin, new_end] = func(manager, page_offset, copy_amount);
if (new_begin != 0 || new_end != 0) {
const u64 base_address = page_index << HIGHER_PAGE_BITS;
begin = std::min(new_begin + base_address, begin);
end = std::max(new_end + base_address, end);
}
};
if (manager) {
execute();
} else if constexpr (create_region_on_fail) {
CreateRegion(page_index);
manager = top_tier[page_index];
execute();
}
page_index++;
page_offset = 0;
remaining_size -= copy_amount;
}
if (begin < end) {
return std::make_pair(begin, end);
} else {
return std::make_pair(0ULL, 0ULL);
}
}
void CreateRegion(std::size_t page_index) {
const VAddr base_cpu_addr = page_index << HIGHER_PAGE_BITS;
top_tier[page_index] = GetNewManager(base_cpu_addr);
}
Manager* GetNewManager(VAddr base_cpu_addess) {
const auto on_return = [&] {
auto* new_manager = free_managers.front();
new_manager->SetCpuAddress(base_cpu_addess);
free_managers.pop_front();
return new_manager;
};
if (!free_managers.empty()) {
return on_return();
}
manager_pool.emplace_back();
auto& last_pool = manager_pool.back();
for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) {
new (&last_pool[i]) Manager(0, *rasterizer, HIGHER_PAGE_SIZE);
free_managers.push_back(&last_pool[i]);
}
return on_return();
}
std::deque<std::array<Manager, MANAGER_POOL_SIZE>> manager_pool;
std::deque<Manager*> free_managers;
std::array<Manager*, NUM_HIGH_PAGES> top_tier{};
std::unordered_set<u32> cached_pages;
RasterizerInterface* rasterizer = nullptr;
};
} // namespace VideoCommon
+462
View File
@@ -0,0 +1,462 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <algorithm>
#include <bit>
#include <limits>
#include <span>
#include <utility>
#include "common/alignment.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/div_ceil.h"
#include "core/memory.h"
namespace VideoCommon {
constexpr u64 PAGES_PER_WORD = 64;
constexpr u64 BYTES_PER_PAGE = Core::Memory::YUZU_PAGESIZE;
constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE;
enum class Type {
CPU,
GPU,
CachedCPU,
Untracked,
};
/// Vector tracking modified pages tightly packed with small vector optimization
template <size_t stack_words = 1>
struct WordsArray {
/// Returns the pointer to the words state
[[nodiscard]] const u64* Pointer(bool is_short) const noexcept {
return is_short ? stack.data() : heap;
}
/// Returns the pointer to the words state
[[nodiscard]] u64* Pointer(bool is_short) noexcept {
return is_short ? stack.data() : heap;
}
std::array<u64, stack_words> stack{}; ///< Small buffers storage
u64* heap; ///< Not-small buffers pointer to the storage
};
template <size_t stack_words = 1>
struct Words {
explicit Words() = default;
explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} {
num_words = Common::DivCeil(size_bytes, BYTES_PER_WORD);
if (IsShort()) {
cpu.stack.fill(~u64{0});
gpu.stack.fill(0);
cached_cpu.stack.fill(0);
untracked.stack.fill(~u64{0});
} else {
// Share allocation between CPU and GPU pages and set their default values
u64* const alloc = new u64[num_words * 4];
cpu.heap = alloc;
gpu.heap = alloc + num_words;
cached_cpu.heap = alloc + num_words * 2;
untracked.heap = alloc + num_words * 3;
std::fill_n(cpu.heap, num_words, ~u64{0});
std::fill_n(gpu.heap, num_words, 0);
std::fill_n(cached_cpu.heap, num_words, 0);
std::fill_n(untracked.heap, num_words, ~u64{0});
}
// Clean up tailing bits
const u64 last_word_size = size_bytes % BYTES_PER_WORD;
const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
const u64 last_word = (~u64{0} << shift) >> shift;
cpu.Pointer(IsShort())[NumWords() - 1] = last_word;
untracked.Pointer(IsShort())[NumWords() - 1] = last_word;
}
~Words() {
Release();
}
Words& operator=(Words&& rhs) noexcept {
Release();
size_bytes = rhs.size_bytes;
num_words = rhs.num_words;
cpu = rhs.cpu;
gpu = rhs.gpu;
cached_cpu = rhs.cached_cpu;
untracked = rhs.untracked;
rhs.cpu.heap = nullptr;
return *this;
}
Words(Words&& rhs) noexcept
: size_bytes{rhs.size_bytes}, num_words{rhs.num_words}, cpu{rhs.cpu}, gpu{rhs.gpu},
cached_cpu{rhs.cached_cpu}, untracked{rhs.untracked} {
rhs.cpu.heap = nullptr;
}
Words& operator=(const Words&) = delete;
Words(const Words&) = delete;
/// Returns true when the buffer fits in the small vector optimization
[[nodiscard]] bool IsShort() const noexcept {
return num_words <= stack_words;
}
/// Returns the number of words of the buffer
[[nodiscard]] size_t NumWords() const noexcept {
return num_words;
}
/// Release buffer resources
void Release() {
if (!IsShort()) {
// CPU written words is the base for the heap allocation
delete[] cpu.heap;
}
}
template <Type type>
std::span<u64> Span() noexcept {
if constexpr (type == Type::CPU) {
return std::span<u64>(cpu.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::GPU) {
return std::span<u64>(gpu.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::CachedCPU) {
return std::span<u64>(cached_cpu.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::Untracked) {
return std::span<u64>(untracked.Pointer(IsShort()), num_words);
}
}
template <Type type>
std::span<const u64> Span() const noexcept {
if constexpr (type == Type::CPU) {
return std::span<const u64>(cpu.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::GPU) {
return std::span<const u64>(gpu.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::CachedCPU) {
return std::span<const u64>(cached_cpu.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::Untracked) {
return std::span<const u64>(untracked.Pointer(IsShort()), num_words);
}
}
u64 size_bytes = 0;
size_t num_words = 0;
WordsArray<stack_words> cpu;
WordsArray<stack_words> gpu;
WordsArray<stack_words> cached_cpu;
WordsArray<stack_words> untracked;
};
template <class RasterizerInterface, size_t stack_words = 1>
class WordManager {
public:
explicit WordManager(VAddr cpu_addr_, RasterizerInterface& rasterizer_, u64 size_bytes)
: cpu_addr{cpu_addr_}, rasterizer{&rasterizer_}, words{size_bytes} {}
explicit WordManager() = default;
void SetCpuAddress(VAddr new_cpu_addr) {
cpu_addr = new_cpu_addr;
}
VAddr GetCpuAddr() const {
return cpu_addr;
}
static u64 ExtractBits(u64 word, size_t page_start, size_t page_end) {
constexpr size_t number_bits = sizeof(u64) * 8;
const size_t limit_page_end = number_bits - std::min(page_end, number_bits);
u64 bits = (word >> page_start) << page_start;
bits = (bits << limit_page_end) >> limit_page_end;
return bits;
}
static std::pair<size_t, size_t> GetWordPage(VAddr address) {
const size_t converted_address = static_cast<size_t>(address);
const size_t word_number = converted_address / BYTES_PER_WORD;
const size_t amount_pages = converted_address % BYTES_PER_WORD;
return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE);
}
template <typename Func>
void IterateWords(size_t offset, size_t size, Func&& func) const {
using FuncReturn = std::invoke_result_t<Func, std::size_t, u64>;
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
const size_t start = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset), 0LL));
const size_t end = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset + size), 0LL));
if (start >= SizeBytes() || end <= start) {
return;
}
auto [start_word, start_page] = GetWordPage(start);
auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL);
const size_t num_words = NumWords();
start_word = std::min(start_word, num_words);
end_word = std::min(end_word, num_words);
const size_t diff = end_word - start_word;
end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD;
end_word = std::min(end_word, num_words);
end_page += diff * PAGES_PER_WORD;
constexpr u64 base_mask{~0ULL};
for (size_t word_index = start_word; word_index < end_word; word_index++) {
const u64 mask = ExtractBits(base_mask, start_page, end_page);
start_page = 0;
end_page -= PAGES_PER_WORD;
if constexpr (BOOL_BREAK) {
if (func(word_index, mask)) {
return;
}
} else {
func(word_index, mask);
}
}
}
template <typename Func>
void IteratePages(u64 mask, Func&& func) const {
size_t offset = 0;
while (mask != 0) {
const size_t empty_bits = std::countr_zero(mask);
offset += empty_bits;
mask = mask >> empty_bits;
const size_t continuous_bits = std::countr_one(mask);
func(offset, continuous_bits);
mask = continuous_bits < PAGES_PER_WORD ? (mask >> continuous_bits) : 0;
offset += continuous_bits;
}
}
/**
* Change the state of a range of pages
*
* @param dirty_addr Base address to mark or unmark as modified
* @param size Size in bytes to mark or unmark as modified
*/
template <Type type, bool enable>
void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) {
std::span<u64> state_words = words.template Span<type>();
[[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
[[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
NotifyRasterizer<!enable>(index, untracked_words[index], mask);
}
if constexpr (enable) {
state_words[index] |= mask;
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
untracked_words[index] |= mask;
}
if constexpr (type == Type::CPU) {
cached_words[index] &= ~mask;
}
} else {
if constexpr (type == Type::CPU) {
const u64 word = state_words[index] & mask;
cached_words[index] &= ~word;
}
state_words[index] &= ~mask;
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
untracked_words[index] &= ~mask;
}
}
});
}
/**
* Loop over each page in the given range, turn off those bits and notify the rasterizer if
* needed. Call the given function on each turned off range.
*
* @param query_cpu_range Base CPU address to loop over
* @param size Size in bytes of the CPU range to loop over
* @param func Function to call for each turned off region
*/
template <Type type, bool clear, typename Func>
void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) {
static_assert(type != Type::Untracked);
std::span<u64> state_words = words.template Span<type>();
[[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
[[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
const size_t offset = query_cpu_range - cpu_addr;
bool pending = false;
size_t pending_offset{};
size_t pending_pointer{};
const auto release = [&]() {
func(cpu_addr + pending_offset * BYTES_PER_PAGE,
(pending_pointer - pending_offset) * BYTES_PER_PAGE);
};
IterateWords(offset, size, [&](size_t index, u64 mask) {
const u64 word = state_words[index] & mask;
if constexpr (clear) {
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
NotifyRasterizer<true>(index, untracked_words[index], mask);
}
state_words[index] &= ~mask;
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
untracked_words[index] &= ~mask;
}
if constexpr (type == Type::CPU) {
cached_words[index] &= ~word;
}
}
const size_t base_offset = index * PAGES_PER_WORD;
IteratePages(word, [&](size_t pages_offset, size_t pages_size) {
const auto reset = [&]() {
pending_offset = base_offset + pages_offset;
pending_pointer = base_offset + pages_offset + pages_size;
};
if (!pending) {
reset();
pending = true;
return;
}
if (pending_pointer == base_offset + pages_offset) {
pending_pointer += pages_size;
return;
}
release();
reset();
});
});
if (pending) {
release();
}
}
/**
* Returns true when a region has been modified
*
* @param offset Offset in bytes from the start of the buffer
* @param size Size in bytes of the region to query for modifications
*/
template <Type type>
[[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
static_assert(type != Type::Untracked);
const std::span<const u64> state_words = words.template Span<type>();
bool result = false;
IterateWords(offset, size, [&](size_t index, u64 mask) {
const u64 word = state_words[index] & mask;
if (word != 0) {
result = true;
return true;
}
return false;
});
return result;
}
/**
* Returns a begin end pair with the inclusive modified region
*
* @param offset Offset in bytes from the start of the buffer
* @param size Size in bytes of the region to query for modifications
*/
template <Type type>
[[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept {
static_assert(type != Type::Untracked);
const std::span<const u64> state_words = words.template Span<type>();
u64 begin = std::numeric_limits<u64>::max();
u64 end = 0;
IterateWords(offset, size, [&](size_t index, u64 mask) {
const u64 word = state_words[index] & mask;
if (word == 0) {
return;
}
const u64 local_page_begin = std::countr_zero(word);
const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
const u64 page_index = index * PAGES_PER_WORD;
begin = std::min(begin, page_index + local_page_begin);
end = page_index + local_page_end;
});
static constexpr std::pair<u64, u64> EMPTY{0, 0};
return begin < end ? std::make_pair(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE) : EMPTY;
}
/// Returns the number of words of the manager
[[nodiscard]] size_t NumWords() const noexcept {
return words.NumWords();
}
/// Returns the size in bytes of the manager
[[nodiscard]] u64 SizeBytes() const noexcept {
return words.size_bytes;
}
/// Returns true when the buffer fits in the small vector optimization
[[nodiscard]] bool IsShort() const noexcept {
return words.IsShort();
}
void FlushCachedWrites() noexcept {
const u64 num_words = NumWords();
u64* const cached_words = Array<Type::CachedCPU>();
u64* const untracked_words = Array<Type::Untracked>();
u64* const cpu_words = Array<Type::CPU>();
for (u64 word_index = 0; word_index < num_words; ++word_index) {
const u64 cached_bits = cached_words[word_index];
NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits);
untracked_words[word_index] |= cached_bits;
cpu_words[word_index] |= cached_bits;
cached_words[word_index] = 0;
}
}
private:
template <Type type>
u64* Array() noexcept {
if constexpr (type == Type::CPU) {
return words.cpu.Pointer(IsShort());
} else if constexpr (type == Type::GPU) {
return words.gpu.Pointer(IsShort());
} else if constexpr (type == Type::CachedCPU) {
return words.cached_cpu.Pointer(IsShort());
} else if constexpr (type == Type::Untracked) {
return words.untracked.Pointer(IsShort());
}
}
template <Type type>
const u64* Array() const noexcept {
if constexpr (type == Type::CPU) {
return words.cpu.Pointer(IsShort());
} else if constexpr (type == Type::GPU) {
return words.gpu.Pointer(IsShort());
} else if constexpr (type == Type::CachedCPU) {
return words.cached_cpu.Pointer(IsShort());
} else if constexpr (type == Type::Untracked) {
return words.untracked.Pointer(IsShort());
}
}
/**
* Notify rasterizer about changes in the CPU tracking state of a word in the buffer
*
* @param word_index Index to the word to notify to the rasterizer
* @param current_bits Current state of the word
* @param new_bits New state of the word
*
* @tparam add_to_rasterizer True when the rasterizer should start tracking the new pages
*/
template <bool add_to_rasterizer>
void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const {
u64 changed_bits = (add_to_rasterizer ? current_bits : ~current_bits) & new_bits;
VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
IteratePages(changed_bits, [&](size_t offset, size_t size) {
rasterizer->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE,
size * BYTES_PER_PAGE, add_to_rasterizer ? 1 : -1);
});
}
VAddr cpu_addr = 0;
RasterizerInterface* rasterizer = nullptr;
Words<stack_words> words;
};
} // namespace VideoCommon
+11 -9
View File
@@ -126,15 +126,14 @@ constexpr std::array VIEW_CLASS_ASTC_8x8_RGBA{
PixelFormat::ASTC_2D_8X8_SRGB,
};
// Missing formats:
// PixelFormat::ASTC_2D_10X5_UNORM
// PixelFormat::ASTC_2D_10X5_SRGB
// Missing formats:
// PixelFormat::ASTC_2D_10X6_SRGB
constexpr std::array VIEW_CLASS_ASTC_10x5_RGBA{
PixelFormat::ASTC_2D_10X5_UNORM,
PixelFormat::ASTC_2D_10X5_SRGB,
};
constexpr std::array VIEW_CLASS_ASTC_10x6_RGBA{
PixelFormat::ASTC_2D_10X6_UNORM,
PixelFormat::ASTC_2D_10X6_SRGB,
};
constexpr std::array VIEW_CLASS_ASTC_10x8_RGBA{
@@ -147,9 +146,10 @@ constexpr std::array VIEW_CLASS_ASTC_10x10_RGBA{
PixelFormat::ASTC_2D_10X10_SRGB,
};
// Missing formats
// ASTC_2D_12X10_UNORM,
// ASTC_2D_12X10_SRGB,
constexpr std::array VIEW_CLASS_ASTC_12x10_RGBA{
PixelFormat::ASTC_2D_12X10_UNORM,
PixelFormat::ASTC_2D_12X10_SRGB,
};
constexpr std::array VIEW_CLASS_ASTC_12x12_RGBA{
PixelFormat::ASTC_2D_12X12_UNORM,
@@ -229,9 +229,11 @@ constexpr Table MakeViewTable() {
EnableRange(view, VIEW_CLASS_ASTC_6x6_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_8x5_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_8x8_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_10x5_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_10x6_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_10x8_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_10x10_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_12x10_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_12x12_RGBA);
return view;
}
+12 -4
View File
@@ -77,6 +77,14 @@ void Fermi2D::Blit() {
const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
const bool delegate_to_gpu = src.width > 512 && src.height > 512 && bytes_per_pixel <= 8 &&
src.format != regs.dst.format;
auto srcX = args.src_x0;
auto srcY = args.src_y0;
if (args.sample_mode.origin == Origin::Corner) {
srcX -= (args.du_dx >> 33) << 32;
srcY -= (args.dv_dy >> 33) << 32;
}
Config config{
.operation = regs.operation,
.filter = args.sample_mode.filter,
@@ -86,10 +94,10 @@ void Fermi2D::Blit() {
.dst_y0 = args.dst_y0,
.dst_x1 = args.dst_x0 + args.dst_width,
.dst_y1 = args.dst_y0 + args.dst_height,
.src_x0 = static_cast<s32>(args.src_x0 >> 32),
.src_y0 = static_cast<s32>(args.src_y0 >> 32),
.src_x1 = static_cast<s32>((args.du_dx * args.dst_width + args.src_x0) >> 32),
.src_y1 = static_cast<s32>((args.dv_dy * args.dst_height + args.src_y0) >> 32),
.src_x0 = static_cast<s32>(srcX >> 32),
.src_y0 = static_cast<s32>(srcY >> 32),
.src_x1 = static_cast<s32>((srcX + args.du_dx * args.dst_width) >> 32),
.src_y1 = static_cast<s32>((srcY + args.dv_dy * args.dst_height) >> 32),
};
const auto need_align_to_pitch =
+9 -4
View File
@@ -4,6 +4,7 @@
#include <cstring>
#include <optional>
#include "common/assert.h"
#include "common/bit_util.h"
#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/core.h"
@@ -222,6 +223,9 @@ void Maxwell3D::ProcessMacro(u32 method, const u32* base_start, u32 amount, bool
}
void Maxwell3D::RefreshParametersImpl() {
if (!Settings::IsGPULevelHigh()) {
return;
}
size_t current_index = 0;
for (auto& segment : macro_segments) {
if (segment.first == 0) {
@@ -259,12 +263,13 @@ u32 Maxwell3D::GetMaxCurrentVertices() {
size_t Maxwell3D::EstimateIndexBufferSize() {
GPUVAddr start_address = regs.index_buffer.StartAddress();
GPUVAddr end_address = regs.index_buffer.EndAddress();
static constexpr std::array<size_t, 4> max_sizes = {
std::numeric_limits<u8>::max(), std::numeric_limits<u16>::max(),
std::numeric_limits<u32>::max(), std::numeric_limits<u32>::max()};
static constexpr std::array<size_t, 3> max_sizes = {std::numeric_limits<u8>::max(),
std::numeric_limits<u16>::max(),
std::numeric_limits<u32>::max()};
const size_t byte_size = regs.index_buffer.FormatSizeInBytes();
const size_t log2_byte_size = Common::Log2Ceil64(byte_size);
return std::min<size_t>(
memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[byte_size]) /
memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) /
byte_size,
static_cast<size_t>(end_address - start_address));
}
+111 -32
View File
@@ -4,13 +4,20 @@
#pragma once
#include <algorithm>
#include <condition_variable>
#include <cstring>
#include <deque>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
#include <queue>
#include "common/common_types.h"
#include "common/microprofile.h"
#include "common/scope_exit.h"
#include "common/settings.h"
#include "common/thread.h"
#include "video_core/delayed_destruction_ring.h"
#include "video_core/gpu.h"
#include "video_core/host1x/host1x.h"
@@ -23,15 +30,26 @@ class FenceBase {
public:
explicit FenceBase(bool is_stubbed_) : is_stubbed{is_stubbed_} {}
bool IsStubbed() const {
return is_stubbed;
}
protected:
bool is_stubbed;
};
template <typename TFence, typename TTextureCache, typename TTBufferCache, typename TQueryCache>
template <typename Traits>
class FenceManager {
using TFence = typename Traits::FenceType;
using TTextureCache = typename Traits::TextureCacheType;
using TBufferCache = typename Traits::BufferCacheType;
using TQueryCache = typename Traits::QueryCacheType;
static constexpr bool can_async_check = Traits::HAS_ASYNC_CHECK;
public:
/// Notify the fence manager about a new frame
void TickFrame() {
std::unique_lock lock(ring_guard);
delayed_destruction_ring.Tick();
}
@@ -46,17 +64,33 @@ public:
}
void SignalFence(std::function<void()>&& func) {
TryReleasePendingFences();
rasterizer.InvalidateGPUCache();
bool delay_fence = Settings::IsGPULevelHigh();
if constexpr (!can_async_check) {
TryReleasePendingFences<false>();
}
const bool should_flush = ShouldFlush();
CommitAsyncFlushes();
uncommitted_operations.emplace_back(std::move(func));
CommitOperations();
TFence new_fence = CreateFence(!should_flush);
fences.push(new_fence);
if constexpr (can_async_check) {
guard.lock();
}
if (delay_fence) {
uncommitted_operations.emplace_back(std::move(func));
}
pending_operations.emplace_back(std::move(uncommitted_operations));
QueueFence(new_fence);
if (!delay_fence) {
func();
}
fences.push(std::move(new_fence));
if (should_flush) {
rasterizer.FlushCommands();
}
if constexpr (can_async_check) {
guard.unlock();
cv.notify_all();
}
}
void SignalSyncPoint(u32 value) {
@@ -66,29 +100,30 @@ public:
}
void WaitPendingFences() {
while (!fences.empty()) {
TFence& current_fence = fences.front();
if (ShouldWait()) {
WaitFence(current_fence);
}
PopAsyncFlushes();
auto operations = std::move(pending_operations.front());
pending_operations.pop_front();
for (auto& operation : operations) {
operation();
}
PopFence();
if constexpr (!can_async_check) {
TryReleasePendingFences<true>();
}
}
protected:
explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
TTextureCache& texture_cache_, TTBufferCache& buffer_cache_,
TTextureCache& texture_cache_, TBufferCache& buffer_cache_,
TQueryCache& query_cache_)
: rasterizer{rasterizer_}, gpu{gpu_}, syncpoint_manager{gpu.Host1x().GetSyncpointManager()},
texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {}
texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {
if constexpr (can_async_check) {
fence_thread =
std::jthread([this](std::stop_token token) { ReleaseThreadFunc(token); });
}
}
virtual ~FenceManager() = default;
virtual ~FenceManager() {
if constexpr (can_async_check) {
fence_thread.request_stop();
cv.notify_all();
fence_thread.join();
}
}
/// Creates a Fence Interface, does not create a backend fence if 'is_stubbed' is
/// true
@@ -104,15 +139,20 @@ protected:
Tegra::GPU& gpu;
Tegra::Host1x::SyncpointManager& syncpoint_manager;
TTextureCache& texture_cache;
TTBufferCache& buffer_cache;
TBufferCache& buffer_cache;
TQueryCache& query_cache;
private:
template <bool force_wait>
void TryReleasePendingFences() {
while (!fences.empty()) {
TFence& current_fence = fences.front();
if (ShouldWait() && !IsFenceSignaled(current_fence)) {
return;
if constexpr (force_wait) {
WaitFence(current_fence);
} else {
return;
}
}
PopAsyncFlushes();
auto operations = std::move(pending_operations.front());
@@ -120,7 +160,49 @@ private:
for (auto& operation : operations) {
operation();
}
PopFence();
{
std::unique_lock lock(ring_guard);
delayed_destruction_ring.Push(std::move(current_fence));
}
fences.pop();
}
}
void ReleaseThreadFunc(std::stop_token stop_token) {
std::string name = "GPUFencingThread";
MicroProfileOnThreadCreate(name.c_str());
// Cleanup
SCOPE_EXIT({ MicroProfileOnThreadExit(); });
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
TFence current_fence;
std::deque<std::function<void()>> current_operations;
while (!stop_token.stop_requested()) {
{
std::unique_lock lock(guard);
cv.wait(lock, [&] { return stop_token.stop_requested() || !fences.empty(); });
if (stop_token.stop_requested()) [[unlikely]] {
return;
}
current_fence = std::move(fences.front());
current_operations = std::move(pending_operations.front());
fences.pop();
pending_operations.pop_front();
}
if (!current_fence->IsStubbed()) {
WaitFence(current_fence);
}
PopAsyncFlushes();
for (auto& operation : current_operations) {
operation();
}
{
std::unique_lock lock(ring_guard);
delayed_destruction_ring.Push(std::move(current_fence));
}
}
}
@@ -154,19 +236,16 @@ private:
query_cache.CommitAsyncFlushes();
}
void PopFence() {
delayed_destruction_ring.Push(std::move(fences.front()));
fences.pop();
}
void CommitOperations() {
pending_operations.emplace_back(std::move(uncommitted_operations));
}
std::queue<TFence> fences;
std::deque<std::function<void()>> uncommitted_operations;
std::deque<std::deque<std::function<void()>>> pending_operations;
std::mutex guard;
std::mutex ring_guard;
std::condition_variable cv;
std::jthread fence_thread;
DelayedDestructionRing<TFence, 6> delayed_destruction_ring;
};
+9 -4
View File
@@ -82,6 +82,7 @@ void MemoryManager::SetEntry(size_t position, MemoryManager::EntryType entry) {
}
PTEKind MemoryManager::GetPageKind(GPUVAddr gpu_addr) const {
std::unique_lock<std::mutex> lock(guard);
return kind_map.GetValueAt(gpu_addr);
}
@@ -160,7 +161,10 @@ GPUVAddr MemoryManager::BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr
}
remaining_size -= big_page_size;
}
kind_map.Map(gpu_addr, gpu_addr + size, kind);
{
std::unique_lock<std::mutex> lock(guard);
kind_map.Map(gpu_addr, gpu_addr + size, kind);
}
return gpu_addr;
}
@@ -553,6 +557,7 @@ size_t MemoryManager::MaxContinuousRange(GPUVAddr gpu_addr, size_t size) const {
}
size_t MemoryManager::GetMemoryLayoutSize(GPUVAddr gpu_addr, size_t max_size) const {
std::unique_lock<std::mutex> lock(guard);
return kind_map.GetContinuousSizeFrom(gpu_addr);
}
@@ -745,10 +750,10 @@ void MemoryManager::FlushCaching() {
return;
}
accumulator->Callback([this](GPUVAddr addr, size_t size) {
GetSubmappedRangeImpl<false>(addr, size, page_stash);
GetSubmappedRangeImpl<false>(addr, size, page_stash2);
});
rasterizer->InnerInvalidation(page_stash);
page_stash.clear();
rasterizer->InnerInvalidation(page_stash2);
page_stash2.clear();
accumulator->Clear();
}
+4
View File
@@ -5,6 +5,7 @@
#include <atomic>
#include <map>
#include <mutex>
#include <optional>
#include <vector>
@@ -215,6 +216,9 @@ private:
std::vector<u64> big_page_continuous;
std::vector<std::pair<VAddr, std::size_t>> page_stash{};
std::vector<std::pair<VAddr, std::size_t>> page_stash2{};
mutable std::mutex guard;
static constexpr size_t continuous_bits = 64;
+114 -25
View File
@@ -6,6 +6,7 @@
#include <algorithm>
#include <array>
#include <cstring>
#include <functional>
#include <iterator>
#include <list>
#include <memory>
@@ -17,13 +18,19 @@
#include "common/assert.h"
#include "common/settings.h"
#include "core/memory.h"
#include "video_core/control/channel_state_cache.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/texture_cache/slot_vector.h"
namespace VideoCommon {
using AsyncJobId = SlotId;
static constexpr AsyncJobId NULL_ASYNC_JOB_ID{0};
template <class QueryCache, class HostCounter>
class CounterStreamBase {
public:
@@ -93,9 +100,13 @@ private:
template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter>
class QueryCacheBase : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
public:
explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_)
: rasterizer{rasterizer_}, streams{{CounterStream{static_cast<QueryCache&>(*this),
VideoCore::QueryType::SamplesPassed}}} {}
explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_,
Core::Memory::Memory& cpu_memory_)
: rasterizer{rasterizer_},
cpu_memory{cpu_memory_}, streams{{CounterStream{static_cast<QueryCache&>(*this),
VideoCore::QueryType::SamplesPassed}}} {
(void)slot_async_jobs.insert(); // Null value
}
void InvalidateRegion(VAddr addr, std::size_t size) {
std::unique_lock lock{mutex};
@@ -126,10 +137,15 @@ public:
query = Register(type, *cpu_addr, host_ptr, timestamp.has_value());
}
query->BindCounter(Stream(type).Current(), timestamp);
if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
AsyncFlushQuery(*cpu_addr);
auto result = query->BindCounter(Stream(type).Current(), timestamp);
if (result) {
auto async_job_id = query->GetAsyncJob();
auto& async_job = slot_async_jobs[async_job_id];
async_job.collected = true;
async_job.value = *result;
query->SetAsyncJob(NULL_ASYNC_JOB_ID);
}
AsyncFlushQuery(query, timestamp, lock);
}
/// Updates counters from GPU state. Expected to be called once per draw, clear or dispatch.
@@ -173,15 +189,18 @@ public:
}
void CommitAsyncFlushes() {
std::unique_lock lock{mutex};
committed_flushes.push_back(uncommitted_flushes);
uncommitted_flushes.reset();
}
bool HasUncommittedFlushes() const {
std::unique_lock lock{mutex};
return uncommitted_flushes != nullptr;
}
bool ShouldWaitAsyncFlushes() const {
std::unique_lock lock{mutex};
if (committed_flushes.empty()) {
return false;
}
@@ -189,6 +208,7 @@ public:
}
void PopAsyncFlushes() {
std::unique_lock lock{mutex};
if (committed_flushes.empty()) {
return;
}
@@ -197,15 +217,25 @@ public:
committed_flushes.pop_front();
return;
}
for (VAddr query_address : *flush_list) {
FlushAndRemoveRegion(query_address, 4);
for (AsyncJobId async_job_id : *flush_list) {
AsyncJob& async_job = slot_async_jobs[async_job_id];
if (!async_job.collected) {
FlushAndRemoveRegion(async_job.query_location, 2, true);
}
}
committed_flushes.pop_front();
}
private:
struct AsyncJob {
bool collected = false;
u64 value = 0;
VAddr query_location = 0;
std::optional<u64> timestamp{};
};
/// Flushes a memory range to guest memory and removes it from the cache.
void FlushAndRemoveRegion(VAddr addr, std::size_t size) {
void FlushAndRemoveRegion(VAddr addr, std::size_t size, bool async = false) {
const u64 addr_begin = addr;
const u64 addr_end = addr_begin + size;
const auto in_range = [addr_begin, addr_end](const CachedQuery& query) {
@@ -226,7 +256,16 @@ private:
continue;
}
rasterizer.UpdatePagesCachedCount(query.GetCpuAddr(), query.SizeInBytes(), -1);
query.Flush();
AsyncJobId async_job_id = query.GetAsyncJob();
auto flush_result = query.Flush(async);
if (async_job_id == NULL_ASYNC_JOB_ID) {
ASSERT_MSG(false, "This should not be reachable at all");
continue;
}
AsyncJob& async_job = slot_async_jobs[async_job_id];
async_job.collected = true;
async_job.value = flush_result;
query.SetAsyncJob(NULL_ASYNC_JOB_ID);
}
std::erase_if(contents, in_range);
}
@@ -253,26 +292,60 @@ private:
return found != std::end(contents) ? &*found : nullptr;
}
void AsyncFlushQuery(VAddr addr) {
if (!uncommitted_flushes) {
uncommitted_flushes = std::make_shared<std::vector<VAddr>>();
void AsyncFlushQuery(CachedQuery* query, std::optional<u64> timestamp,
std::unique_lock<std::recursive_mutex>& lock) {
const AsyncJobId new_async_job_id = slot_async_jobs.insert();
{
AsyncJob& async_job = slot_async_jobs[new_async_job_id];
query->SetAsyncJob(new_async_job_id);
async_job.query_location = query->GetCpuAddr();
async_job.collected = false;
if (!uncommitted_flushes) {
uncommitted_flushes = std::make_shared<std::vector<AsyncJobId>>();
}
uncommitted_flushes->push_back(new_async_job_id);
}
uncommitted_flushes->push_back(addr);
lock.unlock();
std::function<void()> operation([this, new_async_job_id, timestamp] {
std::unique_lock local_lock{mutex};
AsyncJob& async_job = slot_async_jobs[new_async_job_id];
u64 value = async_job.value;
VAddr address = async_job.query_location;
slot_async_jobs.erase(new_async_job_id);
local_lock.unlock();
if (timestamp) {
u64 timestamp_value = *timestamp;
cpu_memory.WriteBlockUnsafe(address + sizeof(u64), &timestamp_value, sizeof(u64));
cpu_memory.WriteBlockUnsafe(address, &value, sizeof(u64));
rasterizer.InvalidateRegion(address, sizeof(u64) * 2,
VideoCommon::CacheType::NoQueryCache);
} else {
u32 small_value = static_cast<u32>(value);
cpu_memory.WriteBlockUnsafe(address, &small_value, sizeof(u32));
rasterizer.InvalidateRegion(address, sizeof(u32),
VideoCommon::CacheType::NoQueryCache);
}
});
rasterizer.SyncOperation(std::move(operation));
}
static constexpr std::uintptr_t YUZU_PAGESIZE = 4096;
static constexpr unsigned YUZU_PAGEBITS = 12;
VideoCore::RasterizerInterface& rasterizer;
SlotVector<AsyncJob> slot_async_jobs;
std::recursive_mutex mutex;
VideoCore::RasterizerInterface& rasterizer;
Core::Memory::Memory& cpu_memory;
mutable std::recursive_mutex mutex;
std::unordered_map<u64, std::vector<CachedQuery>> cached_queries;
std::array<CounterStream, VideoCore::NumQueryTypes> streams;
std::shared_ptr<std::vector<VAddr>> uncommitted_flushes{};
std::list<std::shared_ptr<std::vector<VAddr>>> committed_flushes;
std::shared_ptr<std::vector<AsyncJobId>> uncommitted_flushes{};
std::list<std::shared_ptr<std::vector<AsyncJobId>>> committed_flushes;
};
template <class QueryCache, class HostCounter>
@@ -291,12 +364,12 @@ public:
virtual ~HostCounterBase() = default;
/// Returns the current value of the query.
u64 Query() {
u64 Query(bool async = false) {
if (result) {
return *result;
}
u64 value = BlockingQuery() + base_result;
u64 value = BlockingQuery(async) + base_result;
if (dependency) {
value += dependency->Query();
dependency = nullptr;
@@ -317,7 +390,7 @@ public:
protected:
/// Returns the value of query from the backend API blocking as needed.
virtual u64 BlockingQuery() const = 0;
virtual u64 BlockingQuery(bool async = false) const = 0;
private:
std::shared_ptr<HostCounter> dependency; ///< Counter to add to this value.
@@ -340,26 +413,33 @@ public:
CachedQueryBase& operator=(const CachedQueryBase&) = delete;
/// Flushes the query to guest memory.
virtual void Flush() {
virtual u64 Flush(bool async = false) {
// When counter is nullptr it means that it's just been reset. We are supposed to write a
// zero in these cases.
const u64 value = counter ? counter->Query() : 0;
const u64 value = counter ? counter->Query(async) : 0;
if (async) {
return value;
}
std::memcpy(host_ptr, &value, sizeof(u64));
if (timestamp) {
std::memcpy(host_ptr + TIMESTAMP_OFFSET, &*timestamp, sizeof(u64));
}
return value;
}
/// Binds a counter to this query.
void BindCounter(std::shared_ptr<HostCounter> counter_, std::optional<u64> timestamp_) {
std::optional<u64> BindCounter(std::shared_ptr<HostCounter> counter_,
std::optional<u64> timestamp_) {
std::optional<u64> result{};
if (counter) {
// If there's an old counter set it means the query is being rewritten by the game.
// To avoid losing the data forever, flush here.
Flush();
result = std::make_optional(Flush());
}
counter = std::move(counter_);
timestamp = timestamp_;
return result;
}
VAddr GetCpuAddr() const noexcept {
@@ -374,6 +454,14 @@ public:
return with_timestamp ? LARGE_QUERY_SIZE : SMALL_QUERY_SIZE;
}
void SetAsyncJob(AsyncJobId assigned_async_job_) {
assigned_async_job = assigned_async_job_;
}
AsyncJobId GetAsyncJob() const {
return assigned_async_job;
}
protected:
/// Returns true when querying the counter may potentially block.
bool WaitPending() const noexcept {
@@ -389,6 +477,7 @@ private:
u8* host_ptr; ///< Writable host pointer.
std::shared_ptr<HostCounter> counter; ///< Host counter to query, owns the dependency tree.
std::optional<u64> timestamp; ///< Timestamp to flush to guest memory.
AsyncJobId assigned_async_job;
};
} // namespace VideoCommon
@@ -8,6 +8,7 @@
#include "common/common_types.h"
#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/buffer_cache/memory_tracker_base.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -200,6 +201,8 @@ private:
struct BufferCacheParams {
using Runtime = OpenGL::BufferCacheRuntime;
using Buffer = OpenGL::Buffer;
using Async_Buffer = u32;
using MemoryTracker = VideoCommon::MemoryTrackerBase<VideoCore::RasterizerInterface>;
static constexpr bool IS_OPENGL = true;
static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS = true;
@@ -208,6 +211,7 @@ struct BufferCacheParams {
static constexpr bool NEEDS_BIND_STORAGE_INDEX = true;
static constexpr bool USE_MEMORY_MAPS = false;
static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = true;
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = false;
};
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
namespace VideoCommon {
template class VideoCommon::BufferCache<OpenGL::BufferCacheParams>;
}
@@ -30,7 +30,17 @@ private:
};
using Fence = std::shared_ptr<GLInnerFence>;
using GenericFenceManager = VideoCommon::FenceManager<Fence, TextureCache, BufferCache, QueryCache>;
struct FenceManagerParams {
using FenceType = Fence;
using BufferCacheType = BufferCache;
using TextureCacheType = TextureCache;
using QueryCacheType = QueryCache;
static constexpr bool HAS_ASYNC_CHECK = false;
};
using GenericFenceManager = VideoCommon::FenceManager<FenceManagerParams>;
class FenceManagerOpenGL final : public GenericFenceManager {
public:
@@ -26,8 +26,8 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) {
} // Anonymous namespace
QueryCache::QueryCache(RasterizerOpenGL& rasterizer_)
: QueryCacheBase(rasterizer_), gl_rasterizer{rasterizer_} {}
QueryCache::QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_)
: QueryCacheBase(rasterizer_, cpu_memory_), gl_rasterizer{rasterizer_} {}
QueryCache::~QueryCache() = default;
@@ -74,7 +74,7 @@ void HostCounter::EndQuery() {
glEndQuery(GetTarget(type));
}
u64 HostCounter::BlockingQuery() const {
u64 HostCounter::BlockingQuery([[maybe_unused]] bool async) const {
GLint64 value;
glGetQueryObjecti64v(query.handle, GL_QUERY_RESULT, &value);
return static_cast<u64>(value);
@@ -96,7 +96,7 @@ CachedQuery& CachedQuery::operator=(CachedQuery&& rhs) noexcept {
return *this;
}
void CachedQuery::Flush() {
u64 CachedQuery::Flush([[maybe_unused]] bool async) {
// Waiting for a query while another query of the same target is enabled locks Nvidia's driver.
// To avoid this disable and re-enable keeping the dependency stream.
// But we only have to do this if we have pending waits to be done.
@@ -106,11 +106,13 @@ void CachedQuery::Flush() {
stream.Update(false);
}
VideoCommon::CachedQueryBase<HostCounter>::Flush();
auto result = VideoCommon::CachedQueryBase<HostCounter>::Flush();
if (slice_counter) {
stream.Update(true);
}
return result;
}
} // namespace OpenGL
@@ -28,7 +28,7 @@ using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
class QueryCache final
: public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> {
public:
explicit QueryCache(RasterizerOpenGL& rasterizer_);
explicit QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_);
~QueryCache();
OGLQuery AllocateQuery(VideoCore::QueryType type);
@@ -51,7 +51,7 @@ public:
void EndQuery();
private:
u64 BlockingQuery() const override;
u64 BlockingQuery(bool async = false) const override;
QueryCache& cache;
const VideoCore::QueryType type;
@@ -70,7 +70,7 @@ public:
CachedQuery(const CachedQuery&) = delete;
CachedQuery& operator=(const CachedQuery&) = delete;
void Flush() override;
u64 Flush(bool async = false) override;
private:
QueryCache* cache;
@@ -63,7 +63,7 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window_, Tegra
buffer_cache(*this, cpu_memory_, buffer_cache_runtime),
shader_cache(*this, emu_window_, device, texture_cache, buffer_cache, program_manager,
state_tracker, gpu.ShaderNotify()),
query_cache(*this), accelerate_dma(buffer_cache, texture_cache),
query_cache(*this, cpu_memory_), accelerate_dma(buffer_cache, texture_cache),
fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache),
blit_image(program_manager_) {}
@@ -1287,8 +1287,7 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
}
const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height);
static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
const auto post_op = IS_IMAGE_UPLOAD ? VideoCommon::ObtainBufferOperation::DoNothing
: VideoCommon::ObtainBufferOperation::MarkAsWritten;
const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
const auto [buffer, offset] =
buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op);
@@ -1299,7 +1298,8 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
if constexpr (IS_IMAGE_UPLOAD) {
image->UploadMemory(buffer->Handle(), offset, copy_span);
} else {
image->DownloadMemory(buffer->Handle(), offset, copy_span);
texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span,
buffer_operand.address, buffer_size);
}
return true;
}
@@ -803,30 +803,40 @@ void Image::UploadMemory(const ImageBufferMap& map,
void Image::DownloadMemory(GLuint buffer_handle, size_t buffer_offset,
std::span<const VideoCommon::BufferImageCopy> copies) {
std::array buffer_handles{buffer_handle};
std::array buffer_offsets{buffer_offset};
DownloadMemory(buffer_handles, buffer_offsets, copies);
}
void Image::DownloadMemory(std::span<GLuint> buffer_handles, std::span<size_t> buffer_offsets,
std::span<const VideoCommon::BufferImageCopy> copies) {
const bool is_rescaled = True(flags & ImageFlagBits::Rescaled);
if (is_rescaled) {
ScaleDown();
}
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT); // TODO: Move this to its own API
glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer_handle);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
for (size_t i = 0; i < buffer_handles.size(); i++) {
auto& buffer_handle = buffer_handles[i];
glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer_handle);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
u32 current_row_length = std::numeric_limits<u32>::max();
u32 current_image_height = std::numeric_limits<u32>::max();
u32 current_row_length = std::numeric_limits<u32>::max();
u32 current_image_height = std::numeric_limits<u32>::max();
for (const VideoCommon::BufferImageCopy& copy : copies) {
if (copy.image_subresource.base_level >= gl_num_levels) {
continue;
for (const VideoCommon::BufferImageCopy& copy : copies) {
if (copy.image_subresource.base_level >= gl_num_levels) {
continue;
}
if (current_row_length != copy.buffer_row_length) {
current_row_length = copy.buffer_row_length;
glPixelStorei(GL_PACK_ROW_LENGTH, current_row_length);
}
if (current_image_height != copy.buffer_image_height) {
current_image_height = copy.buffer_image_height;
glPixelStorei(GL_PACK_IMAGE_HEIGHT, current_image_height);
}
CopyImageToBuffer(copy, buffer_offsets[i]);
}
if (current_row_length != copy.buffer_row_length) {
current_row_length = copy.buffer_row_length;
glPixelStorei(GL_PACK_ROW_LENGTH, current_row_length);
}
if (current_image_height != copy.buffer_image_height) {
current_image_height = copy.buffer_image_height;
glPixelStorei(GL_PACK_IMAGE_HEIGHT, current_image_height);
}
CopyImageToBuffer(copy, buffer_offset);
}
if (is_rescaled) {
ScaleUp(true);
@@ -851,9 +861,12 @@ GLuint Image::StorageHandle() noexcept {
case PixelFormat::ASTC_2D_8X5_SRGB:
case PixelFormat::ASTC_2D_5X4_SRGB:
case PixelFormat::ASTC_2D_5X5_SRGB:
case PixelFormat::ASTC_2D_10X5_SRGB:
case PixelFormat::ASTC_2D_10X6_SRGB:
case PixelFormat::ASTC_2D_10X8_SRGB:
case PixelFormat::ASTC_2D_6X6_SRGB:
case PixelFormat::ASTC_2D_10X10_SRGB:
case PixelFormat::ASTC_2D_12X10_SRGB:
case PixelFormat::ASTC_2D_12X12_SRGB:
case PixelFormat::ASTC_2D_8X6_SRGB:
case PixelFormat::ASTC_2D_6X5_SRGB:
@@ -215,6 +215,9 @@ public:
void DownloadMemory(GLuint buffer_handle, size_t buffer_offset,
std::span<const VideoCommon::BufferImageCopy> copies);
void DownloadMemory(std::span<GLuint> buffer_handle, std::span<size_t> buffer_offset,
std::span<const VideoCommon::BufferImageCopy> copies);
void DownloadMemory(ImageBufferMap& map, std::span<const VideoCommon::BufferImageCopy> copies);
GLuint StorageHandle() noexcept;
@@ -376,6 +379,7 @@ struct TextureCacheParams {
using Sampler = OpenGL::Sampler;
using Framebuffer = OpenGL::Framebuffer;
using AsyncBuffer = u32;
using BufferType = GLuint;
};
using TextureCache = VideoCommon::TextureCache<TextureCacheParams>;
@@ -100,10 +100,13 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB
{GL_COMPRESSED_RGBA_ASTC_6x6_KHR}, // ASTC_2D_6X6_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}, // ASTC_2D_6X6_SRGB
{GL_COMPRESSED_RGBA_ASTC_10x6_KHR}, // ASTC_2D_10X6_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR}, // ASTC_2D_10X6_SRGB
{GL_COMPRESSED_RGBA_ASTC_10x5_KHR}, // ASTC_2D_10X5_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR}, // ASTC_2D_10X5_SRGB
{GL_COMPRESSED_RGBA_ASTC_10x10_KHR}, // ASTC_2D_10X10_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}, // ASTC_2D_10X10_SRGB
{GL_COMPRESSED_RGBA_ASTC_12x10_KHR}, // ASTC_2D_12X10_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR}, // ASTC_2D_12X10_SRGB
{GL_COMPRESSED_RGBA_ASTC_12x12_KHR}, // ASTC_2D_12X12_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR}, // ASTC_2D_12X12_SRGB
{GL_COMPRESSED_RGBA_ASTC_8x6_KHR}, // ASTC_2D_8X6_UNORM
@@ -197,10 +197,13 @@ struct FormatTuple {
{VK_FORMAT_ASTC_6x6_UNORM_BLOCK}, // ASTC_2D_6X6_UNORM
{VK_FORMAT_ASTC_6x6_SRGB_BLOCK}, // ASTC_2D_6X6_SRGB
{VK_FORMAT_ASTC_10x6_UNORM_BLOCK}, // ASTC_2D_10X6_UNORM
{VK_FORMAT_ASTC_10x6_SRGB_BLOCK}, // ASTC_2D_10X6_SRGB
{VK_FORMAT_ASTC_10x5_UNORM_BLOCK}, // ASTC_2D_10X5_UNORM
{VK_FORMAT_ASTC_10x5_SRGB_BLOCK}, // ASTC_2D_10X5_SRGB
{VK_FORMAT_ASTC_10x10_UNORM_BLOCK}, // ASTC_2D_10X10_UNORM
{VK_FORMAT_ASTC_10x10_SRGB_BLOCK}, // ASTC_2D_10X10_SRGB
{VK_FORMAT_ASTC_12x10_UNORM_BLOCK}, // ASTC_2D_12X10_UNORM
{VK_FORMAT_ASTC_12x10_SRGB_BLOCK}, // ASTC_2D_12X10_SRGB
{VK_FORMAT_ASTC_12x12_UNORM_BLOCK}, // ASTC_2D_12X12_UNORM
{VK_FORMAT_ASTC_12x12_SRGB_BLOCK}, // ASTC_2D_12X12_SRGB
{VK_FORMAT_ASTC_8x6_UNORM_BLOCK}, // ASTC_2D_8X6_UNORM
@@ -93,8 +93,9 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
state_tracker(), scheduler(device, state_tracker),
swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width,
render_window.GetFramebufferLayout().height, false),
blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, scheduler,
screen_info),
present_manager(render_window, device, memory_allocator, scheduler, swapchain),
blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, present_manager,
scheduler, screen_info),
rasterizer(render_window, gpu, cpu_memory, screen_info, device, memory_allocator,
state_tracker, scheduler) {
if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
@@ -121,46 +122,19 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
return;
}
// Update screen info if the framebuffer size has changed.
if (screen_info.width != framebuffer->width || screen_info.height != framebuffer->height) {
screen_info.width = framebuffer->width;
screen_info.height = framebuffer->height;
}
screen_info.width = framebuffer->width;
screen_info.height = framebuffer->height;
const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset;
const bool use_accelerated =
rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
const bool is_srgb = use_accelerated && screen_info.is_srgb;
RenderScreenshot(*framebuffer, use_accelerated);
bool has_been_recreated = false;
const auto recreate_swapchain = [&](u32 width, u32 height) {
if (!has_been_recreated) {
has_been_recreated = true;
scheduler.Finish();
}
swapchain.Create(width, height, is_srgb);
};
const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
if (swapchain.NeedsRecreation(is_srgb) || swapchain.GetWidth() != layout.width ||
swapchain.GetHeight() != layout.height) {
recreate_swapchain(layout.width, layout.height);
}
bool is_outdated;
do {
swapchain.AcquireNextImage();
is_outdated = swapchain.IsOutDated();
if (is_outdated) {
recreate_swapchain(layout.width, layout.height);
}
} while (is_outdated);
if (has_been_recreated) {
blit_screen.Recreate();
}
const VkSemaphore render_semaphore = blit_screen.DrawToSwapchain(*framebuffer, use_accelerated);
const VkSemaphore present_semaphore = swapchain.CurrentPresentSemaphore();
scheduler.Flush(render_semaphore, present_semaphore);
scheduler.WaitWorker();
swapchain.Present(render_semaphore);
Frame* frame = present_manager.GetRenderFrame();
blit_screen.DrawToSwapchain(frame, *framebuffer, use_accelerated, is_srgb);
scheduler.Flush(*frame->render_ready);
present_manager.Present(frame);
gpu.RendererFrameEndNotify();
rasterizer.TickFrame();
@@ -246,8 +220,7 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr
});
const VkExtent2D render_area{.width = layout.width, .height = layout.height};
const vk::Framebuffer screenshot_fb = blit_screen.CreateFramebuffer(*dst_view, render_area);
// Since we're not rendering to the screen, ignore the render semaphore.
void(blit_screen.Draw(framebuffer, *screenshot_fb, layout, render_area, use_accelerated));
blit_screen.Draw(framebuffer, *screenshot_fb, layout, render_area, use_accelerated);
const auto buffer_size = static_cast<VkDeviceSize>(layout.width * layout.height * 4);
const VkBufferCreateInfo dst_buffer_info{
@@ -270,7 +243,7 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
@@ -9,6 +9,7 @@
#include "common/dynamic_library.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h"
#include "video_core/renderer_vulkan/vk_present_manager.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_state_tracker.h"
@@ -76,6 +77,7 @@ private:
StateTracker state_tracker;
Scheduler scheduler;
Swapchain swapchain;
PresentManager present_manager;
BlitScreen blit_screen;
RasterizerVulkan rasterizer;
std::optional<TurboMode> turbo_mode;
+109 -113
View File
@@ -122,10 +122,12 @@ struct BlitScreen::BufferData {
BlitScreen::BlitScreen(Core::Memory::Memory& cpu_memory_, Core::Frontend::EmuWindow& render_window_,
const Device& device_, MemoryAllocator& memory_allocator_,
Swapchain& swapchain_, Scheduler& scheduler_, const ScreenInfo& screen_info_)
Swapchain& swapchain_, PresentManager& present_manager_,
Scheduler& scheduler_, const ScreenInfo& screen_info_)
: cpu_memory{cpu_memory_}, render_window{render_window_}, device{device_},
memory_allocator{memory_allocator_}, swapchain{swapchain_}, scheduler{scheduler_},
image_count{swapchain.GetImageCount()}, screen_info{screen_info_} {
memory_allocator{memory_allocator_}, swapchain{swapchain_}, present_manager{present_manager_},
scheduler{scheduler_}, image_count{swapchain.GetImageCount()}, screen_info{screen_info_},
current_srgb{swapchain.IsSrgb()}, image_view_format{swapchain.GetImageViewFormat()} {
resource_ticks.resize(image_count);
CreateStaticResources();
@@ -135,25 +137,20 @@ BlitScreen::BlitScreen(Core::Memory::Memory& cpu_memory_, Core::Frontend::EmuWin
BlitScreen::~BlitScreen() = default;
void BlitScreen::Recreate() {
present_manager.WaitPresent();
scheduler.Finish();
device.GetLogical().WaitIdle();
CreateDynamicResources();
}
VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
const VkFramebuffer& host_framebuffer,
const Layout::FramebufferLayout layout, VkExtent2D render_area,
bool use_accelerated) {
void BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
const VkFramebuffer& host_framebuffer, const Layout::FramebufferLayout layout,
VkExtent2D render_area, bool use_accelerated) {
RefreshResources(framebuffer);
// Finish any pending renderpass
scheduler.RequestOutsideRenderPassOperationContext();
if (const auto swapchain_images = swapchain.GetImageCount(); swapchain_images != image_count) {
image_count = swapchain_images;
Recreate();
}
const std::size_t image_index = swapchain.GetImageIndex();
scheduler.Wait(resource_ticks[image_index]);
resource_ticks[image_index] = scheduler.CurrentTick();
@@ -169,7 +166,7 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
std::memcpy(mapped_span.data(), &data, sizeof(data));
if (!use_accelerated) {
const u64 image_offset = GetRawImageOffset(framebuffer, image_index);
const u64 image_offset = GetRawImageOffset(framebuffer);
const VAddr framebuffer_addr = framebuffer.address + framebuffer.offset;
const u8* const host_ptr = cpu_memory.GetPointer(framebuffer_addr);
@@ -204,8 +201,8 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
.depth = 1,
},
};
scheduler.Record([this, copy, image_index](vk::CommandBuffer cmdbuf) {
const VkImage image = *raw_images[image_index];
scheduler.Record([this, copy, index = image_index](vk::CommandBuffer cmdbuf) {
const VkImage image = *raw_images[index];
const VkImageMemoryBarrier base_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
@@ -245,14 +242,15 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
const auto anti_alias_pass = Settings::values.anti_aliasing.GetValue();
if (use_accelerated && anti_alias_pass == Settings::AntiAliasing::Fxaa) {
UpdateAADescriptorSet(image_index, source_image_view, false);
UpdateAADescriptorSet(source_image_view, false);
const u32 up_scale = Settings::values.resolution_info.up_scale;
const u32 down_shift = Settings::values.resolution_info.down_shift;
VkExtent2D size{
.width = (up_scale * framebuffer.width) >> down_shift,
.height = (up_scale * framebuffer.height) >> down_shift,
};
scheduler.Record([this, image_index, size, anti_alias_pass](vk::CommandBuffer cmdbuf) {
scheduler.Record([this, index = image_index, size,
anti_alias_pass](vk::CommandBuffer cmdbuf) {
const VkImageMemoryBarrier base_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
@@ -326,7 +324,7 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices));
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *aa_pipeline_layout, 0,
aa_descriptor_sets[image_index], {});
aa_descriptor_sets[index], {});
cmdbuf.Draw(4, 1, 0, 0);
cmdbuf.EndRenderPass();
@@ -369,81 +367,99 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
};
VkImageView fsr_image_view =
fsr->Draw(scheduler, image_index, source_image_view, fsr_input_size, crop_rect);
UpdateDescriptorSet(image_index, fsr_image_view, true);
UpdateDescriptorSet(fsr_image_view, true);
} else {
const bool is_nn =
Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::NearestNeighbor;
UpdateDescriptorSet(image_index, source_image_view, is_nn);
UpdateDescriptorSet(source_image_view, is_nn);
}
scheduler.Record(
[this, host_framebuffer, image_index, size = render_area](vk::CommandBuffer cmdbuf) {
const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f;
const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f;
const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f;
const VkClearValue clear_color{
.color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}},
};
const VkRenderPassBeginInfo renderpass_bi{
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.pNext = nullptr,
.renderPass = *renderpass,
.framebuffer = host_framebuffer,
.renderArea =
{
.offset = {0, 0},
.extent = size,
},
.clearValueCount = 1,
.pClearValues = &clear_color,
};
const VkViewport viewport{
.x = 0.0f,
.y = 0.0f,
.width = static_cast<float>(size.width),
.height = static_cast<float>(size.height),
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
const VkRect2D scissor{
.offset = {0, 0},
.extent = size,
};
cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
auto graphics_pipeline = [this]() {
switch (Settings::values.scaling_filter.GetValue()) {
case Settings::ScalingFilter::NearestNeighbor:
case Settings::ScalingFilter::Bilinear:
return *bilinear_pipeline;
case Settings::ScalingFilter::Bicubic:
return *bicubic_pipeline;
case Settings::ScalingFilter::Gaussian:
return *gaussian_pipeline;
case Settings::ScalingFilter::ScaleForce:
return *scaleforce_pipeline;
default:
return *bilinear_pipeline;
}
}();
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline);
cmdbuf.SetViewport(0, viewport);
cmdbuf.SetScissor(0, scissor);
scheduler.Record([this, host_framebuffer, index = image_index,
size = render_area](vk::CommandBuffer cmdbuf) {
const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f;
const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f;
const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f;
const VkClearValue clear_color{
.color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}},
};
const VkRenderPassBeginInfo renderpass_bi{
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.pNext = nullptr,
.renderPass = *renderpass,
.framebuffer = host_framebuffer,
.renderArea =
{
.offset = {0, 0},
.extent = size,
},
.clearValueCount = 1,
.pClearValues = &clear_color,
};
const VkViewport viewport{
.x = 0.0f,
.y = 0.0f,
.width = static_cast<float>(size.width),
.height = static_cast<float>(size.height),
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
const VkRect2D scissor{
.offset = {0, 0},
.extent = size,
};
cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
auto graphics_pipeline = [this]() {
switch (Settings::values.scaling_filter.GetValue()) {
case Settings::ScalingFilter::NearestNeighbor:
case Settings::ScalingFilter::Bilinear:
return *bilinear_pipeline;
case Settings::ScalingFilter::Bicubic:
return *bicubic_pipeline;
case Settings::ScalingFilter::Gaussian:
return *gaussian_pipeline;
case Settings::ScalingFilter::ScaleForce:
return *scaleforce_pipeline;
default:
return *bilinear_pipeline;
}
}();
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline);
cmdbuf.SetViewport(0, viewport);
cmdbuf.SetScissor(0, scissor);
cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices));
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0,
descriptor_sets[image_index], {});
cmdbuf.Draw(4, 1, 0, 0);
cmdbuf.EndRenderPass();
});
return *semaphores[image_index];
cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices));
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0,
descriptor_sets[index], {});
cmdbuf.Draw(4, 1, 0, 0);
cmdbuf.EndRenderPass();
});
}
VkSemaphore BlitScreen::DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer,
bool use_accelerated) {
const std::size_t image_index = swapchain.GetImageIndex();
const VkExtent2D render_area = swapchain.GetSize();
void BlitScreen::DrawToSwapchain(Frame* frame, const Tegra::FramebufferConfig& framebuffer,
bool use_accelerated, bool is_srgb) {
// Recreate dynamic resources if the the image count or colorspace changed
if (const std::size_t swapchain_images = swapchain.GetImageCount();
swapchain_images != image_count || current_srgb != is_srgb) {
current_srgb = is_srgb;
image_view_format = current_srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
image_count = swapchain_images;
Recreate();
}
// Recreate the presentation frame if the dimensions of the window changed
const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
return Draw(framebuffer, *framebuffers[image_index], layout, render_area, use_accelerated);
if (layout.width != frame->width || layout.height != frame->height ||
is_srgb != frame->is_srgb) {
Recreate();
present_manager.RecreateFrame(frame, layout.width, layout.height, is_srgb,
image_view_format, *renderpass);
}
const VkExtent2D render_area{frame->width, frame->height};
Draw(framebuffer, *frame->framebuffer, layout, render_area, use_accelerated);
if (++image_index >= image_count) {
image_index = 0;
}
}
vk::Framebuffer BlitScreen::CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent) {
@@ -471,13 +487,11 @@ void BlitScreen::CreateStaticResources() {
}
void BlitScreen::CreateDynamicResources() {
CreateSemaphores();
CreateDescriptorPool();
CreateDescriptorSetLayout();
CreateDescriptorSets();
CreatePipelineLayout();
CreateRenderPass();
CreateFramebuffers();
CreateGraphicsPipeline();
fsr.reset();
smaa.reset();
@@ -525,11 +539,6 @@ void BlitScreen::CreateShaders() {
}
}
void BlitScreen::CreateSemaphores() {
semaphores.resize(image_count);
std::ranges::generate(semaphores, [this] { return device.GetLogical().CreateSemaphore(); });
}
void BlitScreen::CreateDescriptorPool() {
const std::array<VkDescriptorPoolSize, 2> pool_sizes{{
{
@@ -571,10 +580,10 @@ void BlitScreen::CreateDescriptorPool() {
}
void BlitScreen::CreateRenderPass() {
renderpass = CreateRenderPassImpl(swapchain.GetImageViewFormat());
renderpass = CreateRenderPassImpl(image_view_format);
}
vk::RenderPass BlitScreen::CreateRenderPassImpl(VkFormat format, bool is_present) {
vk::RenderPass BlitScreen::CreateRenderPassImpl(VkFormat format) {
const VkAttachmentDescription color_attachment{
.flags = 0,
.format = format,
@@ -584,7 +593,7 @@ vk::RenderPass BlitScreen::CreateRenderPassImpl(VkFormat format, bool is_present
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = is_present ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_GENERAL,
.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
};
const VkAttachmentReference color_attachment_ref{
@@ -1052,16 +1061,6 @@ void BlitScreen::CreateSampler() {
nn_sampler = device.GetLogical().CreateSampler(ci_nn);
}
void BlitScreen::CreateFramebuffers() {
const VkExtent2D size{swapchain.GetSize()};
framebuffers.resize(image_count);
for (std::size_t i = 0; i < image_count; ++i) {
const VkImageView image_view{swapchain.GetImageViewIndex(i)};
framebuffers[i] = CreateFramebuffer(image_view, size, renderpass);
}
}
void BlitScreen::ReleaseRawImages() {
for (const u64 tick : resource_ticks) {
scheduler.Wait(tick);
@@ -1175,7 +1174,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
aa_framebuffer = CreateFramebuffer(*aa_image_view, size, aa_renderpass);
return;
}
aa_renderpass = CreateRenderPassImpl(GetFormat(framebuffer), false);
aa_renderpass = CreateRenderPassImpl(GetFormat(framebuffer));
aa_framebuffer = CreateFramebuffer(*aa_image_view, size, aa_renderpass);
const std::array<VkPipelineShaderStageCreateInfo, 2> fxaa_shader_stages{{
@@ -1319,8 +1318,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
aa_pipeline = device.GetLogical().CreateGraphicsPipeline(fxaa_pipeline_ci);
}
void BlitScreen::UpdateAADescriptorSet(std::size_t image_index, VkImageView image_view,
bool nn) const {
void BlitScreen::UpdateAADescriptorSet(VkImageView image_view, bool nn) const {
const VkDescriptorImageInfo image_info{
.sampler = nn ? *nn_sampler : *sampler,
.imageView = image_view,
@@ -1356,8 +1354,7 @@ void BlitScreen::UpdateAADescriptorSet(std::size_t image_index, VkImageView imag
device.GetLogical().UpdateDescriptorSets(std::array{sampler_write, sampler_write_2}, {});
}
void BlitScreen::UpdateDescriptorSet(std::size_t image_index, VkImageView image_view,
bool nn) const {
void BlitScreen::UpdateDescriptorSet(VkImageView image_view, bool nn) const {
const VkDescriptorBufferInfo buffer_info{
.buffer = *buffer,
.offset = offsetof(BufferData, uniform),
@@ -1480,8 +1477,7 @@ u64 BlitScreen::CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer)
return sizeof(BufferData) + GetSizeInBytes(framebuffer) * image_count;
}
u64 BlitScreen::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
std::size_t image_index) const {
u64 BlitScreen::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer) const {
constexpr auto first_image_offset = static_cast<u64>(sizeof(BufferData));
return first_image_offset + GetSizeInBytes(framebuffer) * image_index;
}
+18 -16
View File
@@ -5,6 +5,7 @@
#include <memory>
#include "core/frontend/framebuffer_layout.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -42,6 +43,9 @@ class RasterizerVulkan;
class Scheduler;
class SMAA;
class Swapchain;
class PresentManager;
struct Frame;
struct ScreenInfo {
VkImage image{};
@@ -55,18 +59,17 @@ class BlitScreen {
public:
explicit BlitScreen(Core::Memory::Memory& cpu_memory, Core::Frontend::EmuWindow& render_window,
const Device& device, MemoryAllocator& memory_manager, Swapchain& swapchain,
Scheduler& scheduler, const ScreenInfo& screen_info);
PresentManager& present_manager, Scheduler& scheduler,
const ScreenInfo& screen_info);
~BlitScreen();
void Recreate();
[[nodiscard]] VkSemaphore Draw(const Tegra::FramebufferConfig& framebuffer,
const VkFramebuffer& host_framebuffer,
const Layout::FramebufferLayout layout, VkExtent2D render_area,
bool use_accelerated);
void Draw(const Tegra::FramebufferConfig& framebuffer, const VkFramebuffer& host_framebuffer,
const Layout::FramebufferLayout layout, VkExtent2D render_area, bool use_accelerated);
[[nodiscard]] VkSemaphore DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer,
bool use_accelerated);
void DrawToSwapchain(Frame* frame, const Tegra::FramebufferConfig& framebuffer,
bool use_accelerated, bool is_srgb);
[[nodiscard]] vk::Framebuffer CreateFramebuffer(const VkImageView& image_view,
VkExtent2D extent);
@@ -79,10 +82,9 @@ private:
void CreateStaticResources();
void CreateShaders();
void CreateSemaphores();
void CreateDescriptorPool();
void CreateRenderPass();
vk::RenderPass CreateRenderPassImpl(VkFormat, bool is_present = true);
vk::RenderPass CreateRenderPassImpl(VkFormat format);
void CreateDescriptorSetLayout();
void CreateDescriptorSets();
void CreatePipelineLayout();
@@ -90,15 +92,14 @@ private:
void CreateSampler();
void CreateDynamicResources();
void CreateFramebuffers();
void RefreshResources(const Tegra::FramebufferConfig& framebuffer);
void ReleaseRawImages();
void CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer);
void CreateRawImages(const Tegra::FramebufferConfig& framebuffer);
void UpdateDescriptorSet(std::size_t image_index, VkImageView image_view, bool nn) const;
void UpdateAADescriptorSet(std::size_t image_index, VkImageView image_view, bool nn) const;
void UpdateDescriptorSet(VkImageView image_view, bool nn) const;
void UpdateAADescriptorSet(VkImageView image_view, bool nn) const;
void SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const;
void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer,
const Layout::FramebufferLayout layout) const;
@@ -107,16 +108,17 @@ private:
void CreateFSR();
u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const;
u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
std::size_t image_index) const;
u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer) const;
Core::Memory::Memory& cpu_memory;
Core::Frontend::EmuWindow& render_window;
const Device& device;
MemoryAllocator& memory_allocator;
Swapchain& swapchain;
PresentManager& present_manager;
Scheduler& scheduler;
std::size_t image_count;
std::size_t image_index{};
const ScreenInfo& screen_info;
vk::ShaderModule vertex_shader;
@@ -135,7 +137,6 @@ private:
vk::Pipeline gaussian_pipeline;
vk::Pipeline scaleforce_pipeline;
vk::RenderPass renderpass;
std::vector<vk::Framebuffer> framebuffers;
vk::DescriptorSets descriptor_sets;
vk::Sampler nn_sampler;
vk::Sampler sampler;
@@ -145,7 +146,6 @@ private:
std::vector<u64> resource_ticks;
std::vector<vk::Semaphore> semaphores;
std::vector<vk::Image> raw_images;
std::vector<vk::ImageView> raw_image_views;
std::vector<MemoryCommit> raw_buffer_commits;
@@ -164,6 +164,8 @@ private:
u32 raw_width = 0;
u32 raw_height = 0;
Service::android::PixelFormat pixel_format{};
bool current_srgb;
VkFormat image_view_format;
std::unique_ptr<FSR> fsr;
std::unique_ptr<SMAA> smaa;
@@ -314,8 +314,12 @@ StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
return staging_pool.Request(size, MemoryUsage::Upload);
}
StagingBufferRef BufferCacheRuntime::DownloadStagingBuffer(size_t size) {
return staging_pool.Request(size, MemoryUsage::Download);
StagingBufferRef BufferCacheRuntime::DownloadStagingBuffer(size_t size, bool deferred) {
return staging_pool.Request(size, MemoryUsage::Download, deferred);
}
void BufferCacheRuntime::FreeDeferredStagingBuffer(StagingBufferRef& ref) {
staging_pool.FreeDeferred(ref);
}
u64 BufferCacheRuntime::GetDeviceLocalMemory() const {
@@ -3,7 +3,8 @@
#pragma once
#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/buffer_cache/memory_tracker_base.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -75,7 +76,9 @@ public:
[[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size);
[[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size);
[[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size, bool deferred = false);
void FreeDeferredStagingBuffer(StagingBufferRef& ref);
void PreCopyBarrier();
@@ -142,6 +145,8 @@ private:
struct BufferCacheParams {
using Runtime = Vulkan::BufferCacheRuntime;
using Buffer = Vulkan::Buffer;
using Async_Buffer = Vulkan::StagingBufferRef;
using MemoryTracker = VideoCommon::MemoryTrackerBase<VideoCore::RasterizerInterface>;
static constexpr bool IS_OPENGL = false;
static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS = false;
@@ -150,6 +155,7 @@ struct BufferCacheParams {
static constexpr bool NEEDS_BIND_STORAGE_INDEX = false;
static constexpr bool USE_MEMORY_MAPS = true;
static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = false;
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
};
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
namespace VideoCommon {
template class VideoCommon::BufferCache<Vulkan::BufferCacheParams>;
}
@@ -5,6 +5,7 @@
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_fence_manager.h"
#include "video_core/renderer_vulkan/vk_query_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/vulkan_common/vulkan_device.h"
@@ -40,7 +40,16 @@ private:
};
using Fence = std::shared_ptr<InnerFence>;
using GenericFenceManager = VideoCommon::FenceManager<Fence, TextureCache, BufferCache, QueryCache>;
struct FenceManagerParams {
using FenceType = Fence;
using BufferCacheType = BufferCache;
using TextureCacheType = TextureCache;
using QueryCacheType = QueryCache;
static constexpr bool HAS_ASYNC_CHECK = true;
};
using GenericFenceManager = VideoCommon::FenceManager<FenceManagerParams>;
class FenceManager final : public GenericFenceManager {
public:
@@ -10,7 +10,14 @@
namespace Vulkan {
MasterSemaphore::MasterSemaphore(const Device& device) {
MasterSemaphore::MasterSemaphore(const Device& device_) : device(device_) {
if (!device.HasTimelineSemaphore()) {
static constexpr VkFenceCreateInfo fence_ci{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = nullptr, .flags = 0};
fence = device.GetLogical().CreateFence(fence_ci);
return;
}
static constexpr VkSemaphoreTypeCreateInfo semaphore_type_ci{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO,
.pNext = nullptr,
@@ -42,4 +49,134 @@ MasterSemaphore::MasterSemaphore(const Device& device) {
MasterSemaphore::~MasterSemaphore() = default;
void MasterSemaphore::Refresh() {
if (!semaphore) {
// If we don't support timeline semaphores, there's nothing to refresh
return;
}
u64 this_tick{};
u64 counter{};
do {
this_tick = gpu_tick.load(std::memory_order_acquire);
counter = semaphore.GetCounter();
if (counter < this_tick) {
return;
}
} while (!gpu_tick.compare_exchange_weak(this_tick, counter, std::memory_order_release,
std::memory_order_relaxed));
}
void MasterSemaphore::Wait(u64 tick) {
if (!semaphore) {
// If we don't support timeline semaphores, use an atomic wait
while (true) {
u64 current_value = gpu_tick.load(std::memory_order_relaxed);
if (current_value >= tick) {
return;
}
gpu_tick.wait(current_value);
}
return;
}
// No need to wait if the GPU is ahead of the tick
if (IsFree(tick)) {
return;
}
// Update the GPU tick and try again
Refresh();
if (IsFree(tick)) {
return;
}
// If none of the above is hit, fallback to a regular wait
while (!semaphore.Wait(tick)) {
}
Refresh();
}
VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore,
VkSemaphore wait_semaphore, u64 host_tick) {
if (semaphore) {
return SubmitQueueTimeline(cmdbuf, signal_semaphore, wait_semaphore, host_tick);
} else {
return SubmitQueueFence(cmdbuf, signal_semaphore, wait_semaphore, host_tick);
}
}
static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
};
VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
VkSemaphore signal_semaphore,
VkSemaphore wait_semaphore, u64 host_tick) {
const VkSemaphore timeline_semaphore = *semaphore;
const u32 num_signal_semaphores = signal_semaphore ? 2 : 1;
const std::array signal_values{host_tick, u64(0)};
const std::array signal_semaphores{timeline_semaphore, signal_semaphore};
const u32 num_wait_semaphores = wait_semaphore ? 2 : 1;
const std::array wait_values{host_tick - 1, u64(1)};
const std::array wait_semaphores{timeline_semaphore, wait_semaphore};
const VkTimelineSemaphoreSubmitInfo timeline_si{
.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
.pNext = nullptr,
.waitSemaphoreValueCount = num_wait_semaphores,
.pWaitSemaphoreValues = wait_values.data(),
.signalSemaphoreValueCount = num_signal_semaphores,
.pSignalSemaphoreValues = signal_values.data(),
};
const VkSubmitInfo submit_info{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pNext = &timeline_si,
.waitSemaphoreCount = num_wait_semaphores,
.pWaitSemaphores = wait_semaphores.data(),
.pWaitDstStageMask = wait_stage_masks.data(),
.commandBufferCount = 1,
.pCommandBuffers = cmdbuf.address(),
.signalSemaphoreCount = num_signal_semaphores,
.pSignalSemaphores = signal_semaphores.data(),
};
return device.GetGraphicsQueue().Submit(submit_info);
}
VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore,
VkSemaphore wait_semaphore, u64 host_tick) {
const u32 num_signal_semaphores = signal_semaphore ? 1 : 0;
const u32 num_wait_semaphores = wait_semaphore ? 1 : 0;
const VkSubmitInfo submit_info{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pNext = nullptr,
.waitSemaphoreCount = num_wait_semaphores,
.pWaitSemaphores = &wait_semaphore,
.pWaitDstStageMask = wait_stage_masks.data(),
.commandBufferCount = 1,
.pCommandBuffers = cmdbuf.address(),
.signalSemaphoreCount = num_signal_semaphores,
.pSignalSemaphores = &signal_semaphore,
};
auto result = device.GetGraphicsQueue().Submit(submit_info, *fence);
if (result == VK_SUCCESS) {
fence.Wait();
fence.Reset();
gpu_tick.store(host_tick);
gpu_tick.notify_all();
}
return result;
}
} // namespace Vulkan
@@ -4,6 +4,8 @@
#pragma once
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <thread>
#include "common/common_types.h"
@@ -29,11 +31,6 @@ public:
return gpu_tick.load(std::memory_order_acquire);
}
/// Returns the timeline semaphore handle.
[[nodiscard]] VkSemaphore Handle() const noexcept {
return *semaphore;
}
/// Returns true when a tick has been hit by the GPU.
[[nodiscard]] bool IsFree(u64 tick) const noexcept {
return KnownGpuTick() >= tick;
@@ -45,37 +42,24 @@ public:
}
/// Refresh the known GPU tick
void Refresh() {
u64 this_tick{};
u64 counter{};
do {
this_tick = gpu_tick.load(std::memory_order_acquire);
counter = semaphore.GetCounter();
if (counter < this_tick) {
return;
}
} while (!gpu_tick.compare_exchange_weak(this_tick, counter, std::memory_order_release,
std::memory_order_relaxed));
}
void Refresh();
/// Waits for a tick to be hit on the GPU
void Wait(u64 tick) {
// No need to wait if the GPU is ahead of the tick
if (IsFree(tick)) {
return;
}
// Update the GPU tick and try again
Refresh();
if (IsFree(tick)) {
return;
}
// If none of the above is hit, fallback to a regular wait
while (!semaphore.Wait(tick)) {
}
Refresh();
}
void Wait(u64 tick);
/// Submits the device graphics queue, updating the tick as necessary
VkResult SubmitQueue(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore,
VkSemaphore wait_semaphore, u64 host_tick);
private:
VkResult SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore,
VkSemaphore wait_semaphore, u64 host_tick);
VkResult SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore,
VkSemaphore wait_semaphore, u64 host_tick);
private:
const Device& device; ///< Device.
vk::Fence fence; ///< Fence.
vk::Semaphore semaphore; ///< Timeline semaphore.
std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick.
std::atomic<u64> current_tick{1}; ///< Current logical tick.
@@ -696,6 +696,13 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env,
PipelineStatistics* statistics, bool build_in_parallel) try {
// TODO: Remove this when Intel fixes their shader compiler.
// https://github.com/IGCIT/Intel-GPU-Community-Issue-Tracker-IGCIT/issues/159
if (device.GetDriverID() == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
LOG_ERROR(Render_Vulkan, "Skipping 0x{:016x}", key.Hash());
return nullptr;
}
LOG_INFO(Render_Vulkan, "0x{:016x}", key.Hash());
Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()};
@@ -0,0 +1,457 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/microprofile.h"
#include "common/settings.h"
#include "common/thread.h"
#include "video_core/renderer_vulkan/vk_present_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
MICROPROFILE_DEFINE(Vulkan_WaitPresent, "Vulkan", "Wait For Present", MP_RGB(128, 128, 128));
MICROPROFILE_DEFINE(Vulkan_CopyToSwapchain, "Vulkan", "Copy to swapchain", MP_RGB(192, 255, 192));
namespace {
bool CanBlitToSwapchain(const vk::PhysicalDevice& physical_device, VkFormat format) {
const VkFormatProperties props{physical_device.GetFormatProperties(format)};
return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT);
}
[[nodiscard]] VkImageSubresourceLayers MakeImageSubresourceLayers() {
return VkImageSubresourceLayers{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
};
}
[[nodiscard]] VkImageBlit MakeImageBlit(s32 frame_width, s32 frame_height, s32 swapchain_width,
s32 swapchain_height) {
return VkImageBlit{
.srcSubresource = MakeImageSubresourceLayers(),
.srcOffsets =
{
{
.x = 0,
.y = 0,
.z = 0,
},
{
.x = frame_width,
.y = frame_height,
.z = 1,
},
},
.dstSubresource = MakeImageSubresourceLayers(),
.dstOffsets =
{
{
.x = 0,
.y = 0,
.z = 0,
},
{
.x = swapchain_width,
.y = swapchain_height,
.z = 1,
},
},
};
}
[[nodiscard]] VkImageCopy MakeImageCopy(u32 frame_width, u32 frame_height, u32 swapchain_width,
u32 swapchain_height) {
return VkImageCopy{
.srcSubresource = MakeImageSubresourceLayers(),
.srcOffset =
{
.x = 0,
.y = 0,
.z = 0,
},
.dstSubresource = MakeImageSubresourceLayers(),
.dstOffset =
{
.x = 0,
.y = 0,
.z = 0,
},
.extent =
{
.width = std::min(frame_width, swapchain_width),
.height = std::min(frame_height, swapchain_height),
.depth = 1,
},
};
}
} // Anonymous namespace
PresentManager::PresentManager(Core::Frontend::EmuWindow& render_window_, const Device& device_,
MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
Swapchain& swapchain_)
: render_window{render_window_}, device{device_},
memory_allocator{memory_allocator_}, scheduler{scheduler_}, swapchain{swapchain_},
blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())},
use_present_thread{Settings::values.async_presentation.GetValue()},
image_count{swapchain.GetImageCount()} {
auto& dld = device.GetLogical();
cmdpool = dld.CreateCommandPool({
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.pNext = nullptr,
.flags =
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = device.GetGraphicsFamily(),
});
auto cmdbuffers = cmdpool.Allocate(image_count);
frames.resize(image_count);
for (u32 i = 0; i < frames.size(); i++) {
Frame& frame = frames[i];
frame.cmdbuf = vk::CommandBuffer{cmdbuffers[i], device.GetDispatchLoader()};
frame.render_ready = dld.CreateSemaphore({
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
});
frame.present_done = dld.CreateFence({
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.pNext = nullptr,
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
});
free_queue.push(&frame);
}
if (use_present_thread) {
present_thread = std::jthread([this](std::stop_token token) { PresentThread(token); });
}
}
PresentManager::~PresentManager() = default;
Frame* PresentManager::GetRenderFrame() {
MICROPROFILE_SCOPE(Vulkan_WaitPresent);
// Wait for free presentation frames
std::unique_lock lock{free_mutex};
free_cv.wait(lock, [this] { return !free_queue.empty(); });
// Take the frame from the queue
Frame* frame = free_queue.front();
free_queue.pop();
// Wait for the presentation to be finished so all frame resources are free
frame->present_done.Wait();
frame->present_done.Reset();
return frame;
}
void PresentManager::Present(Frame* frame) {
if (!use_present_thread) {
scheduler.WaitWorker();
CopyToSwapchain(frame);
free_queue.push(frame);
return;
}
scheduler.Record([this, frame](vk::CommandBuffer) {
std::unique_lock lock{queue_mutex};
present_queue.push(frame);
frame_cv.notify_one();
});
}
void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, bool is_srgb,
VkFormat image_view_format, VkRenderPass rd) {
auto& dld = device.GetLogical();
frame->width = width;
frame->height = height;
frame->is_srgb = is_srgb;
frame->image = dld.CreateImage({
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
.imageType = VK_IMAGE_TYPE_2D,
.format = swapchain.GetImageFormat(),
.extent =
{
.width = width,
.height = height,
.depth = 1,
},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
});
frame->image_commit = memory_allocator.Commit(frame->image, MemoryUsage::DeviceLocal);
frame->image_view = dld.CreateImageView({
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.image = *frame->image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = image_view_format,
.components =
{
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
.g = VK_COMPONENT_SWIZZLE_IDENTITY,
.b = VK_COMPONENT_SWIZZLE_IDENTITY,
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
},
.subresourceRange =
{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
});
const VkImageView image_view{*frame->image_view};
frame->framebuffer = dld.CreateFramebuffer({
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.renderPass = rd,
.attachmentCount = 1,
.pAttachments = &image_view,
.width = width,
.height = height,
.layers = 1,
});
}
void PresentManager::WaitPresent() {
if (!use_present_thread) {
return;
}
// Wait for the present queue to be empty
{
std::unique_lock queue_lock{queue_mutex};
frame_cv.wait(queue_lock, [this] { return present_queue.empty(); });
}
// The above condition will be satisfied when the last frame is taken from the queue.
// To ensure that frame has been presented as well take hold of the swapchain
// mutex.
std::scoped_lock swapchain_lock{swapchain_mutex};
}
void PresentManager::PresentThread(std::stop_token token) {
Common::SetCurrentThreadName("VulkanPresent");
while (!token.stop_requested()) {
std::unique_lock lock{queue_mutex};
// Wait for presentation frames
Common::CondvarWait(frame_cv, lock, token, [this] { return !present_queue.empty(); });
if (token.stop_requested()) {
return;
}
// Take the frame and notify anyone waiting
Frame* frame = present_queue.front();
present_queue.pop();
frame_cv.notify_one();
// By exchanging the lock ownership we take the swapchain lock
// before the queue lock goes out of scope. This way the swapchain
// lock in WaitPresent is guaranteed to occur after here.
std::exchange(lock, std::unique_lock{swapchain_mutex});
CopyToSwapchain(frame);
// Free the frame for reuse
std::scoped_lock fl{free_mutex};
free_queue.push(frame);
free_cv.notify_one();
}
}
void PresentManager::CopyToSwapchain(Frame* frame) {
MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
const auto recreate_swapchain = [&] {
swapchain.Create(frame->width, frame->height, frame->is_srgb);
image_count = swapchain.GetImageCount();
};
// If the size or colorspace of the incoming frames has changed, recreate the swapchain
// to account for that.
const bool srgb_changed = swapchain.NeedsRecreation(frame->is_srgb);
const bool size_changed =
swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height;
if (srgb_changed || size_changed) {
recreate_swapchain();
}
while (swapchain.AcquireNextImage()) {
recreate_swapchain();
}
const vk::CommandBuffer cmdbuf{frame->cmdbuf};
cmdbuf.Begin({
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.pNext = nullptr,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
.pInheritanceInfo = nullptr,
});
const VkImage image{swapchain.CurrentImage()};
const VkExtent2D extent = swapchain.GetExtent();
const std::array pre_barriers{
VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image,
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
},
VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = *frame->image,
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
},
};
const std::array post_barriers{
VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image,
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
},
VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = *frame->image,
.subresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, {},
{}, {}, pre_barriers);
if (blit_supported) {
cmdbuf.BlitImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
MakeImageBlit(frame->width, frame->height, extent.width, extent.height),
VK_FILTER_LINEAR);
} else {
cmdbuf.CopyImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
MakeImageCopy(frame->width, frame->height, extent.width, extent.height));
}
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, {},
{}, {}, post_barriers);
cmdbuf.End();
const VkSemaphore present_semaphore = swapchain.CurrentPresentSemaphore();
const VkSemaphore render_semaphore = swapchain.CurrentRenderSemaphore();
const std::array wait_semaphores = {present_semaphore, *frame->render_ready};
static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
};
const VkSubmitInfo submit_info{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pNext = nullptr,
.waitSemaphoreCount = 2U,
.pWaitSemaphores = wait_semaphores.data(),
.pWaitDstStageMask = wait_stage_masks.data(),
.commandBufferCount = 1,
.pCommandBuffers = cmdbuf.address(),
.signalSemaphoreCount = 1U,
.pSignalSemaphores = &render_semaphore,
};
// Submit the image copy/blit to the swapchain
{
std::scoped_lock lock{scheduler.submit_mutex};
switch (const VkResult result =
device.GetGraphicsQueue().Submit(submit_info, *frame->present_done)) {
case VK_SUCCESS:
break;
case VK_ERROR_DEVICE_LOST:
device.ReportLoss();
[[fallthrough]];
default:
vk::Check(result);
break;
}
}
// Present
swapchain.Present(render_semaphore);
}
} // namespace Vulkan
@@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <mutex>
#include <queue>
#include "common/common_types.h"
#include "common/polyfill_thread.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Core::Frontend {
class EmuWindow;
} // namespace Core::Frontend
namespace Vulkan {
class Device;
class Scheduler;
class Swapchain;
struct Frame {
u32 width;
u32 height;
bool is_srgb;
vk::Image image;
vk::ImageView image_view;
vk::Framebuffer framebuffer;
MemoryCommit image_commit;
vk::CommandBuffer cmdbuf;
vk::Semaphore render_ready;
vk::Fence present_done;
};
class PresentManager {
public:
PresentManager(Core::Frontend::EmuWindow& render_window, const Device& device,
MemoryAllocator& memory_allocator, Scheduler& scheduler, Swapchain& swapchain);
~PresentManager();
/// Returns the last used presentation frame
Frame* GetRenderFrame();
/// Pushes a frame for presentation
void Present(Frame* frame);
/// Recreates the present frame to match the provided parameters
void RecreateFrame(Frame* frame, u32 width, u32 height, bool is_srgb,
VkFormat image_view_format, VkRenderPass rd);
/// Waits for the present thread to finish presenting all queued frames.
void WaitPresent();
private:
void PresentThread(std::stop_token token);
void CopyToSwapchain(Frame* frame);
private:
Core::Frontend::EmuWindow& render_window;
const Device& device;
MemoryAllocator& memory_allocator;
Scheduler& scheduler;
Swapchain& swapchain;
vk::CommandPool cmdpool;
std::vector<Frame> frames;
std::queue<Frame*> present_queue;
std::queue<Frame*> free_queue;
std::condition_variable_any frame_cv;
std::condition_variable free_cv;
std::mutex swapchain_mutex;
std::mutex queue_mutex;
std::mutex free_mutex;
std::jthread present_thread;
bool blit_supported;
bool use_present_thread;
std::size_t image_count;
};
} // namespace Vulkan
@@ -66,9 +66,10 @@ void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) {
}
}
QueryCache::QueryCache(VideoCore::RasterizerInterface& rasterizer_, const Device& device_,
QueryCache::QueryCache(VideoCore::RasterizerInterface& rasterizer_,
Core::Memory::Memory& cpu_memory_, const Device& device_,
Scheduler& scheduler_)
: QueryCacheBase{rasterizer_}, device{device_}, scheduler{scheduler_},
: QueryCacheBase{rasterizer_, cpu_memory_}, device{device_}, scheduler{scheduler_},
query_pools{
QueryPool{device_, scheduler_, QueryType::SamplesPassed},
} {}
@@ -98,8 +99,10 @@ HostCounter::HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> depend
query{cache_.AllocateQuery(type_)}, tick{cache_.GetScheduler().CurrentTick()} {
const vk::Device* logical = &cache.GetDevice().GetLogical();
cache.GetScheduler().Record([logical, query = query](vk::CommandBuffer cmdbuf) {
const bool use_precise = Settings::IsGPULevelHigh();
logical->ResetQueryPool(query.first, query.second, 1);
cmdbuf.BeginQuery(query.first, query.second, VK_QUERY_CONTROL_PRECISE_BIT);
cmdbuf.BeginQuery(query.first, query.second,
use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0);
});
}
@@ -112,8 +115,10 @@ void HostCounter::EndQuery() {
[query = query](vk::CommandBuffer cmdbuf) { cmdbuf.EndQuery(query.first, query.second); });
}
u64 HostCounter::BlockingQuery() const {
cache.GetScheduler().Wait(tick);
u64 HostCounter::BlockingQuery(bool async) const {
if (!async) {
cache.GetScheduler().Wait(tick);
}
u64 data;
const VkResult query_result = cache.GetDevice().GetLogical().GetQueryResults(
query.first, query.second, 1, sizeof(data), &data, sizeof(data),
@@ -52,7 +52,8 @@ private:
class QueryCache final
: public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> {
public:
explicit QueryCache(VideoCore::RasterizerInterface& rasterizer_, const Device& device_,
explicit QueryCache(VideoCore::RasterizerInterface& rasterizer_,
Core::Memory::Memory& cpu_memory_, const Device& device_,
Scheduler& scheduler_);
~QueryCache();
@@ -83,7 +84,7 @@ public:
void EndQuery();
private:
u64 BlockingQuery() const override;
u64 BlockingQuery(bool async = false) const override;
QueryCache& cache;
const VideoCore::QueryType type;

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