Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b095a0242d | |||
| 2506594c50 | |||
| 7d5683c63c | |||
| 055ee84024 | |||
| ae59ffc56c | |||
| 1c13c74295 | |||
| a661c547d8 | |||
| 737e1ca101 | |||
| 87a9be8dec | |||
| ffeb5cdd8d | |||
| 3ba95402fd | |||
| 8dd3baa562 | |||
| daf7936095 | |||
| 627022bef9 | |||
| f3fcc15ad5 | |||
| d1dd54cbfa | |||
| 8f43b05d6b | |||
| 451b1bba26 | |||
| 494cc992eb | |||
| f603dc9763 | |||
| 4df49631de | |||
| e1c74cea10 | |||
| f7292c776b | |||
| 2007d0e4a0 | |||
| f403d27941 | |||
| 1d7abac84b | |||
| 50791cb974 | |||
| 2ad9acf795 | |||
| 47938541c2 | |||
| 1f079d9566 | |||
| 3f4315e4c6 | |||
| d6f565e5da | |||
| 7ec66db22c | |||
| 2cd9e1ecb6 | |||
| b566c98258 | |||
| 689f9a75a7 | |||
| b143ce8134 | |||
| 1b5c87ab6a | |||
| 2afaa7aed7 | |||
| fe57f39676 | |||
| 4bc5469f52 | |||
| f8d31d1ae1 | |||
| fff6155bc3 | |||
| 80480fe3f7 | |||
| ed4553806a | |||
| f2d3212de9 | |||
| a16c261131 | |||
| 58d1c7c77a | |||
| 56c9730a16 | |||
| e3a2ca96bd | |||
| 3fbee093b2 | |||
| cb092af3f0 | |||
| 2f15876524 | |||
| 9a7c172f76 | |||
| e4dc73f61e | |||
| 9bf19b04f6 | |||
| 47cd0586ee | |||
| 2311fa7c84 | |||
| e29ced29fa | |||
| 7e76c1642c | |||
| fca72beb2d | |||
| eb7c2314f6 | |||
| d600183583 | |||
| 0f1ff5f34e | |||
| d0e6eafe23 | |||
| 74d203fbe3 | |||
| 7ffc42c397 | |||
| 4e14b64bfc | |||
| 0cfeb2e8d7 | |||
| 79e32127b3 | |||
| 799579c8d2 | |||
| 3e7af5fbd7 | |||
| 34d0d94df0 | |||
| 70a97fb5c7 | |||
| e3fb9b5e00 | |||
| 307371e01d | |||
| 00800d5289 | |||
| 101c0df79c | |||
| e37e1d24f9 |
@@ -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"
|
||||
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
Vendored
+1
-1
@@ -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>
|
||||
|
||||
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
Vendored
+47
-47
@@ -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'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'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><html><head/><body><p>Server address of the host</p></body></html></source>
|
||||
<translation type="unfinished"/>
|
||||
<translation><html><head/><body><p>Dirección del servidor del anfitrión</p></body></html></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"/>
|
||||
|
||||
Vendored
+14
-14
@@ -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'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"/>
|
||||
|
||||
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
Vendored
+7
-7
@@ -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'esecuzione durante i caricamenti</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/yuzu/configuration/configure_tas.ui" line="91"/>
|
||||
@@ -5352,7 +5352,7 @@ Configurazione &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"/>
|
||||
|
||||
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
Vendored
+979
-937
File diff suppressed because it is too large
Load Diff
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
Vendored
+106
-69
@@ -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>
|
||||
<div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div>
|
||||
</source>
|
||||
<translation>
|
||||
<div>Эта опция повышает скорость, уменьшая точность сложенных умноженных инструкций на ЦП без поддержки FMA.</div>
|
||||
<div>Эта опция повышает скорость за счет снижения точности инструкций fused-multiply-add на ЦП без встроенной поддержки FMA.</div>
|
||||
</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>
|
||||
<div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div>
|
||||
</source>
|
||||
<translation>
|
||||
<div>Эта опция повышает скорость некоторых аппроксимирующих функций с плавающей точкой за счет использования менее точных нативных приближений.</div>
|
||||
<div>Эта опция повышает скорость некоторых приближенных функций с плавающей точкой за счет использования менее точных нативных приближений</div>
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -579,13 +579,13 @@ This would ban both their forum username and their IP address.</source>
|
||||
<div>This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes.</div>
|
||||
</source>
|
||||
<translation>
|
||||
<div>Эта опция улучшает скорость 32-битных ASIMD-функций с плавающей запятой путём работы с некорректными режимами округления.</div>
|
||||
<div>Эта опция повышает скорость работы 32-битных ASIMD-функций с плавающей запятой, работая с неправильными режимами округления.</div>
|
||||
</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>
|
||||
<div>This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.</div>
|
||||
</source>
|
||||
<translation>
|
||||
<div>Эта опция повышает скорость, убирая проверку на NaN. Обратите внимание, что это также снижает точность некоторых инструкций с плавающей точкой.</div>
|
||||
<div>Эта опция повышает скорость, удаляя проверку NaN. Обратите внимание, что это также снижает точность некоторых инструкций с плавающей точкой.</div>
|
||||
</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>
|
||||
<div>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.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div>Эта опция повышает скорость за счет исключения проверки безопасности перед каждым чтением/записью памяти в гостевом режиме. Отключение этой опции может позволить игре читать/записывать память эмулятора.
|
||||
</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>
|
||||
<div>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.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div>Эта опция повышает скорость, полагаясь только на семантику cmpxchg для обеспечения безопасности инструкций исключительного доступа. Обратите внимание, что это может привести к полным зависаниям и другим условиям гонки.</div>
|
||||
</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>
|
||||
<div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div>
|
||||
<div style="white-space: nowrap">Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div style="white-space: nowrap">Эта оптимизация ускоряет доступ гостевой программы к памяти.</div>
|
||||
<div style="white-space: nowrap"> Включение этой оптимизации встраивает доступ к указателям PageTable::pointers в эмулируемый код.</div>
|
||||
<div style="white-space: nowrap">Отключение этой функции заставляет все обращения к памяти проходить через функции Memory::Read/Memory::Write.</div>
|
||||
</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>
|
||||
<div>This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div>Эта оптимизация позволяет избежать поиска диспетчера, позволяя эмитированным базовым блокам переходить непосредственно к другим базовым блокам, если конечный ПК статичен.</div>
|
||||
</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>
|
||||
<div>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.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div>Эта оптимизация позволяет избежать поиска диспетчера, отслеживая потенциальные адреса возврата инструкций BL. Это приближено к тому, что происходит с буфером стека возврата на реальном ЦП.</div>
|
||||
</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>
|
||||
<div>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.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div>Включите двухуровневую систему диспетчеризации. Сначала используется более быстрый диспетчер, написанный на ассемблере и имеющий небольшой кэш MRU для мест назначения переходов. Если он не справляется, диспетчеризация возвращается к более медленному диспетчеру на C++.</div>
|
||||
</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>
|
||||
<div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div>Включает IR-оптимизацию, которая уменьшает ненужные обращения к структуре контекста ЦП.</div>
|
||||
</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>
|
||||
<div>Enables IR optimizations that involve constant propagation.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div>Включает IR-оптимизацию, которая включает распространение констант.</div>
|
||||
</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>
|
||||
<div>Enables miscellaneous IR optimizations.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div>Включает различные IR-оптимизации.</div>
|
||||
</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>
|
||||
<div style="white-space: nowrap">When enabled, a misalignment is only triggered when an access crosses a page boundary.</div>
|
||||
<div style="white-space: nowrap">When disabled, a misalignment is triggered on all misaligned accesses.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div style="white-space: nowrap">Если функция включена, смещение срабатывает только тогда, когда доступ пересекает границу страницы.</div>
|
||||
<div style="white-space: nowrap">Если отключено, смещение срабатывает при всех смещенных доступах.</div>
|
||||
</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>
|
||||
<div style="white-space: nowrap">Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU.</div>
|
||||
<div style="white-space: nowrap">Disabling this forces all memory accesses to use Software MMU Emulation.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div style="white-space: nowrap">Эта оптимизация ускоряет доступ гостевой программы к памяти.</div>
|
||||
<div style="white-space: nowrap"> Включение этой оптимизации приводит к тому, что чтение/запись гостевой памяти производится непосредственно в память и использует MMU хоста.</div>
|
||||
<div style="white-space: nowrap">Отключение этой функции заставляет все обращения к памяти использовать программную эмуляцию MMU.</div>
|
||||
</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>
|
||||
<div style="white-space: nowrap">Enabling it causes guest exclusive memory reads/writes to be done directly into memory and make use of Host's MMU.</div>
|
||||
<div style="white-space: nowrap">Disabling this forces all exclusive memory accesses to use Software MMU Emulation.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div style="white-space: nowrap">Эта оптимизация ускоряет доступ гостевой программы к эксклюзивной памяти.</div>
|
||||
<div style="white-space: nowrap">Включение этой оптимизации приводит к тому, что чтение/запись в эксклюзивную память гостя выполняется непосредственно в память и использует MMU хоста.</div>
|
||||
<div style="white-space: nowrap"> Отключение этой функции заставляет все эксклюзивные доступы к памяти использовать эмуляцию программного MMU.</div>
|
||||
</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>
|
||||
<div style="white-space: nowrap">This optimization speeds up exclusive memory accesses by the guest program.</div>
|
||||
<div style="white-space: nowrap">Enabling it reduces the overhead of fastmem failure of exclusive memory accesses.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div style="white-space: nowrap">Эта оптимизация ускоряет обращение гостевой программы к исключительной памяти.</div>
|
||||
<div style="white-space: nowrap">Ее включение снижает накладные расходы, связанные с отказом fastmem при доступе к исключительной памяти.</div>
|
||||
</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>
|
||||
<div style="white-space: nowrap">This optimization speeds up memory accesses by allowing invalid memory accesses to succeed.</div>
|
||||
<div style="white-space: nowrap">Enabling it reduces the overhead of all memory accesses and has no impact on programs that don't access invalid memory.</div>
|
||||
</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>
|
||||
<div style="white-space: nowrap">Эта оптимизация ускоряет обращение к памяти, позволяя успешное обращение к недопустимой памяти.</div>
|
||||
<div style="white-space: nowrap">Включение этой оптимизации снижает накладные расходы на все обращения к памяти и не влияет на программы, которые не обращаются к недопустимой памяти.</div>
|
||||
</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>
|
||||
|
||||
Vendored
+35
-35
@@ -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><html><head/><body><p>Does the game boot?</p></body></html></source>
|
||||
<translation type="unfinished"/>
|
||||
<translation><html><head/><body><p>Startar Spelet? </p></body></html></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't get past the "Launching..." screen</source>
|
||||
<translation type="unfinished"/>
|
||||
<translation>Nej Spelet öppnar ej förbi "Startar..." 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.</div>
|
||||
<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.</div>
|
||||
</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"/>
|
||||
|
||||
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
Vendored
+2
-2
@@ -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>Розширене компонування пам'яті (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"/>
|
||||
|
||||
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
Vendored
+2
-2
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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"};
|
||||
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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&) {}
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
+436
-59
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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), ×tamp_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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user