diff options
208 files changed, 8725 insertions, 6191 deletions
diff --git a/.lgtm.yml b/.lgtm.yml deleted file mode 100644 index 7cd3f9926..000000000 --- a/.lgtm.yml +++ /dev/null @@ -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" diff --git a/dist/languages/ca.ts b/dist/languages/ca.ts index b4e77d029..6978a4536 100644 --- a/dist/languages/ca.ts +++ b/dist/languages/ca.ts @@ -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"/> diff --git a/dist/languages/cs.ts b/dist/languages/cs.ts index b1b12c019..c7f77b15d 100644 --- a/dist/languages/cs.ts +++ b/dist/languages/cs.ts @@ -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> diff --git a/dist/languages/da.ts b/dist/languages/da.ts index 7c43ff7df..4bb799f90 100644 --- a/dist/languages/da.ts +++ b/dist/languages/da.ts @@ -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"/> diff --git a/dist/languages/de.ts b/dist/languages/de.ts index a455a3e78..47def0166 100644 --- a/dist/languages/de.ts +++ b/dist/languages/de.ts @@ -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"/> diff --git a/dist/languages/el.ts b/dist/languages/el.ts index 023cf4825..19abc7939 100644 --- a/dist/languages/el.ts +++ b/dist/languages/el.ts @@ -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"/> diff --git a/dist/languages/es.ts b/dist/languages/es.ts index fb5ade667..7c7f97397 100644 --- a/dist/languages/es.ts +++ b/dist/languages/es.ts @@ -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"/> diff --git a/dist/languages/fr.ts b/dist/languages/fr.ts index 7730208b7..606a8816e 100644 --- a/dist/languages/fr.ts +++ b/dist/languages/fr.ts @@ -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"/> diff --git a/dist/languages/id.ts b/dist/languages/id.ts index 15054a333..a6831d7e5 100644 --- a/dist/languages/id.ts +++ b/dist/languages/id.ts @@ -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"/> diff --git a/dist/languages/it.ts b/dist/languages/it.ts index 688a071e7..8c76842b5 100644 --- a/dist/languages/it.ts +++ b/dist/languages/it.ts @@ -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"/> diff --git a/dist/languages/ja_JP.ts b/dist/languages/ja_JP.ts index 66c09d493..e61137ac7 100644 --- a/dist/languages/ja_JP.ts +++ b/dist/languages/ja_JP.ts @@ -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"/> diff --git a/dist/languages/ko_KR.ts b/dist/languages/ko_KR.ts index bd5e8fa18..ed0a9334b 100644 --- a/dist/languages/ko_KR.ts +++ b/dist/languages/ko_KR.ts @@ -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"/> diff --git a/dist/languages/nb.ts b/dist/languages/nb.ts index 5aec4a1cc..86cd4ea85 100644 --- a/dist/languages/nb.ts +++ b/dist/languages/nb.ts @@ -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"/> diff --git a/dist/languages/nl.ts b/dist/languages/nl.ts index 074742e39..7f7ba6da2 100644 --- a/dist/languages/nl.ts +++ b/dist/languages/nl.ts @@ -25,7 +25,13 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv3.0+.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">This software should not be used to play games you have not legally obtained.</span></p></body></html></source> - <translation type="unfinished"/> + <translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">yuzu is een experimentele open-source emulator voor de Nintendo Switch met een licentie onder GPLv3.0+.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">Deze software mag niet worden gebruikt om spellen te spelen die je niet legaal hebt verkregen.</span></p></body></html></translation> </message> <message> <location filename="../../src/yuzu/aboutdialog.ui" line="130"/> @@ -48,17 +54,17 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="24"/> <source>Cancel</source> - <translation>Annuleren</translation> + <translation>Annuleer</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="43"/> <source>Touch the top left corner <br>of your touchpad.</source> - <translation>Raak de linkerbovenhoek <br> van uw touchpad aan.</translation> + <translation>Raak de linkerbovenhoek <br> van je touchpad aan.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="46"/> <source>Now touch the bottom right corner <br>of your touchpad.</source> - <translation>klik nu op toets <br> op je toetsenbord</translation> + <translation>Raak nu de rechterbenedenhoek <br>van je touchpad aan.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="49"/> @@ -76,17 +82,17 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/multiplayer/chat_room.ui" line="14"/> <source>Room Window</source> - <translation type="unfinished"/> + <translation>Kamerraam</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.ui" line="40"/> <source>Send Chat Message</source> - <translation>Stuur Chatbericht</translation> + <translation>Verzend Chatbericht</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.ui" line="47"/> <source>Send Message</source> - <translation>Stuur Bericht</translation> + <translation>Verzend Bericht</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="181"/> @@ -106,7 +112,7 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="324"/> <source>%1 has been kicked</source> - <translation>%1 is verwijderd</translation> + <translation>%1 is kicked</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="327"/> @@ -116,55 +122,57 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="330"/> <source>%1 has been unbanned</source> - <translation>%1's ban is ongedaan gemaakt</translation> + <translation>Ban van %1 is ongedaan gemaakt</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="446"/> <source>View Profile</source> - <translation>Profiel Bekijken</translation> + <translation>Bekijk Profiel</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="459"/> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="469"/> <source>Block Player</source> - <translation>Speler Blokkeren</translation> + <translation>Blokkeer Speler</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="470"/> <source>When you block a player, you will no longer receive chat messages from them.<br><br>Are you sure you would like to block %1?</source> - <translation type="unfinished"/> + <translation>Als je een speler blokkeert, ontvang je geen chatberichten meer van ze.<br><br>Weet je zeker dat je %1 wilt blokkeren?</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="483"/> <source>Kick</source> - <translation>Verwijderen</translation> + <translation>Kick</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="484"/> <source>Ban</source> - <translation>Verwijderen</translation> + <translation>Ban</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="488"/> <source>Kick Player</source> - <translation>Speler verwijderen</translation> + <translation>Kick Speler</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="489"/> <source>Are you sure you would like to <b>kick</b> %1?</source> - <translation>Weet je zeker dat je %1 wil <b>verwijderen</b>?</translation> + <translation>Weet je zeker dat je %1 wil <b>kicken</b>?</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="497"/> <source>Ban Player</source> - <translation>Speler Verbannen</translation> + <translation>Ban Speler</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="498"/> <source>Are you sure you would like to <b>kick and ban</b> %1? This would ban both their forum username and their IP address.</source> - <translation type="unfinished"/> + <translation>Weet je zeker dat je %1 wilt <b>kicken en bannen</b>? + +Dit zou zowel hun forum gebruikersnaam als hun IP-adres verbannen.</translation> </message> </context> <context> @@ -172,12 +180,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/multiplayer/client_room.ui" line="14"/> <source>Room Window</source> - <translation type="unfinished"/> + <translation>Kamervenster</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/client_room.ui" line="27"/> <source>Room Description</source> - <translation>Kamer Beschrijving</translation> + <translation>Kamerbeschrijving</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/client_room.ui" line="47"/> @@ -187,7 +195,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/multiplayer/client_room.ui" line="57"/> <source>Leave Room</source> - <translation type="unfinished"/> + <translation>Verlaat Kamer</translation> </message> </context> <context> @@ -200,12 +208,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/multiplayer/client_room.cpp" line="87"/> <source>Disconnected</source> - <translation type="unfinished"/> + <translation>Verbinding verbroken</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/client_room.cpp" line="100"/> <source>%1 - %2 (%3/%4 members) - connected</source> - <translation type="unfinished"/> + <translation>%1 - %2 (%3/%4 leden) - verbonden</translation> </message> </context> <context> @@ -224,112 +232,112 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/compatdb.ui" line="271"/> <location filename="../../src/yuzu/compatdb.ui" line="330"/> <source>Report Game Compatibility</source> - <translation>Rapporteer Game Compatibiliteit</translation> + <translation>Rapporteer Spelcompatibiliteit</translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="36"/> <source><html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of yuzu you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected yuzu account</li></ul></body></html></source> - <translation><html><head/><body><p><span style=" font-size:10pt;">Als je kiest een test case op te sturen naar de </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu compatibiliteitslijst</span></a><span style=" font-size:10pt;">, zal de volgende informatie worden verzameld en getoond op de site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Informatie (CPU / GPU / Besturingssysteem)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Welke versie van yuzu je draait</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Het verbonden yuzu account</li></ul></body></html></translation> + <translation><html><head/><body><p><span style=" font-size:10pt;">Als je kiest een test case op te sturen naar de </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu-compatibiliteitslijst</span></a><span style=" font-size:10pt;">, zal de volgende informatie worden verzameld en getoond op de site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware-informatie (CPU / GPU / Besturingssysteem)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Welke versie van yuzu je draait</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Het verbonden yuzu-account</li></ul></body></html></translation> </message> <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>Start het spel op?</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 Het spel begint video of audio te produceren</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>Nee Het spel komt niet voorbij het "Starten..." scherm</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 Het spel komt voorbij het intro/menu en begint met het spel</translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="131"/> <source>No The game crashes or freezes while loading or using the menu</source> - <translation type="unfinished"/> + <translation>Nee Het spel loopt vast tijdens het laden of gebruik van het menu</translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="143"/> <source><html><head/><body><p>Does the game reach gameplay?</p></body></html></source> - <translation type="unfinished"/> + <translation><html><head/><body><p>Bereikt het spel de gameplay?</p></body></html></translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="176"/> <source>Yes The game works without crashes</source> - <translation type="unfinished"/> + <translation>Ja Het spel werkt zonder crashes</translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="183"/> <source>No The game crashes or freezes during gameplay</source> - <translation type="unfinished"/> + <translation>Nee Het spel loopt vast of loopt vast tijdens het spelen</translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="195"/> <source><html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html></source> - <translation type="unfinished"/> + <translation><html><head/><body><p>Werkt het spel zonder te crashen, te bevriezen of vast te lopen tijdens het spelen?</p></body></html></translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="228"/> <source>Yes The game can be finished without any workarounds</source> - <translation type="unfinished"/> + <translation>Ja Het spel kan zonder omwegen worden uitgespeeld</translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="235"/> <source>No The game can't progress past a certain area</source> - <translation type="unfinished"/> + <translation>Nee Het spel kan niet verder dan een bepaald gebied</translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="247"/> <source><html><head/><body><p>Is the game completely playable from start to finish?</p></body></html></source> - <translation type="unfinished"/> + <translation><html><head/><body><p>Is het spel volledig speelbaar van begin tot eind?</p></body></html></translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="280"/> <source>Major The game has major graphical errors</source> - <translation type="unfinished"/> + <translation>Major Het spel heeft aanzienlijke grafische fouten</translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="287"/> <source>Minor The game has minor graphical errors</source> - <translation type="unfinished"/> + <translation>Minor Het spel heeft lichte grafische fouten</translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="294"/> <source>None Everything is rendered as it looks on the Nintendo Switch</source> - <translation type="unfinished"/> + <translation>Geen Alles wordt weergegeven zoals het eruit ziet op de Nintendo Switch</translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="306"/> <source><html><head/><body><p>Does the game have any graphical glitches?</p></body></html></source> - <translation type="unfinished"/> + <translation><html><head/><body><p>Heeft het spel grafische glitches?</p></body></html></translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="339"/> <source>Major The game has major audio errors</source> - <translation type="unfinished"/> + <translation>Major Het spel heeft aanzienlijke audiofouten</translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="346"/> <source>Minor The game has minor audio errors</source> - <translation type="unfinished"/> + <translation>Minor Het spel heeft lichte audiofouten</translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="353"/> <source>None Audio is played perfectly</source> - <translation type="unfinished"/> + <translation>Geen Audio wordt perfect afgespeeld</translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="365"/> <source><html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html></source> - <translation type="unfinished"/> + <translation><html><head/><body><p>Heeft het spel audio-glitches / ontbrekende effecten?</p></body></html></translation> </message> <message> <location filename="../../src/yuzu/compatdb.ui" line="389"/> @@ -363,27 +371,27 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_audio.ui" line="14"/> <location filename="../../src/yuzu/configuration/configure_audio.ui" line="20"/> <source>Audio</source> - <translation>Geluid</translation> + <translation>Audio</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_audio.ui" line="28"/> <source>Output Engine:</source> - <translation>Output Engine:</translation> + <translation>Uitvoer-engine:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_audio.ui" line="42"/> <source>Output Device:</source> - <translation type="unfinished"/> + <translation>Uitvoerapparaat:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_audio.ui" line="56"/> <source>Input Device:</source> - <translation type="unfinished"/> + <translation>Invoerapparaat:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_audio.ui" line="70"/> <source>Sound Output Mode:</source> - <translation type="unfinished"/> + <translation>Geluidsuitvoermodus:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_audio.ui" line="78"/> @@ -408,7 +416,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_audio.ui" line="119"/> <source>Set volume:</source> - <translation>stel volume in:</translation> + <translation>Stel volume in:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_audio.ui" line="127"/> @@ -423,7 +431,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_audio.ui" line="187"/> <source>Mute audio when in background</source> - <translation type="unfinished"/> + <translation>Demp audio op de achtergrond</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="115"/> @@ -437,37 +445,37 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_camera.ui" line="14"/> <source>Configure Infrared Camera</source> - <translation type="unfinished"/> + <translation>Configureer Infraroodcamera</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_camera.ui" line="26"/> <source>Select where the image of the emulated camera comes from. It may be a virtual camera or a real camera.</source> - <translation type="unfinished"/> + <translation>Selecteer waar het beeld van de geëmuleerde camera vandaan komt. Het kan een virtuele camera of een echte camera zijn.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_camera.ui" line="52"/> <source>Camera Image Source:</source> - <translation type="unfinished"/> + <translation>Camera Beeldbron:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_camera.ui" line="71"/> <source>Input device:</source> - <translation type="unfinished"/> + <translation>Invoerapparaat:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_camera.ui" line="96"/> <source>Preview</source> - <translation type="unfinished"/> + <translation>Preview</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_camera.ui" line="108"/> <source>Resolution: 320*240</source> - <translation type="unfinished"/> + <translation>Resolutie: 320*240</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_camera.ui" line="115"/> <source>Click to preview</source> - <translation type="unfinished"/> + <translation>Klik om een preview te zien</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_camera.ui" line="140"/> @@ -500,7 +508,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="34"/> <source>Accuracy:</source> - <translation>Accuratie:</translation> + <translation>Nauwkeurigheid:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="42"/> @@ -520,7 +528,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="57"/> <source>Paranoid (disables most optimizations)</source> - <translation type="unfinished"/> + <translation>Paranoid (schakelt de meeste optimalisaties uit)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="68"/> @@ -530,7 +538,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="85"/> <source>Unsafe CPU Optimization Settings</source> - <translation>Onveilige CPU optimalisatie instellingen</translation> + <translation>Onveilige CPU-optimalisatie-instellingen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/> @@ -543,8 +551,7 @@ 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>Deze optie verbeterd de prestatie door de accuratie van fused-multiply-add instructies te verminderen op CPU's zonder native FMA support.</div> -</translation> +<div>Deze optie verbeterd de prestatie door de accuratie van fused-multiply-add-instructies te verminderen op CPU's zonder native FMA support.</div></translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/> @@ -570,13 +577,12 @@ 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>Deze optie verbetert de snelheid of 32-bit ASIMD floating-point functies door incorrecte afronding modellen te gebruiken.</div> -</translation> +<div>Deze optie verbetert de snelheid of 32-bit ASIMD floating-point functies door incorrecte afronding modellen te gebruiken.</div></translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="130"/> <source>Faster ASIMD instructions (32 bits only)</source> - <translation type="unfinished"/> + <translation>Snellere ASIMD-instructies (alleen 32-bits)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="137"/> @@ -589,36 +595,38 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="142"/> <source>Inaccurate NaN handling</source> - <translation>Onnauwkeurige verwerking van NaN</translation> + <translation>Onnauwkeurige NaN-verwerking</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>Deze optie verbetert de snelheid door het elimineren van een veiligheidscontrole voor elk geheugen lezen/schrijven in de gast. Door deze optie uit te schakelen kan een spel het geheugen van de emulator lezen/schrijven.</div></translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="154"/> <source>Disable address space checks</source> - <translation type="unfinished"/> + <translation>Schakel adresruimtecontroles uit</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="161"/> <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>Deze optie verbetert de snelheid door alleen de semantiek van cmpxchg te gebruiken om de veiligheid van exclusieve toegangsinstructies te garanderen. Dit kan resulteren in deadlocks en andere "race conditions".</div></translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="166"/> <source>Ignore global monitor</source> - <translation type="unfinished"/> + <translation>Negeer globale monitor</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="191"/> <source>CPU settings are available only when game is not running.</source> - <translation>CPU settings zijn alleen toegankelijk als er geen spel draait</translation> + <translation>CPU-instellingen zijn alleen toegankelijk als er geen spel draait.</translation> </message> </context> <context> @@ -626,13 +634,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/> <source>Form</source> - <translation>Formulier</translation> + <translation>Vorm</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="17"/> <source>CPU</source> - <translation>CPU -</translation> + <translation>CPU</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="25"/> @@ -642,7 +649,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/> <source><html><head/><body><p><span style=" font-weight:600;">For debugging only.</span><br/>If you're not sure what these do, keep all of these enabled. <br/>These settings, when disabled, only take effect when CPU Debugging is enabled. </p></body></html></source> - <translation type="unfinished"/> + <translation><html><head/><body><p><span style=" font-weight:600;">Alleen voor debugging.</span><br/> Als u niet zeker weet wat deze doen, laat ze dan allemaal ingeschakeld. <br/>Deze instellingen, indien uitgeschakeld, hebben alleen effect wanneer CPU Debugging is ingeschakeld.</p></body></html></translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="41"/> @@ -652,14 +659,14 @@ This would ban both their forum username and their IP address.</source> <div style="white-space: nowrap">Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.</div> </source> <translation> -<div style="white-space: nowrap">Deze optimazie versneld geheugen toegang door het gastprogramma.</div> -<div style="white-space: nowrap">Door dit aan te leggen geeft toegang tot PageTable::pointers in uitgezonden code.</div> -<div style="white-space: nowrap">Door dit uit te leggen forceerd u alle geheugen toegang door Memory::Read/Memory::Write functies te gaan.</div></translation> +<div style="white-space: nowrap">Deze optimalisatie versnelt exclusieve geheugentoegang door het gastprogramma.</div> +<div style="white-space: nowrap">Inschakelen zorgt ervoor dat exclusieve geheugenlees/schrijfacties van de gast rechtstreeks in het geheugen plaatsvinden en gebruik maken van de MMU van de host.</div> +<div style="white-space: nowrap">Uitschakelen forceert het gebruik van Software MMU-emulatie voor alle exclusieve geheugentoepassingen.</div></translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="48"/> <source>Enable inline page tables</source> - <translation>Schakel inlijne pagina tafles in</translation> + <translation>Schakel inline paginatabellen in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="55"/> @@ -672,7 +679,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/> <source>Enable block linking</source> - <translation>Schakel block linking in</translation> + <translation>Schakel blocklinking in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="67"/> @@ -680,13 +687,12 @@ This would ban both their forum username and their IP address.</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> -<div>Deze optimalisatie vermijdt het opzoeken van dispatchers door potentiële retouradressen van BL-instructies bij te houden. Dit benadert wat er gebeurt met een retourstackbuffer op een echte CPU.</div></translation> +<div>Deze optimalisatie vermijdt dispatcher lookups door potentiële terugkeeradressen van BL-instructies bij te houden. Dit benadert wat er gebeurt met een return stack buffer op een echte CPU.</div></translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/> <source>Enable return stack buffer</source> - <translation>Return-stackbuffer inschakelen - </translation> + <translation>Schakel return-stackbuffer in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="79"/> @@ -699,7 +705,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/> <source>Enable fast dispatcher</source> - <translation>Shakel snelle dispatcher in</translation> + <translation>Schakel snelle dispatcher in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="91"/> @@ -712,7 +718,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/> <source>Enable context elimination</source> - <translation>Shakel context eliminatie in</translation> + <translation>Schakel context eliminatie in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="103"/> @@ -725,7 +731,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/> <source>Enable constant propagation</source> - <translation>Constante verspreiding inschakelen</translation> + <translation>Schakel constante verspreiding in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="115"/> @@ -738,7 +744,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/> <source>Enable miscellaneous optimizations</source> - <translation>Diverse optimalisaties inschakelen</translation> + <translation>Schakel diverse optimalisaties in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="127"/> @@ -753,7 +759,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="133"/> <source>Enable misalignment check reduction</source> - <translation>Schakel verkeerde uitlijning vermindering in</translation> + <translation>Schakel verkeerde uitlijningsvermindering in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="140"/> @@ -763,14 +769,14 @@ This would ban both their forum username and their IP address.</source> <div style="white-space: nowrap">Disabling this forces all memory accesses to use Software MMU Emulation.</div> </source> <translation> -<div style="white-space: nowrap">Deze optimazie versneld geheugen toegang door het gastprogramma.</div> -<div style="white-space: nowrap">Door dit aan te leggen geeft toegang tot PageTable::pointers in uitgezonden code.</div> -<div style="white-space: nowrap">Door dit uit te leggen forceerd u alle geheugen toegang door Memory::Read/Memory::Write functies te gaan.</div></translation> +<div style="white-space: nowrap">Deze optimalisatie versnelt exclusieve geheugentoegang door het gastprogramma.</div> +<div style="white-space: nowrap">Inschakelen zorgt ervoor dat geheugenlees/schrijfacties rechtstreeks in het geheugen plaatsvinden en gebruik maken van de MMU van de host.</div> +<div style="white-space: nowrap">Uitschakelen forceert het gebruik van Software MMU-emulatie voor alle exclusieve geheugentoepassingen.</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>Schakel Host MMU-emulatie in (algemene geheugeninstructies)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="154"/> @@ -779,12 +785,15 @@ 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">Deze optimalisatie versnelt exclusieve geheugentoegang door het gastprogramma.</div> +<div style="white-space: nowrap">Inschakelen zorgt ervoor dat exclusieve geheugenlees/schrijfacties van de gast rechtstreeks in het geheugen plaatsvinden en gebruik maken van de MMU van de host.</div> +<div style="white-space: nowrap">Uitschakelen forceert het gebruik van Software MMU-emulatie voor alle exclusieve geheugentoepassingen.</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>Schakel Host MMU-emulatie in (exclusieve geheugeninstructies)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="168"/> @@ -792,12 +801,14 @@ 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">Deze optimalisatie versnelt exclusieve geheugentoegang door het gastprogramma.</div> +<div style="white-space: nowrap">Het inschakelen ervan vermindert de overhead van fastmem falen van exclusieve geheugentoegang.</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>Schakel hercompilatie van exclusieve geheugeninstructies in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="181"/> @@ -805,12 +816,14 @@ 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">Deze optimalisering versnelt geheugentoepassingen door ongeldige geheugentoepassingen te laten slagen.</div> +<div style="white-space: nowrap">Het inschakelen ervan vermindert de overhead van alle geheugentoepassingen en heeft geen invloed op programma's die geen ongeldig geheugen gebruiken.</div></translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="187"/> <source>Enable fallbacks for invalid memory accesses</source> - <translation type="unfinished"/> + <translation>Schakel fallbacks in voor ongeldige geheugentoegang</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="212"/> @@ -823,12 +836,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="15"/> <source>Debugger</source> - <translation type="unfinished"/> + <translation>Debugger</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="23"/> <source>Enable GDB Stub</source> - <translation>GDB Stub Aanzetten</translation> + <translation>Schakel GDB Stub in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="43"/> @@ -848,12 +861,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="87"/> <source>Show Log in Console</source> - <translation>Laat Log Venster Zien</translation> + <translation>Toon Login-console</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="94"/> <source>Open Log Location</source> - <translation>Open Log Locatie</translation> + <translation>Open Loglocatie</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="104"/> @@ -863,7 +876,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="107"/> <source>Enable Extended Logging**</source> - <translation>Activeer Uitgebreid Loggen**</translation> + <translation>Schakel Uitgebreid Loggen** in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="117"/> @@ -873,7 +886,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="125"/> <source>Arguments String</source> - <translation>Argumenten Rij</translation> + <translation>Argumentenrij</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="140"/> @@ -888,42 +901,42 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="152"/> <source>Enable Graphics Debugging</source> - <translation>Grafische foutopsporing inschakelen</translation> + <translation>Schakel Graphics Foutopsporing in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="159"/> <source>When checked, it enables Nsight Aftermath crash dumps</source> - <translation type="unfinished"/> + <translation>Indien aangevinkt schakelt het Nsight Aftermath crashdumps in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="162"/> <source>Enable Nsight Aftermath</source> - <translation type="unfinished"/> + <translation>Schakel Nsight Aftermath in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="172"/> <source>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</source> - <translation type="unfinished"/> + <translation>Indien aangevinkt, zal het alle originele assembler shaders van de disk shader cache of het gevonden spel dumpen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="175"/> <source>Dump Game Shaders</source> - <translation type="unfinished"/> + <translation>Dump Spel-shaders</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="185"/> <source>When checked, it will dump all the macro programs of the GPU</source> - <translation type="unfinished"/> + <translation>Indien aangevinkt, worden alle macroprogramma's van de GPU gedumpt</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/> <source>Dump Maxwell Macros</source> - <translation type="unfinished"/> + <translation>Dump Maxwell-macro's</translation> </message> <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>Indien aangevinkt, wordt de macro Just In Time-compiler uitgeschakeld. Als u dit inschakelt, worden games langzamer</translation> + <translation>Indien aangevinkt, wordt de macro Just In Time-compiler uitgeschakeld. Als je dit inschakelt, worden spellen langzamer</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/> @@ -933,32 +946,32 @@ This would ban both their forum username and their IP address.</source> <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>Indien aangevinkt, schakelt het de macro HLE functies uit. Inschakelen maakt spellen langzamer</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="214"/> <source>Disable Macro HLE</source> - <translation type="unfinished"/> + <translation>Schakel Macro HLE uit</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="221"/> <source>When checked, yuzu will log statistics about the compiled pipeline cache</source> - <translation type="unfinished"/> + <translation>Indien aangevinkt, zal yuzu statistieken registreren over de gecompileerde pijplijn-cache</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="224"/> <source>Enable Shader Feedback</source> - <translation type="unfinished"/> + <translation>Schakel Shader Feedback in</translation> </message> <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>Indien aangevinkt, voert het shaders uit zonder wijzigingen in de luslogica</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="234"/> <source>Disable Loop safety checks</source> - <translation type="unfinished"/> + <translation>Schakel Lusveiligheidscontroles uit</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="244"/> @@ -968,27 +981,27 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="250"/> <source>Enable Verbose Reporting Services**</source> - <translation type="unfinished"/> + <translation>Schakel Verbose Reporting Services** in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="257"/> <source>Enable FS Access Log</source> - <translation type="unfinished"/> + <translation>Schakel FS-toegangslogboek in</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>Zet dit aan om de laatst gegenereerde audio commandolijst naar de console te sturen. Alleen van invloed op spellen die de audio renderer gebruiken.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="267"/> <source>Dump Audio Commands To Console**</source> - <translation type="unfinished"/> + <translation>Dump Audio-opdrachten naar Console**</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="274"/> <source>Create Minidump After Crash</source> - <translation type="unfinished"/> + <translation>Maak Minidump na Crash</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="284"/> @@ -998,42 +1011,42 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="290"/> <source>Kiosk (Quest) Mode</source> - <translation>Kiosk (Quest) Modus</translation> + <translation>Kiosk-modus (Quest)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="297"/> <source>Enable CPU Debugging</source> - <translation type="unfinished"/> + <translation>Schakel CPU-foutopsporing in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="304"/> <source>Enable Debug Asserts</source> - <translation>Schakel Debug asserties in</translation> + <translation>Schakel Debug-asserts in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="311"/> <source>Enable Auto-Stub**</source> - <translation type="unfinished"/> + <translation>Schakel Auto-Stub** in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="318"/> <source>Enable All Controller Types</source> - <translation type="unfinished"/> + <translation>Schakel Alle Controler-soorten in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="325"/> <source>Disable Web Applet</source> - <translation type="unfinished"/> + <translation>Schakel Webapplet uit</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="332"/> <source>Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu.</source> - <translation type="unfinished"/> + <translation>Laat yuzu controleren op een werkende Vulkan-omgeving wanneer het programma opstart. Schakel dit uit als dit problemen veroorzaakt met externe programma's die yuzu zien.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="335"/> <source>Perform Startup Vulkan Check</source> - <translation type="unfinished"/> + <translation>Voer Vulkan-controle bij het opstarten uit</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.ui" line="350"/> @@ -1043,22 +1056,22 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_debug.cpp" line="35"/> <source>Restart Required</source> - <translation type="unfinished"/> + <translation>Herstart Vereist</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.cpp" line="36"/> <source>yuzu is required to restart in order to apply this setting.</source> - <translation type="unfinished"/> + <translation>yuzu moet opnieuw opstarten om deze instelling toe te passen.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.cpp" line="88"/> <source>Web applet not compiled</source> - <translation type="unfinished"/> + <translation>Webapplet niet gecompileerd</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug.cpp" line="95"/> <source>MiniDump creation not compiled</source> - <translation type="unfinished"/> + <translation>MiniDump-creatie niet gecompileerd</translation> </message> </context> <context> @@ -1066,12 +1079,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/> <source>Configure Debug Controller</source> - <translation>Debug-controller configureren</translation> + <translation>Configureer Debug-controller</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/> <source>Clear</source> - <translation>Verwijder</translation> + <translation>Wis</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/> @@ -1084,7 +1097,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_debug_tab.ui" line="14"/> <source>Form</source> - <translation>Formulier</translation> + <translation>Vorm</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_debug_tab.ui" line="17"/> @@ -1095,8 +1108,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_debug_tab.cpp" line="17"/> <source>CPU</source> - <translation>CPU -</translation> + <translation>CPU</translation> </message> </context> <context> @@ -1104,13 +1116,13 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure.ui" line="20"/> <source>yuzu Configuration</source> - <translation>yuzu Configuratie</translation> + <translation>yuzu-configuratie</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="52"/> <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="155"/> <source>Audio</source> - <translation>Geluid</translation> + <translation>Audio</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="53"/> @@ -1138,12 +1150,12 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="57"/> <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="154"/> <source>Graphics</source> - <translation>Grafisch</translation> + <translation>Graphics</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="58"/> <source>GraphicsAdvanced</source> - <translation>GeAdvanceerdeGrafisch</translation> + <translation>Geavanceerde Graphics</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="59"/> @@ -1164,7 +1176,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="62"/> <source>Network</source> - <translation type="unfinished"/> + <translation>Netwerk</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="63"/> @@ -1175,7 +1187,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="64"/> <source>Game List</source> - <translation>Game Lijst</translation> + <translation>Spellijst</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="65"/> @@ -1198,7 +1210,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="25"/> <source>Storage Directories</source> - <translation>Opslag Folders</translation> + <translation>Opslagmappen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="31"/> @@ -1217,12 +1229,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="51"/> <source>SD Card</source> - <translation>SD Kaart</translation> + <translation>SD-kaart</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="84"/> <source>Gamecard</source> - <translation>Gamekaart</translation> + <translation>Spelkaart</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="90"/> @@ -1232,7 +1244,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="100"/> <source>Inserted</source> - <translation>Ingevoerd</translation> + <translation>Geplaatst</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="107"/> @@ -1242,7 +1254,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="124"/> <source>Patch Manager</source> - <translation>Patch Beheer</translation> + <translation>Patch-beheer</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="152"/> @@ -1272,7 +1284,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="209"/> <source>Cache Game List Metadata</source> - <translation>Cache Spel Lijst Metadata</translation> + <translation>Cache Metagegevens van Spellijst</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="216"/> @@ -1280,37 +1292,37 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="135"/> <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="140"/> <source>Reset Metadata Cache</source> - <translation>Herstel Metadata Cache</translation> + <translation>Herstel Metagegevenscache</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="93"/> <source>Select Emulated NAND Directory...</source> - <translation>Selecteer Geëmuleerde NAND Folder</translation> + <translation>Selecteer Geëmuleerde NAND-map...</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="96"/> <source>Select Emulated SD Directory...</source> - <translation>Selecteer Geëmuleerde SD Folder</translation> + <translation>Selecteer Geëmuleerde SD-map...</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="99"/> <source>Select Gamecard Path...</source> - <translation>Selecteer Gamekaart Pad</translation> + <translation>Selecteer Spelkaartpad...</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="102"/> <source>Select Dump Directory...</source> - <translation>Selecteer Dump Folder</translation> + <translation>Selecteer Dump-map...</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="105"/> <source>Select Mod Load Directory...</source> - <translation>Selecteer Mod Laad Folder</translation> + <translation>Selecteer Mod-laadmap...</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="132"/> <source>The metadata cache is already empty.</source> - <translation>De metadata cache is al leeg.</translation> + <translation>De metagegevenscache is al leeg.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="136"/> @@ -1320,7 +1332,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="141"/> <source>The metadata cache couldn't be deleted. It might be in use or non-existent.</source> - <translation>De metadata cache kon niet worden verwijderd. Het wordt mogelijk gebruikt of bestaat niet.</translation> + <translation>De metagegevenscache kon niet worden verwijderd. Het wordt mogelijk gebruikt of bestaat niet.</translation> </message> </context> <context> @@ -1339,7 +1351,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_general.ui" line="35"/> <source>Limit Speed Percent</source> - <translation>Limiteer Snelheid Percentage</translation> + <translation>Beperk Snelheidspercentage</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_general.ui" line="42"/> @@ -1349,12 +1361,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_general.ui" line="60"/> <source>Multicore CPU Emulation</source> - <translation>Multicore CPU Emulatie</translation> + <translation>Multicore CPU-emulatie</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/> - <source>Extended memory layout (6GB DRAM)</source> - <translation type="unfinished"/> + <source>Extended memory layout (8GB DRAM)</source> + <translation>Uitgebreide geheugenindeling (8 GB DRAM)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/> @@ -1364,22 +1376,22 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_general.ui" line="81"/> <source>Prompt for user on game boot</source> - <translation>Vraag voor gebruiker bij het opstartten van het spel.</translation> + <translation>Vraag aan gebruiker bij opstarten van het spel</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_general.ui" line="88"/> <source>Pause emulation when in background</source> - <translation>Pauzeer Emulatie wanneer yuzu op de achtergrond openstaat</translation> + <translation>Emulatie onderbreken op de achtergrond</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_general.ui" line="95"/> <source>Hide mouse on inactivity</source> - <translation>Verstop muis wanneer inactief</translation> + <translation>Verberg muis wanneer inactief</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_general.ui" line="137"/> <source>Reset All Settings</source> - <translation>Reset alle instellingen</translation> + <translation>Reset Alle Instellingen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_general.cpp" line="67"/> @@ -1402,17 +1414,17 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="17"/> <source>Graphics</source> - <translation>Grafisch</translation> + <translation>Graphics</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="25"/> <source>API Settings</source> - <translation>API instellingen</translation> + <translation>API-instellingen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="64"/> <source>Shader Backend:</source> - <translation type="unfinished"/> + <translation>Shader Backend:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="92"/> @@ -1438,37 +1450,37 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="173"/> <source>Use disk pipeline cache</source> - <translation type="unfinished"/> + <translation>Gebruik schijfpijplijn-cache</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="180"/> <source>Use asynchronous GPU emulation</source> - <translation>Gebruik asynchroon GPU emulatie</translation> + <translation>Gebruik asynchrone GPU-emulatie</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="187"/> <source>Accelerate ASTC texture decoding</source> - <translation type="unfinished"/> + <translation>Versnel ASTC-textuurdecodering</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="209"/> <source>NVDEC emulation:</source> - <translation type="unfinished"/> + <translation>NVDEC-emulatie:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="217"/> <source>No Video Output</source> - <translation type="unfinished"/> + <translation>Geen Video-uitvoer</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="222"/> <source>CPU Video Decoding</source> - <translation type="unfinished"/> + <translation>CPU Videodecodering</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="227"/> <source>GPU Video Decoding (Default)</source> - <translation type="unfinished"/> + <translation>GPU Videodecodering (Standaard)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="253"/> @@ -1478,7 +1490,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="261"/> <source>Borderless Windowed</source> - <translation>BoordLoos Venster</translation> + <translation>Randloos Venster</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="266"/> @@ -1508,142 +1520,142 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="315"/> <source>Force 16:10</source> - <translation type="unfinished"/> + <translation>Forceer 16:10</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="320"/> <source>Stretch to Window</source> - <translation>Rek naar Venster</translation> + <translation>Uitrekken naar Venster</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="346"/> <source>Resolution:</source> - <translation type="unfinished"/> + <translation>Resolutie:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="354"/> <source>0.5X (360p/540p) [EXPERIMENTAL]</source> - <translation type="unfinished"/> + <translation>0.5X (360p/540p) [EXPERIMENTEEL]</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="359"/> <source>0.75X (540p/810p) [EXPERIMENTAL]</source> - <translation type="unfinished"/> + <translation>0.75X (540p/810p) [EXPERIMENTEEL]</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="364"/> <source>1X (720p/1080p)</source> - <translation type="unfinished"/> + <translation>1X (720p/1080p)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="369"/> <source>1.5X (1080p/1620p) [EXPERIMENTAL]</source> - <translation type="unfinished"/> + <translation>1.5X (1080p/1620p) [EXPERIMENTEEL]</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="374"/> <source>2X (1440p/2160p)</source> - <translation type="unfinished"/> + <translation>2X (1440p/2160p)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="379"/> <source>3X (2160p/3240p)</source> - <translation type="unfinished"/> + <translation>3X (2160p/3240p)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="384"/> <source>4X (2880p/4320p)</source> - <translation type="unfinished"/> + <translation>4X (2880p/4320p)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="389"/> <source>5X (3600p/5400p)</source> - <translation type="unfinished"/> + <translation>5X (3600p/5400p)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="394"/> <source>6X (4320p/6480p)</source> - <translation type="unfinished"/> + <translation>6X (4320p/6480p)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="399"/> <source>7X (5040p/7560p)</source> - <translation type="unfinished"/> + <translation>7X (5040p/7560p)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="404"/> <source>8X (5760p/8640p)</source> - <translation type="unfinished"/> + <translation>8X (5760p/8640p)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="430"/> <source>Window Adapting Filter:</source> - <translation type="unfinished"/> + <translation>Window Adapting Filter:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="438"/> <source>Nearest Neighbor</source> - <translation type="unfinished"/> + <translation>Nearest Neighbor</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="443"/> <source>Bilinear</source> - <translation type="unfinished"/> + <translation>Bilinear</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="448"/> <source>Bicubic</source> - <translation type="unfinished"/> + <translation>Bicubic</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="453"/> <source>Gaussian</source> - <translation type="unfinished"/> + <translation>Gaussian</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="458"/> <source>ScaleForce</source> - <translation type="unfinished"/> + <translation>ScaleForce</translation> </message> <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"/> <source>Anti-Aliasing Method:</source> - <translation type="unfinished"/> + <translation>Antialiasing-methode:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="502"/> <source>FXAA</source> - <translation type="unfinished"/> + <translation>FXAA</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="507"/> <source>SMAA</source> - <translation type="unfinished"/> + <translation>SMAA</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="563"/> <source>Use global FSR Sharpness</source> - <translation type="unfinished"/> + <translation>Gebruik globale FSR-scherpte</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="568"/> <source>Set FSR Sharpness</source> - <translation type="unfinished"/> + <translation>Stel FSR-scherpte in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="582"/> <source>FSR Sharpness:</source> - <translation type="unfinished"/> + <translation>FSR-scherpte:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="649"/> <source>100%</source> - <translation type="unfinished"/> + <translation>100%</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="688"/> @@ -1664,12 +1676,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="33"/> <source>GLASM (Assembly Shaders, NVIDIA Only)</source> - <translation type="unfinished"/> + <translation>GLASM (Assembly Shaders, alleen NVIDIA)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="34"/> <source>SPIR-V (Experimental, Mesa Only)</source> - <translation type="unfinished"/> + <translation>SPIR-V (Experimenteel, alleen Mesa)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="178"/> @@ -1683,12 +1695,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/> <source>Form</source> - <translation>Formulier</translation> + <translation>Vorm</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="17"/> <source>Advanced</source> - <translation>Gedadvanceerd</translation> + <translation>Geavanceerd</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="25"/> @@ -1703,12 +1715,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/> <source>Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed.</source> - <translation type="unfinished"/> + <translation>Werkt op de achtergrond terwijl er wordt gewacht op grafische opdrachten om te voorkomen dat de GPU zijn kloksnelheid verlaagt.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="78"/> <source>Force maximum clocks (Vulkan only)</source> - <translation type="unfinished"/> + <translation>Forceer maximale klokken (alleen Vulkan)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/> @@ -1718,17 +1730,17 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="88"/> <source>Use VSync</source> - <translation type="unfinished"/> + <translation>Gebruik VSync</translation> </message> <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>Maakt asynchrone ASTC-textuurdecodering mogelijk, waardoor de laadtijd minder stottert. Deze functie is experimenteel.</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>Decodeer ASTC-texturen asynchroon (Hack)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="105"/> @@ -1738,37 +1750,37 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="108"/> <source>Use asynchronous shader building (Hack)</source> - <translation type="unfinished"/> + <translation>Gebruik asynchrone shaderbouw (Hack)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="115"/> <source>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</source> - <translation type="unfinished"/> + <translation>Schakelt Snelle GPU-tijd in. Deze optie forceert de meeste games om op hun hoogste native resolutie te draaien.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="118"/> <source>Use Fast GPU Time (Hack)</source> - <translation type="unfinished"/> + <translation>Gebruik Snelle GPU-tijd (Hack)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="125"/> <source>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</source> - <translation type="unfinished"/> + <translation>Schakelt pessimistische bufferschoonmaakacties in. Deze optie forceert het schoonmaken van ongewijzigde buffers, wat prestaties kan kosten.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="128"/> <source>Use pessimistic buffer flushes (Hack)</source> - <translation type="unfinished"/> + <translation>Gebruik pessimistische bufferleegmaakacties (Hack)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="135"/> <source>Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally.</source> - <translation type="unfinished"/> + <translation>Schakelt GPU-leverancier-specifieke pijplijn-cache in. Deze optie kan de laadtijd van shaders aanzienlijk verbeteren in gevallen waarin het Vulkan-stuurprogramma de pijplijncachebestanden niet intern opslaat.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="138"/> <source>Use Vulkan pipeline cache</source> - <translation type="unfinished"/> + <translation>Gebruik Vulkan-pijplijn-cache</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="160"/> @@ -1778,7 +1790,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="168"/> <source>Automatic</source> - <translation type="unfinished"/> + <translation>Automatisch</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="173"/> @@ -1811,7 +1823,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/> <source>Hotkey Settings</source> - <translation>Sneltoets Instellingen</translation> + <translation>Sneltoets-instellingen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="17"/> @@ -1821,7 +1833,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="25"/> <source>Double-click on a binding to change it.</source> - <translation>Dubbel-klik op een binding om het te veranderen.</translation> + <translation>Dubbelklik op een instelling om deze te wijzigen.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="45"/> @@ -1846,14 +1858,14 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="98"/> <source>Controller Hotkey</source> - <translation type="unfinished"/> + <translation>Controller-sneltoets</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="138"/> <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="164"/> <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="379"/> <source>Conflicting Key Sequence</source> - <translation>Ongeldige Toets Volgorde</translation> + <translation>Ongeldige Toetsvolgorde</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="139"/> @@ -1864,7 +1876,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="158"/> <source>Home+%1</source> - <translation type="unfinished"/> + <translation>Home+%1</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/> @@ -1874,7 +1886,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="242"/> <source>Invalid</source> - <translation type="unfinished"/> + <translation>Ongeldig</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="343"/> @@ -1884,17 +1896,17 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="344"/> <source>Clear</source> - <translation>Verwijder</translation> + <translation>Wis</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="365"/> <source>Conflicting Button Sequence</source> - <translation type="unfinished"/> + <translation>Conflicterende Knoppencombinatie</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="366"/> <source>The default button sequence is already assigned to: %1</source> - <translation type="unfinished"/> + <translation>De standaard knoppencombinatie is al toegewezen aan: %1</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="380"/> @@ -1966,17 +1978,17 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/> <source>Console Mode</source> - <translation>Console ID:</translation> + <translation>Console-modus:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/> <source>Docked</source> - <translation>GeDocked</translation> + <translation>Docked</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/> <source>Handheld</source> - <translation>Mobiel</translation> + <translation>Handheld</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/> @@ -2052,7 +2064,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_input.ui" line="537"/> <source>Clear</source> - <translation>Verwijder</translation> + <translation>Wis</translation> </message> </context> <context> @@ -2065,7 +2077,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/> <source>Joycon Colors</source> - <translation>Joycon Kleuren</translation> + <translation>Joycon-kleuren</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/> @@ -2082,7 +2094,7 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/> <source>L Body</source> - <translation>L lichaam</translation> + <translation>L-lichaam</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/> @@ -2094,7 +2106,7 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/> <source>L Button</source> - <translation>L Knop</translation> + <translation>L-knop</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/> @@ -2106,7 +2118,7 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/> <source>R Body</source> - <translation>R lichaam</translation> + <translation>R-lichaam</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/> @@ -2118,7 +2130,7 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/> <source>R Button</source> - <translation>R Knop</translation> + <translation>R-knop</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/> @@ -2158,7 +2170,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/> <source>Emulated Devices</source> - <translation type="unfinished"/> + <translation>Geëmuleerde Apparaten</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/> @@ -2178,12 +2190,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2588"/> <source>Advanced</source> - <translation>Gedadvanceerd</translation> + <translation>Geavanceerd</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/> <source>Debug Controller</source> - <translation>Debug Controller</translation> + <translation>Debug-controller</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/> @@ -2196,12 +2208,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/> <source>Ring Controller</source> - <translation type="unfinished"/> + <translation>Ring Controller</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/> <source>Infrared Camera</source> - <translation type="unfinished"/> + <translation>Infraroodcamera</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2640"/> @@ -2211,49 +2223,49 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2652"/> <source>Emulate Analog with Keyboard Input</source> - <translation>Emuleer Anolooge invoer met toetsenbord</translation> + <translation>Emuleer Analoog met Toetsenbordinvoer</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2659"/> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2701"/> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2717"/> <source>Requires restarting yuzu</source> - <translation type="unfinished"/> + <translation>Vereist het herstarten van yuzu</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2668"/> <source>Enable XInput 8 player support (disables web applet)</source> - <translation type="unfinished"/> + <translation>Schakel ondersteuning voor XInput 8-speler in (schakelt webapplet uit)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2681"/> <source>Enable UDP controllers (not needed for motion)</source> - <translation type="unfinished"/> + <translation>Schakel UDP-controllers in (niet nodig voor beweging)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2694"/> <source>Controller navigation</source> - <translation type="unfinished"/> + <translation>Controller-navigering</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2710"/> <source>Enable direct JoyCon driver</source> - <translation type="unfinished"/> + <translation>Schakel JoyCon-stuurprogramma in</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>Schakel Pro Controller-stuurprogramma in [EXPERIMENTEEL]</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2739"/> <source>Enable mouse panning</source> - <translation>Schakel muis panning in</translation> + <translation>Schakel muispanning in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2746"/> <source>Mouse sensitivity</source> - <translation>Muis Gevoeligheid</translation> + <translation>Muisgevoeligheid</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2752"/> @@ -2271,67 +2283,67 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_input_per_game.ui" line="14"/> <source>Form</source> - <translation>Formulier</translation> + <translation>Vorm</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_per_game.ui" line="17"/> <source>Graphics</source> - <translation>Grafisch</translation> + <translation>Graphics</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_per_game.ui" line="28"/> <source>Input Profiles</source> - <translation type="unfinished"/> + <translation>Invoerprofielen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_per_game.ui" line="49"/> <source>Player 1 Profile</source> - <translation type="unfinished"/> + <translation>Profiel Speler 1 </translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_per_game.ui" line="84"/> <source>Player 2 Profile</source> - <translation type="unfinished"/> + <translation>Profiel Speler 2</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_per_game.ui" line="119"/> <source>Player 3 Profile</source> - <translation type="unfinished"/> + <translation>Profiel Speler 3</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_per_game.ui" line="154"/> <source>Player 4 Profile</source> - <translation type="unfinished"/> + <translation>Profiel Speler 4</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_per_game.ui" line="189"/> <source>Player 5 Profile</source> - <translation type="unfinished"/> + <translation>Profiel Speler 5</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_per_game.ui" line="224"/> <source>Player 6 Profile</source> - <translation type="unfinished"/> + <translation>Profiel Speler 6</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_per_game.ui" line="259"/> <source>Player 7 Profile</source> - <translation type="unfinished"/> + <translation>Profiel Speler 7</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_per_game.ui" line="294"/> <source>Player 8 Profile</source> - <translation type="unfinished"/> + <translation>Profiel Speler 8</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_per_game.cpp" line="35"/> <source>Use global input configuration</source> - <translation type="unfinished"/> + <translation>Gebruik globale invoerconfiguratie</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_per_game.cpp" line="47"/> <source>Player %1 profile</source> - <translation type="unfinished"/> + <translation>Profiel Speler %1 </translation> </message> </context> <context> @@ -2344,12 +2356,12 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/> <source>Connect Controller</source> - <translation>Verbindt Controller</translation> + <translation>Verbind Controller</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="100"/> <source>Input Device</source> - <translation>Invoer Apparaat</translation> + <translation>Invoerapparaat</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="137"/> @@ -2385,7 +2397,7 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2564"/> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2603"/> <source>Up</source> - <translation>Boven:</translation> + <translation>Omhoog</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="403"/> @@ -2396,7 +2408,7 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2634"/> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2673"/> <source>Left</source> - <translation>Links:</translation> + <translation>Links</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="452"/> @@ -2407,7 +2419,7 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2683"/> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2722"/> <source>Right</source> - <translation>Rechts:</translation> + <translation>Rechts</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="534"/> @@ -2417,7 +2429,7 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2765"/> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2804"/> <source>Down</source> - <translation>Beneden:</translation> + <translation>Omlaag</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="604"/> @@ -2425,7 +2437,7 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2835"/> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2874"/> <source>Pressed</source> - <translation>Ingedrukt:</translation> + <translation>Ingedrukt</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="653"/> @@ -2433,13 +2445,13 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2884"/> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/> <source>Modifier</source> - <translation>Modificatie:</translation> + <translation>Modificator</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="702"/> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2933"/> <source>Range</source> - <translation>Berijk</translation> + <translation>Bereik</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="735"/> @@ -2457,7 +2469,7 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="802"/> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="3030"/> <source>Modifier Range: 0%</source> - <translation>Bewerk Range: 0%</translation> + <translation>Modificatorbereik: 0%</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="848"/> @@ -2495,13 +2507,13 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1565"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1310"/> <source>Plus</source> - <translation>Plus:</translation> + <translation>Plus</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1575"/> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1614"/> <source>Home</source> - <translation>Home:</translation> + <translation>Home</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1679"/> @@ -2509,7 +2521,7 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1313"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1320"/> <source>R</source> - <translation>R:</translation> + <translation>R</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/> @@ -2543,7 +2555,7 @@ This would ban both their forum username and their IP address.</source> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2151"/> <source>Face Buttons</source> - <translation>Gezicht Knoppen</translation> + <translation>Gezichtsknoppen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2209"/> @@ -2581,7 +2593,7 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="556"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="651"/> <source>Clear</source> - <translation>Verwijder</translation> + <translation>Wis</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="384"/> @@ -2596,64 +2608,64 @@ This would ban both their forum username and their IP address.</source> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="387"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="665"/> <source>Invert button</source> - <translation type="unfinished"/> + <translation>Knop omkeren</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="393"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="656"/> <source>Toggle button</source> - <translation>Shakel Knop</translation> + <translation>Schakel-knop</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="399"/> <source>Turbo button</source> - <translation type="unfinished"/> + <translation>Turbo-knop</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="407"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="607"/> <source>Invert axis</source> - <translation>Spiegel As</translation> + <translation>Spiegel as</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="413"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="417"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="470"/> <source>Set threshold</source> - <translation type="unfinished"/> + <translation>Stel drempel in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="417"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="470"/> <source>Choose a value between 0% and 100%</source> - <translation type="unfinished"/> + <translation>Kies een waarde tussen 0% en 100%</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="429"/> <source>Toggle axis</source> - <translation type="unfinished"/> + <translation>Schakel as</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="466"/> <source>Set gyro threshold</source> - <translation type="unfinished"/> + <translation>Stel gyro-drempel in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="512"/> <source>Map Analog Stick</source> - <translation>Zet Analoge Stick</translation> + <translation> Analoge Stick Toewijzen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="513"/> <source>After pressing OK, first move your joystick horizontally, and then vertically. To invert the axes, first move your joystick vertically, and then horizontally.</source> - <translation>Na OK in te drukken, beweeg je joystick eerst horizontaal en dan verticaal. -Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal.</translation> + <translation>Nadat je op OK hebt gedrukt, beweeg je de joystick eerst horizontaal en vervolgens verticaal. +Om de assen om te keren, beweeg je de joystick eerst verticaal en vervolgens horizontaal.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="581"/> <source>Center axis</source> - <translation type="unfinished"/> + <translation>Midden as</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="689"/> @@ -2665,7 +2677,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="698"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1044"/> <source>Modifier Range: %1%</source> - <translation>Bewerk Range: %1%</translation> + <translation>Modificatorbereik: %1%</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="724"/> @@ -2691,42 +2703,42 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1085"/> <source>Handheld</source> - <translation>Mobiel</translation> + <translation>Handheld</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1089"/> <source>GameCube Controller</source> - <translation>GameCube Controller</translation> + <translation>GameCube-controller</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1098"/> <source>Poke Ball Plus</source> - <translation type="unfinished"/> + <translation>Poke Ball Plus</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1102"/> <source>NES Controller</source> - <translation type="unfinished"/> + <translation>NES-controller</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1106"/> <source>SNES Controller</source> - <translation type="unfinished"/> + <translation>SNES-controller</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1110"/> <source>N64 Controller</source> - <translation type="unfinished"/> + <translation>N64-controller</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1114"/> <source>Sega Genesis</source> - <translation type="unfinished"/> + <translation>Sega Genesis</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1318"/> <source>Start / Pause</source> - <translation>Start / Pauze</translation> + <translation>Begin / Onderbreken</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1321"/> @@ -2746,7 +2758,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1424"/> <source>Shake!</source> - <translation>Shudden!</translation> + <translation>Schud!</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1426"/> @@ -2761,53 +2773,53 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1521"/> <source>Enter a profile name:</source> - <translation>Voer nieuwe gebruikersnaam in:</translation> + <translation>Voer een profielnaam in:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1529"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1537"/> <source>Create Input Profile</source> - <translation>Creëer een nieuw Invoer Profiel</translation> + <translation>Maak Invoerprofiel</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1530"/> <source>The given profile name is not valid!</source> - <translation>De ingevoerde Profiel naam is niet geldig</translation> + <translation>De ingevoerde profielnaam is niet geldig!</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1538"/> <source>Failed to create the input profile "%1"</source> - <translation>Het is mislukt om Invoer Profiel "%1 te Creëer</translation> + <translation>Kon invoerprofiel "%1" niet maken</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1558"/> <source>Delete Input Profile</source> - <translation>Verwijder invoer profiel</translation> + <translation>Verwijder Invoerprofiel</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1559"/> <source>Failed to delete the input profile "%1"</source> - <translation>Het is mislukt om Invoer Profiel "%1 te Verwijderen</translation> + <translation>Kon invoerprofiel "%1" niet verwijderen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1581"/> <source>Load Input Profile</source> - <translation>Laad invoer profiel</translation> + <translation>Laad Invoerprofiel</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1582"/> <source>Failed to load the input profile "%1"</source> - <translation>Het is mislukt om Invoer Profiel "%1 te Laden</translation> + <translation>Kon invoerprofiel "%1" niet laden</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1607"/> <source>Save Input Profile</source> - <translation>Sla Invoer profiel op</translation> + <translation>Sla Invoerprofiel op</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1608"/> <source>Failed to save the input profile "%1"</source> - <translation>Het is mislukt om Invoer Profiel "%1 Op te slaan</translation> + <translation>Kon invoerprofiel "%1" niet opslaan</translation> </message> </context> <context> @@ -2815,12 +2827,12 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_input_profile_dialog.ui" line="14"/> <source>Create Input Profile</source> - <translation>Maak Invoer Profiel</translation> + <translation>Maak Invoerprofiel</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_profile_dialog.ui" line="40"/> <source>Clear</source> - <translation>Verwijder</translation> + <translation>Wis</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_profile_dialog.ui" line="47"/> @@ -2843,7 +2855,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="23"/> <source>UDP Calibration:</source> - <translation>UDP Calibratie:</translation> + <translation>UDP-calibratie:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="30"/> @@ -2860,17 +2872,17 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="57"/> <source>Touch from button profile:</source> - <translation type="unfinished"/> + <translation>Raak van knop-profiel:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="85"/> <source>CemuhookUDP Config</source> - <translation>CemuhookUDP Configuratie</translation> + <translation>CemuhookUDP-configuratie</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="91"/> <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source> - <translation>U kunt elke Cemuhook-compatibele UDP-invoerbron gebruiken voor bewegings- en aanraakinvoer.</translation> + <translation>Je kunt elke Cemuhook-compatibele UDP-invoerbron gebruiken om beweging en aanraking in te voeren.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="134"/> @@ -2885,7 +2897,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="188"/> <source>Learn More</source> - <translation>Leer Meer</translation> + <translation>Meer Info</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="201"/> @@ -2901,12 +2913,12 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/> <source>Remove Server</source> - <translation>Externe Server</translation> + <translation>Verwijder Server</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="87"/> <source><a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a></source> - <translation><a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Leer Meer</span></a></translation> + <translation><a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Meer Info</span></a></translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="168"/> @@ -2921,7 +2933,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="193"/> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="287"/> <source>yuzu</source> - <translation>Yuzu</translation> + <translation>yuzu</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="173"/> @@ -2936,12 +2948,12 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="181"/> <source>IP address is not valid</source> - <translation>IP adress is niet geldig</translation> + <translation>IP-adress is niet geldig</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="187"/> <source>This UDP server already exists</source> - <translation>Deze UDP server bestaat al</translation> + <translation>Deze UDP-server bestaat al</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="193"/> @@ -2961,7 +2973,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="256"/> <source>Test Successful</source> - <translation>Test Succesvol</translation> + <translation>Test Succesvol</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="257"/> @@ -2976,12 +2988,12 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="260"/> <source>Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct.</source> - <translation>Kan niet de juiste data van de server ontvangen.<br>Verifieer dat de server is goed opgezet en dat het adres en poort correct zijn.</translation> + <translation>Kan niet de juiste data van de server ontvangen.<br>Controleer of de server correct is ingesteld en of het adres en de poort correct zijn.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="288"/> <source>UDP Test or calibration configuration is in progress.<br>Please wait for them to finish.</source> - <translation>UDP Test of calibratie configuratie is bezig.<br>Wacht alstublieft totdat het voltooid is.</translation> + <translation>UDP-test of kalibratieconfiguratie is bezig.<br>Wacht tot ze klaar zijn.</translation> </message> </context> <context> @@ -2989,12 +3001,12 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_network.ui" line="14"/> <source>Form</source> - <translation>Formulier</translation> + <translation>Vorm</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_network.ui" line="17"/> <source>Network</source> - <translation type="unfinished"/> + <translation>Netwerk</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_network.ui" line="25"/> @@ -3004,7 +3016,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_network.ui" line="34"/> <source>Network Interface</source> - <translation type="unfinished"/> + <translation>Netwerkinterface</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_network.cpp" line="15"/> @@ -3032,7 +3044,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="92"/> <source>Title ID</source> - <translation>Titel ID</translation> + <translation>Titel-ID</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="129"/> @@ -3082,22 +3094,22 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="62"/> <source>Graphics</source> - <translation>Grafisch</translation> + <translation>Graphics</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="63"/> <source>Adv. Graphics</source> - <translation>Adv. Grafisch</translation> + <translation>Adv. Graphics</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="64"/> <source>Audio</source> - <translation>Geluid</translation> + <translation>Audio</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="65"/> <source>Input Profiles</source> - <translation type="unfinished"/> + <translation>Invoerprofielen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="68"/> @@ -3115,7 +3127,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/> <source>Form</source> - <translation>Formulier</translation> + <translation>Vorm</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="17"/> @@ -3125,7 +3137,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="46"/> <source>Patch Name</source> - <translation>Patch Naam</translation> + <translation>Patch-naam</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="47"/> @@ -3148,7 +3160,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="25"/> <source>Profile Manager</source> - <translation>Profiel Beheer</translation> + <translation>Profielbeheer</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="42"/> @@ -3163,12 +3175,12 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="116"/> <source>Set Image</source> - <translation>Selecteer Afbeelding</translation> + <translation>Stel Afbeelding In</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="136"/> <source>Add</source> - <translation>Voeg Toe</translation> + <translation>Toevoegen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="146"/> @@ -3183,7 +3195,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="168"/> <source>Profile management is available only when game is not running.</source> - <translation>Profiel beheer is alleen beschikbaar wanneer het spel niet bezig is.</translation> + <translation>Profielbeheer is alleen beschikbaar wanneer het spel niet bezig is.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="55"/> @@ -3196,7 +3208,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="73"/> <source>Enter Username</source> - <translation>Voer een Gebruikersnaam in</translation> + <translation>Voer Gebruikersnaam in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="139"/> @@ -3216,12 +3228,12 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="271"/> <source>Select User Image</source> - <translation>Selecteer gebruiker's foto</translation> + <translation>Selecteer Gebruikersfoto</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="272"/> <source>JPEG Images (*.jpg *.jpeg)</source> - <translation>JPEG foto's (*.jpg *.jpeg)</translation> + <translation>JPEG-foto's (*.jpg *.jpeg)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="281"/> @@ -3266,12 +3278,12 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="314"/> <source>Error resizing user image</source> - <translation type="unfinished"/> + <translation>Fout bij het aanpassen van grootte van gebruikersafbeelding</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="315"/> <source>Unable to resize image</source> - <translation type="unfinished"/> + <translation>Kon de grootte van de afbeelding niet wijzigen</translation> </message> </context> <context> @@ -3279,7 +3291,7 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="332"/> <source>Delete this user? All of the user's save data will be deleted.</source> - <translation type="unfinished"/> + <translation>Deze gebruiker verwijderen? Alle opgeslagen gegevens van de gebruiker worden verwijderd.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="344"/> @@ -3290,7 +3302,8 @@ Om de assen te spiegelen, beweek je joystick eerst verticaal en dan horizontaal. <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="360"/> <source>Name: %1 UUID: %2</source> - <translation type="unfinished"/> + <translation>Naam: %1 +UUID: %2</translation> </message> </context> <context> @@ -3298,29 +3311,29 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="14"/> <source>Configure Ring Controller</source> - <translation type="unfinished"/> + <translation>Configureer Ring-controller</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="26"/> <source>If you want to use this controller configure player 1 as right controller and player 2 as dual joycon before starting the game to allow this controller to be detected properly.</source> - <translation type="unfinished"/> + <translation>Als je deze controller wilt gebruiken, configureer dan speler 1 als rechter controller en speler 2 als dual joycon voordat je het spel start, zodat deze controller goed wordt gedetecteerd.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="52"/> <source>Virtual Ring Sensor Parameters</source> - <translation type="unfinished"/> + <translation>Parameters Virtuele Ringsensor</translation> </message> <message> <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>Trek</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>Duw</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="206"/> @@ -3330,29 +3343,29 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="233"/> <source>Direct Joycon Driver</source> - <translation type="unfinished"/> + <translation>Direct Joycon-driver</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="293"/> <source>Enable Ring Input</source> - <translation type="unfinished"/> + <translation>Schakel Ringinvoer in</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>Inschakelen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="307"/> <source>Ring Sensor Value</source> - <translation type="unfinished"/> + <translation>Ringsensorwaarde</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>Niet verbonden</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="344"/> @@ -3362,7 +3375,7 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="168"/> <source>Clear</source> - <translation>Verwijder</translation> + <translation>Wis</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="170"/> @@ -3372,7 +3385,7 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="172"/> <source>Invert axis</source> - <translation>Spiegel As</translation> + <translation>Spiegel as</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="191"/> @@ -3383,12 +3396,12 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="279"/> <source>Error enabling ring input</source> - <translation type="unfinished"/> + <translation>Fout tijdens inschakelen van ringinvoer</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>Direct Joycon-driver niet ingeschakeld</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="290"/> @@ -3398,17 +3411,17 @@ UUID: %2</source> <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>Het huidige apparaat ondersteunt de ringcontroller niet</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>Het huidige apparaat heeft geen ring</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="310"/> <source>Unexpected driver result %1</source> - <translation type="unfinished"/> + <translation>Onverwacht driverresultaat %1</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="333"/> @@ -3441,7 +3454,7 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="41"/> <source>Auto</source> - <translation>Automatisch</translation> + <translation>Auto</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="46"/> @@ -3702,12 +3715,12 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="313"/> <source>Time Zone:</source> - <translation>Tijd Zone:</translation> + <translation>Tijdzone:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="320"/> <source>Note: this can be overridden when region setting is auto-select</source> - <translation>Noot: dit kan worden overschreven wanneer de regio instelling op automatisch selecteren staat.</translation> + <translation>Opmerking: dit kan worden overschreven wanneer de regio-instelling automatisch wordt geselecteerd</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="324"/> @@ -3717,7 +3730,7 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="329"/> <source>American English</source> - <translation type="unfinished"/> + <translation>Amerikaans-Engels</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="334"/> @@ -3742,7 +3755,7 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="354"/> <source>Chinese</source> - <translation>Chinees (正體中文 / 简体中文)</translation> + <translation>Chinees</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="359"/> @@ -3772,17 +3785,17 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="384"/> <source>British English</source> - <translation>Brits Engels</translation> + <translation>Brits-Engels</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="389"/> <source>Canadian French</source> - <translation>Canadees Frans</translation> + <translation>Canadees-Frans</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="394"/> <source>Latin American Spanish</source> - <translation>Latijns Amerikaans Spaans</translation> + <translation>Latijns-Amerikaans Spaans</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="399"/> @@ -3797,12 +3810,12 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/> <source>Brazilian Portuguese (português do Brasil)</source> - <translation type="unfinished"/> + <translation>Braziliaans-Portugees (português do Brasil)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="417"/> <source>Custom RTC</source> - <translation>Handmatige RTC</translation> + <translation>Aangepaste RTC</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="424"/> @@ -3817,7 +3830,7 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="438"/> <source>Device Name</source> - <translation type="unfinished"/> + <translation>Apparaatnaam</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_system.ui" line="512"/> @@ -3827,7 +3840,7 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_system.cpp" line="67"/> <source>Warning: "%1" is not a valid language for region "%2"</source> - <translation type="unfinished"/> + <translation>Waarschuwing: "%1" is geen geldige taal voor regio "%2"</translation> </message> </context> <context> @@ -3835,47 +3848,47 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_tas.ui" line="11"/> <source>TAS</source> - <translation type="unfinished"/> + <translation>TAS</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_tas.ui" line="17"/> <source><html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the <a href="https://yuzu-emu.org/help/feature/tas/"><span style=" text-decoration: underline; color:#039be5;">help page</span></a> on the yuzu website.</p></body></html></source> - <translation type="unfinished"/> + <translation><html><head/><body><p>Leest controller-invoer van scripts in hetzelfde formaat als TAS-nx-scripts.<br/>Voor een meer gedetailleerde uitleg kunt u de<a href="https://yuzu-emu.org/help/feature/tas/"><span style=" text-decoration: underline; color:#039be5;">help-pagina</span></a>op de yuzu-website raadplegen.</p></body></html></translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_tas.ui" line="27"/> <source>To check which hotkeys control the playback/recording, please refer to the Hotkey settings (Configure -> General -> Hotkeys).</source> - <translation type="unfinished"/> + <translation>Om te controleren welke sneltoetsen het afspelen/opnemen regelen, raadpleeg de sneltoetsinstellingen (Configuratie -> Algemeen -> Sneltoetsen).</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_tas.ui" line="37"/> <source>WARNING: This is an experimental feature.<br/>It will not play back scripts frame perfectly with the current, imperfect syncing method.</source> - <translation type="unfinished"/> + <translation>WAARSCHUWING: Dit is een experimentele functie.<br/>Met de huidige, onvolmaakte synchronisatiemethode worden scripts niet perfect afgespeeld.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_tas.ui" line="54"/> <source>Settings</source> - <translation type="unfinished"/> + <translation>Instellingen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_tas.ui" line="60"/> <source>Enable TAS features</source> - <translation type="unfinished"/> + <translation>Schakel TAS-functies in</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_tas.ui" line="67"/> <source>Loop script</source> - <translation type="unfinished"/> + <translation>Lus script</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_tas.ui" line="77"/> <source>Pause execution during loads</source> - <translation type="unfinished"/> + <translation>Onderbreek de uitvoering tijdens ladingen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_tas.ui" line="91"/> <source>Script Directory</source> - <translation type="unfinished"/> + <translation>Script-map</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_tas.ui" line="97"/> @@ -3893,12 +3906,12 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_tas.cpp" line="19"/> <source>TAS Configuration</source> - <translation type="unfinished"/> + <translation>TAS-configuratie</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_tas.cpp" line="49"/> <source>Select TAS Load Directory...</source> - <translation type="unfinished"/> + <translation>Selecteer TAS-laadmap...</translation> </message> </context> <context> @@ -3906,12 +3919,12 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/> <source>Configure Touchscreen Mappings</source> - <translation>Touchscreen Mappings Configureren</translation> + <translation>Configureer Touchscreen-toewijzingen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/> <source>Mapping:</source> - <translation>Mapping:</translation> + <translation>Toewijzing:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/> @@ -3926,14 +3939,14 @@ UUID: %2</source> <message> <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/> <source>Rename</source> - <translation>Hernoemen</translation> + <translation>Hernoem</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/> <source>Click the bottom area to add a point, then press a button to bind. Drag points to change position, or double-click table cells to edit values.</source> - <translation>Klik in de onderste vlakte op een punt toe te voegen, daarna druk op een knop om het te verbinden. -Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen om de waardes te veranderen.</translation> + <translation>Klik op het onderste gebied om een punt toe te voegen en druk vervolgens op een knop om toe te wijzen. +Versleep punten om de positie te veranderen, of dubbelklik op tabelcellen om waarden te bewerken.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/> @@ -3975,7 +3988,7 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="206"/> <source>Delete profile %1?</source> - <translation>Verwijder Profiel %1?</translation> + <translation>Verwijder profiel %1?</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="219"/> @@ -4003,22 +4016,22 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/> <source>Warning: The settings in this page affect the inner workings of yuzu's emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source> - <translation>Waarschuwing: Instellingen in deze pagina hebben invloed op de interne werking van yuzu's geemuleerde touchscreen. Veranderingen kunnen ongewenste resultaten hebben, zoals ervoor zorgen dat het touchscreen half of niet werkt. Gebruik deze pagina enkel als je weet wat je doet.</translation> + <translation>Waarschuwing: De instellingen in deze pagina beïnvloeden de innerlijke werking van yuzu's geëmuleerde touchscreen. Het veranderen ervan kan leiden tot ongewenst gedrag, zoals het gedeeltelijk of niet werken van het touchscreen. Je moet deze pagina alleen gebruiken als je weet wat je doet.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/> <source>Touch Parameters</source> - <translation>Touch Parameters</translation> + <translation>Aanraakparameters</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/> <source>Touch Diameter Y</source> - <translation>Touch Diameter Y</translation> + <translation>Aanraakdiameter Y</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="91"/> <source>Touch Diameter X</source> - <translation>Touch Diameter X</translation> + <translation>Aanraakdiameter X</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/> @@ -4028,7 +4041,7 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="132"/> <source>Restore Defaults</source> - <translation>Herstel Standaardwaardes</translation> + <translation>Herstel Standaardwaarden</translation> </message> </context> <context> @@ -4043,37 +4056,37 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="21"/> <source>Small (32x32)</source> - <translation type="unfinished"/> + <translation>Klein (32x32)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="22"/> <source>Standard (64x64)</source> - <translation type="unfinished"/> + <translation>Standaard (64x64)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="23"/> <source>Large (128x128)</source> - <translation type="unfinished"/> + <translation>Groot (128x128)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="24"/> <source>Full Size (256x256)</source> - <translation type="unfinished"/> + <translation>Volledige Grootte (256x256)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="29"/> <source>Small (24x24)</source> - <translation type="unfinished"/> + <translation>Klein (24x24)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="30"/> <source>Standard (48x48)</source> - <translation type="unfinished"/> + <translation>Standaard (48x48)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="31"/> <source>Large (72x72)</source> - <translation type="unfinished"/> + <translation>Groot (72x72)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="36"/> @@ -4083,17 +4096,17 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="37"/> <source>Filetype</source> - <translation type="unfinished"/> + <translation>Bestandstype</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="38"/> <source>Title ID</source> - <translation>Titel ID</translation> + <translation>Titel-ID</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="39"/> <source>Title Name</source> - <translation type="unfinished"/> + <translation>Titelnaam</translation> </message> </context> <context> @@ -4116,12 +4129,12 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="31"/> <source>Note: Changing language will apply your configuration.</source> - <translation>Notitie: De taal veranderen past uw configuratie toe.</translation> + <translation>Opmerking: Als je de taal wijzigt, wordt je configuratie toegepast.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="43"/> <source>Interface language:</source> - <translation>Interface taal:</translation> + <translation>Interfacetaal:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="57"/> @@ -4131,47 +4144,47 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="74"/> <source>Game List</source> - <translation>Game Lijst</translation> + <translation>Spellijst</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="82"/> <source>Show Compatibility List</source> - <translation type="unfinished"/> + <translation>Toon Compatibiliteitslijst</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="89"/> <source>Show Add-Ons Column</source> - <translation>Toon Add-Ons Kolom</translation> + <translation>Toon Kolom Add-Ons</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="96"/> <source>Show Size Column</source> - <translation type="unfinished"/> + <translation>Toon Kolomgrootte</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="103"/> <source>Show File Types Column</source> - <translation type="unfinished"/> + <translation>Toon Kolom Bestandstypen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="112"/> <source>Game Icon Size:</source> - <translation type="unfinished"/> + <translation>Grootte Spelicoon:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="126"/> <source>Folder Icon Size:</source> - <translation type="unfinished"/> + <translation>Grootte Mapicoon:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="140"/> <source>Row 1 Text:</source> - <translation>Rij 1 Text:</translation> + <translation>Rij 1 Tekst:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="154"/> <source>Row 2 Text:</source> - <translation>Rij 2 Text:</translation> + <translation>Rij 2 Tekst:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="171"/> @@ -4181,12 +4194,12 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="179"/> <source>Ask Where To Save Screenshots (Windows Only)</source> - <translation type="unfinished"/> + <translation>Vraag waar schermafbeeldingen moeten worden opgeslagen (alleen Windows)</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="188"/> <source>Screenshots Path: </source> - <translation type="unfinished"/> + <translation>Schermafbeeldingspad:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.ui" line="198"/> @@ -4196,7 +4209,7 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="96"/> <source>Select Screenshots Path...</source> - <translation type="unfinished"/> + <translation>Selecteer Schermafbeeldingspad...</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="225"/> @@ -4209,12 +4222,12 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="14"/> <source>Configure Vibration</source> - <translation type="unfinished"/> + <translation>Configureer Trilling</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="23"/> <source>Press any controller button to vibrate the controller.</source> - <translation type="unfinished"/> + <translation>Druk op een willekeurige knop om de controller te laten trillen.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="30"/> @@ -4276,12 +4289,12 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="503"/> <source>Settings</source> - <translation type="unfinished"/> + <translation>Instellingen</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="509"/> <source>Enable Accurate Vibration</source> - <translation type="unfinished"/> + <translation>Schakel Nauwkeurige Trillingen In</translation> </message> </context> <context> @@ -4299,12 +4312,12 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_web.ui" line="25"/> <source>yuzu Web Service</source> - <translation>yuzu Web Service</translation> + <translation>yuzu-webservice</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_web.ui" line="31"/> <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source> - <translation>Door je gebruikersnaam en token te geven, ga je akkoord dat yuzu extra gebruiksdata verzameld, waaronder mogelijk gebruikersidentificatie-informatie.</translation> + <translation>Door je gebruikersnaam en token op te geven, ga je ermee akkoord dat yuzu aanvullende gebruiksgegevens verzamelt, die informatie ter identificatie van de gebruiker kunnen bevatten.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_web.ui" line="49"/> @@ -4335,7 +4348,7 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_web.ui" line="118"/> <source>Web Service configuration can only be changed when a public room isn't being hosted.</source> - <translation type="unfinished"/> + <translation>De configuratie van de webservice kan alleen worden gewijzigd als er geen openbare ruimte wordt gehost.</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_web.ui" line="128"/> @@ -4345,17 +4358,17 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_web.ui" line="134"/> <source>Share anonymous usage data with the yuzu team</source> - <translation>Deel anonieme gebruiksdata met het yuzu team</translation> + <translation>Deel anonieme gebruiksdata met het yuzu-team</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_web.ui" line="141"/> <source>Learn more</source> - <translation>Leer meer</translation> + <translation>Meer info</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_web.ui" line="150"/> <source>Telemetry ID:</source> - <translation>Telemetrie ID:</translation> + <translation>Telemetrie-ID:</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_web.ui" line="166"/> @@ -4365,17 +4378,17 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_web.ui" line="180"/> <source>Discord Presence</source> - <translation>Discord Presence</translation> + <translation>Aanwezigheid in Discord</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_web.ui" line="186"/> <source>Show Current Game in your Discord Status</source> - <translation>Toon huidige game in je Discord status</translation> + <translation>Toon huidige game in uw Discord-status</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_web.cpp" line="68"/> <source><a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Learn more</span></a></source> - <translation><a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Leer meer</span></a></translation> + <translation><a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Meer info</span></a></translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_web.cpp" line="72"/> @@ -4391,7 +4404,7 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <location filename="../../src/yuzu/configuration/configure_web.cpp" line="80"/> <location filename="../../src/yuzu/configuration/configure_web.cpp" line="125"/> <source>Telemetry ID: 0x%1</source> - <translation>Telemetrie ID: 0x%1</translation> + <translation>Telemetrie-ID: 0x%1</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_web.cpp" line="91"/> @@ -4413,7 +4426,7 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <location filename="../../src/yuzu/configuration/configure_web.cpp" line="141"/> <source>Unverified, please click Verify before saving configuration</source> <comment>Tooltip</comment> - <translation type="unfinished"/> + <translation>Niet geverifieerd, klik op Verifiëren voordat je de configuratie opslaat</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_web.cpp" line="147"/> @@ -4425,7 +4438,7 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <location filename="../../src/yuzu/configuration/configure_web.cpp" line="164"/> <source>Verified</source> <comment>Tooltip</comment> - <translation type="unfinished"/> + <translation>Geverifiëerd</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_web.cpp" line="169"/> @@ -4441,7 +4454,7 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/configuration/configure_web.cpp" line="172"/> <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source> - <translation>Verificatie mislukt. Check dat uw token correct is en dat uw internet werkt.</translation> + <translation>Verificatie mislukt. Controleer of je je token correct hebt ingevoerd en of je internetverbinding werkt.</translation> </message> </context> <context> @@ -4449,12 +4462,12 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/debugger/controller.cpp" line="20"/> <source>Controller P1</source> - <translation type="unfinished"/> + <translation>Controller P1</translation> </message> <message> <location filename="../../src/yuzu/debugger/controller.cpp" line="58"/> <source>&Controller P1</source> - <translation type="unfinished"/> + <translation>&Controller P1</translation> </message> </context> <context> @@ -4462,42 +4475,42 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="14"/> <source>Direct Connect</source> - <translation type="unfinished"/> + <translation>Directe Verbinding</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="47"/> <source>Server Address</source> - <translation type="unfinished"/> + <translation>Serveradres</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>Serveradres van de host</p></body></html></translation> </message> <message> <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="64"/> <source>Port</source> - <translation type="unfinished"/> + <translation>Poort</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="71"/> <source><html><head/><body><p>Port number the host is listening on</p></body></html></source> - <translation type="unfinished"/> + <translation><html><head/><body><p>Poortnummer waarop de host luistert</p></body></html></translation> </message> <message> <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="97"/> <source>Nickname</source> - <translation type="unfinished"/> + <translation>Gebruikersnaam</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="111"/> <source>Password</source> - <translation type="unfinished"/> + <translation>Wachtwoord</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="153"/> <source>Connect</source> - <translation type="unfinished"/> + <translation>Verbind</translation> </message> </context> <context> @@ -4505,12 +4518,12 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/multiplayer/direct_connect.cpp" line="120"/> <source>Connecting</source> - <translation type="unfinished"/> + <translation>Verbinden</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/direct_connect.cpp" line="125"/> <source>Connect</source> - <translation type="unfinished"/> + <translation>Verbind</translation> </message> </context> <context> @@ -4528,12 +4541,12 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/main.cpp" line="432"/> <source>Broken Vulkan Installation Detected</source> - <translation type="unfinished"/> + <translation>Beschadigde Vulkan-installatie gedetecteerd</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="433"/> <source>Vulkan initialization failed during boot.<br><br>Click <a href='https://yuzu-emu.org/wiki/faq/#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>here for instructions to fix the issue</a>.</source> - <translation type="unfinished"/> + <translation>Vulkan-initialisatie mislukt tijdens het opstarten.<br><br>Klik <a href='https://yuzu-emu.org/wiki/faq/#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>hier voor instructies om het probleem op te lossen</a>.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="824"/> @@ -4544,79 +4557,80 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <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>Schakel Webapplet uit</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="878"/> <source>Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.)</source> - <translation type="unfinished"/> + <translation>Het uitschakelen van de webapplet kan leiden tot ongedefinieerd gedrag en mag alleen gebruikt worden met Super Mario 3D All-Stars. Weet je zeker dat je de webapplet wilt uitschakelen? +(Deze kan opnieuw worden ingeschakeld in de Debug-instellingen).</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="994"/> <source>The amount of shaders currently being built</source> - <translation type="unfinished"/> + <translation>Het aantal shaders dat momenteel wordt gebouwd</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="996"/> <source>The current selected resolution scaling multiplier.</source> - <translation type="unfinished"/> + <translation>De huidige geselecteerde resolutieschaalmultiplier.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="999"/> <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source> - <translation>Huidige emulatie snelheid. Waardes hoger of lager dan 100% betekent dat de emulatie sneller of langzamer loopt dan de Switch.</translation> + <translation>Huidige emulatiesnelheid. Waarden hoger of lager dan 100% geven aan dat de emulatie sneller of langzamer werkt dan een Switch.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1002"/> <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source> - <translation>Hoeveel frames per seconde de game op dit moment weergeeft. Dit zal veranderen van game naar game en van scène naar scène.</translation> + <translation>Hoeveel beelden per seconde het spel momenteel weergeeft. Dit varieert van spel tot spel en van scène tot scène.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1006"/> <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source> - <translation>Tijd gebruikt om een frame van de Switch te emuleren, waarbij framelimiteren of v-sync niet wordt meegerekend. Voor emulatie op volledige snelheid zou dit maximaal 16.67 ms zijn.</translation> + <translation>Tijd die nodig is om een Switch-beeld te emuleren, beeldbeperking of v-sync niet meegerekend. Voor emulatie op volle snelheid mag dit maximaal 16,67 ms zijn.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1156"/> <source>&Clear Recent Files</source> - <translation type="unfinished"/> + <translation>&Wis Recente Bestanden</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1230"/> <source>Emulated mouse is enabled</source> - <translation type="unfinished"/> + <translation>Geëmuleerde muis is ingeschakeld</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>Echte muisinvoer en muispanning zijn niet compatibel. Schakel de geëmuleerde muis uit in de geavanceerde invoerinstellingen om muispanning mogelijk te maken.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1453"/> <source>&Continue</source> - <translation type="unfinished"/> + <translation>&Doorgaan</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1455"/> <source>&Pause</source> - <translation>&Pauzeren</translation> + <translation>&Onderbreken</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1535"/> <source>yuzu is running a game</source> <extracomment>TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping</extracomment> - <translation type="unfinished"/> + <translation>yuzu is een spel aan het uitvoeren</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1668"/> <source>Warning Outdated Game Format</source> - <translation>Waarschuwing Verouderd Spel Formaat</translation> + <translation>Waarschuwing Verouderd Spelformaat</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1669"/> <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br><br>For an explanation of the various Switch formats yuzu supports, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our wiki</a>. This message will not be shown again.</source> - <translation>Je gebruikt gedeconstrueerd ROM map formaat voor dit Spel, dit is een verouderd formaat en is vervangen door formaten zoals NCA, NAX, XCI of NSP. Gedeconstrueerd ROM map heeft geen iconen, metadata en update understeuning.<br><br>Voor een uitleg over welke Switch formaten yuzu ondersteund, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>kijk op onze wiki</a>. Dit bericht word niet nog een keer weergegeven.</translation> + <translation>Je gebruikt het gedeconstrueerde ROM-mapformaat voor dit spel, wat een verouderd formaat is dat vervangen is door andere zoals NCA, NAX, XCI, of NSP. Deconstructed ROM-mappen missen iconen, metadata, en update-ondersteuning.<br><br>Voor een uitleg van de verschillende Switch-formaten die yuzu ondersteunt,<a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'> bekijk onze wiki</a>. Dit bericht wordt niet meer getoond.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1681"/> @@ -4627,7 +4641,7 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/main.cpp" line="1682"/> <source>The ROM format is not supported.</source> - <translation>Het formaat van de ROM is niet ondersteunt.</translation> + <translation>Het ROM-formaat wordt niet ondersteund.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1686"/> @@ -4637,19 +4651,19 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/main.cpp" line="1687"/> <source>yuzu has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-emu.org/help/reference/log-files/'>How to Upload the Log File</a>. </source> - <translation type="unfinished"/> + <translation>yuzu is een fout tegengekomen tijdens het uitvoeren van de videokern. Dit wordt meestal veroorzaakt door verouderde GPU-drivers, inclusief geïntegreerde. Zie het logboek voor meer details. Voor meer informatie over toegang tot het log, zie de volgende pagina: <a href='https://yuzu-emu.org/help/reference/log-files/'>Hoe upload je het logbestand</a>. </translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1702"/> <source>Error while loading ROM! %1</source> <comment>%1 signifies a numeric error code.</comment> - <translation type="unfinished"/> + <translation>Fout tijdens het laden van ROM! %1</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1705"/> <source>%1<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu quickstart guide</a> to redump your files.<br>You can refer to the yuzu wiki</a> or the yuzu Discord</a> for help.</source> <comment>%1 signifies an error string.</comment> - <translation type="unfinished"/> + <translation>%1<br>Volg de <a href='https://yuzu-emu.org/help/quickstart/'>yuzu snelstartgids</a> om je bestanden te redumpen.<br>Je kunt de yuzu-wiki</a>of de yuzu-Discord</a> raadplegen voor hulp.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1716"/> @@ -4659,23 +4673,23 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/main.cpp" line="1858"/> <source>(64-bit)</source> - <translation type="unfinished"/> + <translation>(64-bit)</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1858"/> <source>(32-bit)</source> - <translation type="unfinished"/> + <translation>(32-bit)</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1859"/> <source>%1 %2</source> <comment>%1 is the title name. %2 indicates if the title is 64-bit or 32-bit</comment> - <translation type="unfinished"/> + <translation>%1 %2</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="1917"/> <source>Closing software...</source> - <translation type="unfinished"/> + <translation>Software sluiten...</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2066"/> @@ -4690,58 +4704,58 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/main.cpp" line="2135"/> <source>Error Opening %1 Folder</source> - <translation>Fout tijdens het openen van %1 folder</translation> + <translation>Fout tijdens het openen van %1 map</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2136"/> <location filename="../../src/yuzu/main.cpp" line="2718"/> <source>Folder does not exist!</source> - <translation>Folder bestaat niet!</translation> + <translation>Map bestaat niet!</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2148"/> <source>Error Opening Transferable Shader Cache</source> - <translation>Fout Bij Het Openen Van Overdraagbare Shader Cache</translation> + <translation>Fout bij het openen van overdraagbare shader-cache</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2149"/> <source>Failed to create the shader cache directory for this title.</source> - <translation type="unfinished"/> + <translation>Kon de shader-cache-map voor dit spel niet aanmaken.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2200"/> <source>Error Removing Contents</source> - <translation type="unfinished"/> + <translation>Fout bij het verwijderen van de inhoud</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2202"/> <source>Error Removing Update</source> - <translation type="unfinished"/> + <translation>Fout bij het verwijderen van de update</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2204"/> <source>Error Removing DLC</source> - <translation type="unfinished"/> + <translation>Fout bij het verwijderen van DLC</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2213"/> <source>Remove Installed Game Contents?</source> - <translation type="unfinished"/> + <translation>Geïnstalleerde Spelinhoud Verwijderen?</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2215"/> <source>Remove Installed Game Update?</source> - <translation type="unfinished"/> + <translation>Geïnstalleerde Spel-update Verwijderen?</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2217"/> <source>Remove Installed Game DLC?</source> - <translation type="unfinished"/> + <translation>Geïnstalleerde Spel-DLC Verwijderen?</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2223"/> <source>Remove Entry</source> - <translation type="unfinished"/> + <translation>Verwijder Invoer</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2254"/> @@ -4751,147 +4765,147 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <location filename="../../src/yuzu/main.cpp" line="2398"/> <location filename="../../src/yuzu/main.cpp" line="2421"/> <source>Successfully Removed</source> - <translation type="unfinished"/> + <translation>Met Succes Verwijderd</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2255"/> <source>Successfully removed the installed base game.</source> - <translation type="unfinished"/> + <translation>Het geïnstalleerde basisspel is succesvol verwijderd.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2259"/> <source>The base game is not installed in the NAND and cannot be removed.</source> - <translation type="unfinished"/> + <translation>Het basisspel is niet geïnstalleerd in de NAND en kan niet worden verwijderd.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2271"/> <source>Successfully removed the installed update.</source> - <translation type="unfinished"/> + <translation>De geïnstalleerde update is succesvol verwijderd.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2274"/> <source>There is no update installed for this title.</source> - <translation type="unfinished"/> + <translation>Er is geen update geïnstalleerd voor dit spel.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2297"/> <source>There are no DLC installed for this title.</source> - <translation type="unfinished"/> + <translation>Er is geen DLC geïnstalleerd voor dit spel.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2302"/> <source>Successfully removed %1 installed DLC.</source> - <translation type="unfinished"/> + <translation>%1 geïnstalleerde DLC met succes verwijderd.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2310"/> <source>Delete OpenGL Transferable Shader Cache?</source> - <translation type="unfinished"/> + <translation>Overdraagbare OpenGL-shader-cache Verwijderen?</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2312"/> <source>Delete Vulkan Transferable Shader Cache?</source> - <translation type="unfinished"/> + <translation>Overdraagbare Vulkan-shader-cache Verwijderen?</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2314"/> <source>Delete All Transferable Shader Caches?</source> - <translation type="unfinished"/> + <translation>Alle Overdraagbare Shader-caches Verwijderen?</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2316"/> <source>Remove Custom Game Configuration?</source> - <translation type="unfinished"/> + <translation>Aangepaste Spelconfiguratie Verwijderen?</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2322"/> <source>Remove File</source> - <translation type="unfinished"/> + <translation>Verwijder Bestand</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2359"/> <location filename="../../src/yuzu/main.cpp" line="2367"/> <source>Error Removing Transferable Shader Cache</source> - <translation type="unfinished"/> + <translation>Fout bij het verwijderen van Overdraagbare Shader-cache</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2360"/> <location filename="../../src/yuzu/main.cpp" line="2394"/> <source>A shader cache for this title does not exist.</source> - <translation>Er bestaat geen shader cache voor deze game</translation> + <translation>Er bestaat geen shader-cache voor dit spel.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2365"/> <source>Successfully removed the transferable shader cache.</source> - <translation type="unfinished"/> + <translation>De overdraagbare shader-cache is verwijderd.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2368"/> <source>Failed to remove the transferable shader cache.</source> - <translation type="unfinished"/> + <translation>Kon de overdraagbare shader-cache niet verwijderen.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2383"/> <source>Error Removing Vulkan Driver Pipeline Cache</source> - <translation type="unfinished"/> + <translation>Fout bij het verwijderen van Pijplijn-cache van Vulkan-driver</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2384"/> <source>Failed to remove the driver pipeline cache.</source> - <translation type="unfinished"/> + <translation>Kon de pijplijn-cache van de driver niet verwijderen.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2393"/> <location filename="../../src/yuzu/main.cpp" line="2401"/> <source>Error Removing Transferable Shader Caches</source> - <translation type="unfinished"/> + <translation>Fout bij het verwijderen van overdraagbare shader-caches</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2399"/> <source>Successfully removed the transferable shader caches.</source> - <translation type="unfinished"/> + <translation>De overdraagbare shader-caches zijn verwijderd.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2402"/> <source>Failed to remove the transferable shader cache directory.</source> - <translation type="unfinished"/> + <translation>Kon de overdraagbare shader-cache-map niet verwijderen.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2415"/> <location filename="../../src/yuzu/main.cpp" line="2424"/> <source>Error Removing Custom Configuration</source> - <translation type="unfinished"/> + <translation>Fout bij het verwijderen van aangepaste configuratie</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2416"/> <source>A custom configuration for this title does not exist.</source> - <translation type="unfinished"/> + <translation>Er bestaat geen aangepaste configuratie voor dit spel.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2422"/> <source>Successfully removed the custom game configuration.</source> - <translation type="unfinished"/> + <translation>De aangepaste spelconfiguratie is verwijderd.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2425"/> <source>Failed to remove the custom game configuration.</source> - <translation type="unfinished"/> + <translation>Kon de aangepaste spelconfiguratie niet verwijderen.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2432"/> <location filename="../../src/yuzu/main.cpp" line="2511"/> <source>RomFS Extraction Failed!</source> - <translation>RomFS Extractie Mislukt!</translation> + <translation>RomFS-extractie Mislukt!</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2433"/> <source>There was an error copying the RomFS files or the user cancelled the operation.</source> - <translation>Er was een fout tijdens het kopiëren van de RomFS bestanden of de gebruiker heeft de operatie geannuleerd.</translation> + <translation>Er is een fout opgetreden bij het kopiëren van de RomFS-bestanden of de gebruiker heeft de bewerking geannuleerd.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2491"/> <source>Full</source> - <translation>Vol</translation> + <translation>Volledig</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2491"/> @@ -4901,17 +4915,17 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/main.cpp" line="2493"/> <source>Select RomFS Dump Mode</source> - <translation>Selecteer RomFS Dump Mode</translation> + <translation>Selecteer RomFS-dumpmodus</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2494"/> <source>Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure.</source> - <translation>Selecteer alstublieft hoe je de RomFS wilt dumpen.<br>Volledig kopieërd alle bestanden in een map terwijl <br> skelet maakt alleen het map structuur.</translation> + <translation>Selecteer hoe je de RomFS gedumpt wilt hebben.<br>Volledig zal alle bestanden naar de nieuwe map kopiëren, terwijl <br>Skelet alleen de mapstructuur zal aanmaken.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2512"/> <source>There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root</source> - <translation type="unfinished"/> + <translation>Er is niet genoeg vrije ruimte op %1 om de RomFS uit te pakken. Maak ruimte vrij of kies een andere dumpmap bij Emulatie > Configuratie > Systeem > Bestandssysteem > Dump Root.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2519"/> @@ -4927,12 +4941,12 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/main.cpp" line="2526"/> <source>RomFS Extraction Succeeded!</source> - <translation>RomFS Extractie Geslaagd!</translation> + <translation>RomFS-extractie Geslaagd!</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2527"/> <source>The operation completed successfully.</source> - <translation>De operatie is succesvol voltooid.</translation> + <translation>De bewerking is succesvol voltooid.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2571"/> @@ -4941,47 +4955,47 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <location filename="../../src/yuzu/main.cpp" line="2687"/> <location filename="../../src/yuzu/main.cpp" line="2695"/> <source>Create Shortcut</source> - <translation type="unfinished"/> + <translation>Maak Snelkoppeling</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2572"/> <source>This will create a shortcut to the current AppImage. This may not work well if you update. Continue?</source> - <translation type="unfinished"/> + <translation>Dit maakt een snelkoppeling naar de huidige AppImage. Dit werkt mogelijk niet goed als je een update uitvoert. Doorgaan?</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2596"/> <source>Cannot create shortcut on desktop. Path "%1" does not exist.</source> - <translation type="unfinished"/> + <translation>Kan geen snelkoppeling op het bureaublad maken. Pad "%1" bestaat niet.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2606"/> <source>Cannot create shortcut in applications menu. Path "%1" does not exist and cannot be created.</source> - <translation type="unfinished"/> + <translation>Kan geen snelkoppeling maken in toepassingen menu. Pad "%1" bestaat niet en kan niet worden aangemaakt.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2623"/> <source>Create Icon</source> - <translation type="unfinished"/> + <translation>Maak Icoon</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2624"/> <source>Cannot create icon file. Path "%1" does not exist and cannot be created.</source> - <translation type="unfinished"/> + <translation>Kan geen icoonbestand maken. Pad "%1" bestaat niet en kan niet worden aangemaakt.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2675"/> <source>Start %1 with the yuzu Emulator</source> - <translation type="unfinished"/> + <translation>Voer %1 uiit met de yuzu-emulator</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2688"/> <source>Failed to create a shortcut at %1</source> - <translation type="unfinished"/> + <translation>Er is geen snelkoppeling gemaakt op %1</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2696"/> <source>Successfully created a shortcut to %1</source> - <translation type="unfinished"/> + <translation>Succesvol een snelkoppeling naar %1 gemaakt</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2717"/> @@ -5001,13 +5015,13 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/main.cpp" line="2756"/> <source>The game properties could not be loaded.</source> - <translation>De eigenschappen van de game kunnen niet geladen worden.</translation> + <translation>De speleigenschappen kunnen niet geladen worden.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2773"/> <source>Switch Executable (%1);;All Files (*.*)</source> <comment>%1 is an identifier for the Switch executable file extensions.</comment> - <translation>Switch Executable (%1);;Alle bestanden (*.*)</translation> + <translation>Switch Executable (%1);;Alle Bestanden (*.*)</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2777"/> @@ -5017,7 +5031,7 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/main.cpp" line="2790"/> <source>Open Extracted ROM Directory</source> - <translation>Open Gedecomprimeerd ROM Map</translation> + <translation>Open Uitgepakte ROM-map</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2801"/> @@ -5027,22 +5041,22 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <message> <location filename="../../src/yuzu/main.cpp" line="2802"/> <source>The directory you have selected does not contain a 'main' file.</source> - <translation>De map die je hebt geselecteerd bevat geen 'main' bestand.</translation> + <translation>De map die je hebt geselecteerd bevat geen 'main'-bestand.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2812"/> <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source> - <translation type="unfinished"/> + <translation>Installeerbaar Switch-bestand (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2817"/> <source>Install Files</source> - <translation type="unfinished"/> + <translation>Installeer Bestanden</translation> </message> <message numerus="yes"> <location filename="../../src/yuzu/main.cpp" line="2863"/> <source>%n file(s) remaining</source> - <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation> + <translation><numerusform>%n bestand(en) resterend</numerusform><numerusform>%n bestand(en) resterend</numerusform></translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2865"/> @@ -5053,71 +5067,78 @@ Sleep punten om positie te veranderen, of dubbel klik één van de tabel cellen <location filename="../../src/yuzu/main.cpp" line="2911"/> <location filename="../../src/yuzu/main.cpp" line="2925"/> <source>Install Results</source> - <translation type="unfinished"/> + <translation>Installeerresultaten</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="2912"/> <source>To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC.</source> - <translation type="unfinished"/> + <translation>Om mogelijke conflicten te voorkomen, raden we gebruikers af om basisgames te installeren op de NAND. +Gebruik deze functie alleen om updates en DLC te installeren.</translation> </message> <message numerus="yes"> <location filename="../../src/yuzu/main.cpp" line="2918"/> <source>%n file(s) were newly installed </source> - <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation> + <translation><numerusform>%n bestand(en) zijn recent geïnstalleerd +</numerusform><numerusform>%n bestand(en) zijn recent geïnstalleerd +</numerusform></translation> </message> <message numerus="yes"> <location filename="../../src/yuzu/main.cpp" line="2921"/> <source>%n file(s) were overwritten </source> - <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation> + <translation><numerusform>%n bestand(en) werden overschreven +</numerusform><numerusform>%n bestand(en) werden overschreven +</numerusform></translation> </message> <message numerus="yes"> <location filename="../../src/yuzu/main.cpp" line="2923"/> <source>%n file(s) failed to install </source> - <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation> + <translation><numerusform>%n bestand(en) niet geïnstalleerd +</numerusform><numerusform>%n bestand(en) niet geïnstalleerd +</numerusform></translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3024"/> <source>System Application</source> - <translation>Systeem Applicatie</translation> + <translation>Systeemapplicatie</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3025"/> <source>System Archive</source> - <translation>Systeem Archief</translation> + <translation>Systeemarchief</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3026"/> <source>System Application Update</source> - <translation>Systeem Applicatie Update</translation> + <translation>Systeemapplicatie-update</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3027"/> <source>Firmware Package (Type A)</source> - <translation>Filmware Pakket (Type A)</translation> + <translation>Filmware-pakket (Type A)</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3028"/> <source>Firmware Package (Type B)</source> - <translation>Filmware Pakket (Type B)</translation> + <translation>Filmware-pakket (Type B)</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3029"/> <source>Game</source> - <translation>Game</translation> + <translation>Spel</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3030"/> <source>Game Update</source> - <translation>Game Update</translation> + <translation>Spelupdate</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3031"/> <source>Game DLC</source> - <translation>Game DLC</translation> + <translation>Spel-DLC</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3032"/> @@ -5127,14 +5148,14 @@ Please, only use this feature to install updates and DLC.</source> <message> <location filename="../../src/yuzu/main.cpp" line="3035"/> <source>Select NCA Install Type...</source> - <translation>Selecteer NCA Installatie Type...</translation> + <translation>Selecteer NCA-installatiesoort...</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3036"/> <source>Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.)</source> - <translation>Selecteer het type titel hoe je wilt dat deze NCA installeerd: -(In de meeste gevallen is de standaard 'Game' juist.)</translation> + <translation>Selecteer het type titel waarin je deze NCA wilt installeren: +(In de meeste gevallen is de standaard "Spel" prima).</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3042"/> @@ -5144,7 +5165,7 @@ Please, only use this feature to install updates and DLC.</source> <message> <location filename="../../src/yuzu/main.cpp" line="3043"/> <source>The title type you selected for the NCA is invalid.</source> - <translation>Het type title dat je hebt geselecteerd voor de NCA is ongeldig.</translation> + <translation>Het soort title dat je hebt geselecteerd voor de NCA is ongeldig.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3078"/> @@ -5165,81 +5186,81 @@ 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>Er is niet voldaan aan de hardwarevereisten</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3183"/> <location filename="../../src/yuzu/main.cpp" line="3202"/> <source>Your system does not meet the recommended hardware requirements. Compatibility reporting has been disabled.</source> - <translation type="unfinished"/> + <translation>Je systeem voldoet niet aan de aanbevolen hardwarevereisten. Compatibiliteitsrapportage is uitgeschakeld.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3194"/> <source>Missing yuzu Account</source> - <translation>Je yuzu account mist</translation> + <translation>yuzu-account Ontbreekt</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3195"/> <source>In order to submit a game compatibility test case, you must link your yuzu account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration &gt; Web.</source> - <translation>Om game campatibiliteit te raporteren, moet je je yuzu account koppelen.<br><br/> Om je yuzu account te koppelen, ga naar Emulatie &gt; Configuratie &gt; Web.</translation> + <translation>Om een spelcompatibiliteitstest in te dienen, moet je je yuzu-account koppelen.<br><br/>Om je yuzu-account te koppelen, ga naar Emulatie &gt; Configuratie &gt; Web.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3210"/> <source>Error opening URL</source> - <translation type="unfinished"/> + <translation>Fout bij het openen van URL</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3211"/> <source>Unable to open the URL "%1".</source> - <translation type="unfinished"/> + <translation>Kan de URL "%1" niet openen.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3514"/> <source>TAS Recording</source> - <translation type="unfinished"/> + <translation>TAS-opname</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3515"/> <source>Overwrite file of player 1?</source> - <translation type="unfinished"/> + <translation>Het bestand van speler 1 overschrijven?</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3541"/> <source>Invalid config detected</source> - <translation type="unfinished"/> + <translation>Ongeldige configuratie gedetecteerd</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3542"/> <source>Handheld controller can't be used on docked mode. Pro controller will be selected.</source> - <translation type="unfinished"/> + <translation>Handheld-controller kan niet gebruikt worden in docked-modus. Pro controller wordt geselecteerd.</translation> </message> <message> <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>De huidige amiibo is verwijderd</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3717"/> <source>Error</source> - <translation type="unfinished"/> + <translation>Fout</translation> </message> <message> <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>Het huidige spel is niet op zoek naar amiibo's</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3723"/> <source>Amiibo File (%1);; All Files (*.*)</source> - <translation>Amiibo Bestand (%1);; Alle Bestanden (*.*)</translation> + <translation>Amiibo-bestand (%1);; Alle Bestanden (*.*)</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3724"/> @@ -5249,57 +5270,57 @@ Please, only use this feature to install updates and DLC.</source> <message> <location filename="../../src/yuzu/main.cpp" line="3736"/> <source>Error loading Amiibo data</source> - <translation>Fout tijdens het laden van de Amiibo data</translation> + <translation>Fout tijdens het laden van de Amiibo-gegevens</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3746"/> <source>The selected file is not a valid amiibo</source> - <translation type="unfinished"/> + <translation>Het geselecteerde bestand is geen geldige 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>Het geselecteerde bestand is al in gebruik</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3755"/> <source>An unknown error occurred</source> - <translation type="unfinished"/> + <translation>Er is een onbekende fout opgetreden</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3807"/> <source>Capture Screenshot</source> - <translation>Screenshot Vastleggen</translation> + <translation>Leg Schermafbeelding Vast</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3808"/> <source>PNG Image (*.png)</source> - <translation>PNG afbeelding (*.png)</translation> + <translation>PNG-afbeelding (*.png)</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3891"/> <source>TAS state: Running %1/%2</source> - <translation type="unfinished"/> + <translation>TAS-status: %1/%2 In werking</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3895"/> <source>TAS state: Recording %1</source> - <translation type="unfinished"/> + <translation>TAS-status: %1 Aan het opnemen</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3897"/> <source>TAS state: Idle %1/%2</source> - <translation type="unfinished"/> + <translation>TAS-status: %1/%2 Inactief</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3901"/> <source>TAS State: Invalid</source> - <translation type="unfinished"/> + <translation>TAS-status: Ongeldig</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3915"/> <source>&Stop Running</source> - <translation type="unfinished"/> + <translation>&Stop Uitvoering</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3915"/> @@ -5309,23 +5330,23 @@ Please, only use this feature to install updates and DLC.</source> <message> <location filename="../../src/yuzu/main.cpp" line="3916"/> <source>Stop R&ecording</source> - <translation type="unfinished"/> + <translation>Stop Opname</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3916"/> <source>R&ecord</source> - <translation type="unfinished"/> + <translation>Opnemen</translation> </message> <message numerus="yes"> <location filename="../../src/yuzu/main.cpp" line="3940"/> <source>Building: %n shader(s)</source> - <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation> + <translation><numerusform>Bouwen: %n shader(s)</numerusform><numerusform>Bouwen: %n shader(s)</numerusform></translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3949"/> <source>Scale: %1x</source> <comment>%1 is the resolution scaling factor</comment> - <translation type="unfinished"/> + <translation>Schaal: %1x</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3952"/> @@ -5340,7 +5361,7 @@ Please, only use this feature to install updates and DLC.</source> <message> <location filename="../../src/yuzu/main.cpp" line="3960"/> <source>Game: %1 FPS (Unlocked)</source> - <translation type="unfinished"/> + <translation>Spel: %1 FPS (Ontgrendeld)</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3963"/> @@ -5355,110 +5376,110 @@ Please, only use this feature to install updates and DLC.</source> <message> <location filename="../../src/yuzu/main.cpp" line="3976"/> <source>GPU NORMAL</source> - <translation type="unfinished"/> + <translation>GPU NORMAAL</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3981"/> <source>GPU HIGH</source> - <translation type="unfinished"/> + <translation>GPU HOOG</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3986"/> <source>GPU EXTREME</source> - <translation type="unfinished"/> + <translation>GPU EXTREEM</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="3991"/> <source>GPU ERROR</source> - <translation type="unfinished"/> + <translation>GPU FOUT</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4001"/> <source>DOCKED</source> - <translation type="unfinished"/> + <translation>DOCKED</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4001"/> <source>HANDHELD</source> - <translation type="unfinished"/> + <translation>HANDHELD</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4008"/> <source>OPENGL</source> - <translation type="unfinished"/> + <translation>OPENGL</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4011"/> <source>VULKAN</source> - <translation type="unfinished"/> + <translation>VULKAN</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4014"/> <source>NULL</source> - <translation type="unfinished"/> + <translation>NULL</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4023"/> <source>NEAREST</source> - <translation type="unfinished"/> + <translation>NEAREST</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4026"/> <location filename="../../src/yuzu/main.cpp" line="4041"/> <source>BILINEAR</source> - <translation type="unfinished"/> + <translation>BILINEAR</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4029"/> <source>BICUBIC</source> - <translation type="unfinished"/> + <translation>BICUBIC</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4032"/> <source>GAUSSIAN</source> - <translation type="unfinished"/> + <translation>GAUSSIAN</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4035"/> <source>SCALEFORCE</source> - <translation type="unfinished"/> + <translation>SCALEFORCE</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4038"/> <source>FSR</source> - <translation type="unfinished"/> + <translation>FSR</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4050"/> <location filename="../../src/yuzu/main.cpp" line="4059"/> <source>NO AA</source> - <translation type="unfinished"/> + <translation>GEEN AA</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4053"/> <source>FXAA</source> - <translation type="unfinished"/> + <translation>FXAA</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4056"/> <source>SMAA</source> - <translation type="unfinished"/> + <translation>SMAA</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4069"/> <source>VOLUME: MUTE</source> - <translation type="unfinished"/> + <translation>VOLUME: GEDEMPT</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>VOLUME: %1%</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4153"/> <source>Confirm Key Rederivation</source> - <translation>Bevestig Sleutel Herafleiding</translation> + <translation>Bevestig Sleutelherhaling</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4154"/> @@ -5469,61 +5490,62 @@ Please make sure this is what you want and optionally make backups. This will delete your autogenerated key files and re-run the key derivation module.</source> - <translation>Je bent op het punt al je sleutels geforceerd opnieuw te verkrijgen. -Als je niet weet wat dit doet of wat je aan het doen bent, -dit is potentieel een vernietigende actie. -Zorg ervoor dat je zeker weet dat dit is wat je wilt doen -en optioneel maak backups. + <translation>Je staat op het punt om al je sleutels te forceren. +Als je niet weet wat dit betekent of wat je doet, +is dit een potentieel destructieve actie. +Zorg ervoor dat dit is wat je wilt +en maak eventueel back-ups. -Dit zal je automatisch gegenereerde sleutel bestanden verwijderen en de sleutel verkrijger module opnieuw starten</translation> +Dit zal je automatisch gegenereerde sleutelbestanden verwijderen en de sleutelafleidingsmodule opnieuw uitvoeren.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4186"/> <source>Missing fuses</source> - <translation type="unfinished"/> + <translation>Missing fuses</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4189"/> <source> - Missing BOOT0</source> - <translation type="unfinished"/> + <translation> - BOOT0 Ontbreekt</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4192"/> <source> - Missing BCPKG2-1-Normal-Main</source> - <translation type="unfinished"/> + <translation> - BCPKG2-1-Normal-Main Ontbreekt</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4195"/> <source> - Missing PRODINFO</source> - <translation type="unfinished"/> + <translation> - PRODINFO Ontbreekt</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4199"/> <source>Derivation Components Missing</source> - <translation type="unfinished"/> + <translation>Afleidingscomponenten ontbreken</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4200"/> <source>Encryption keys are missing. <br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu quickstart guide</a> to get all your keys, firmware and games.<br><br><small>(%1)</small></source> - <translation type="unfinished"/> + <translation>Encryptiesleutels ontbreken. <br>Volg <a href='https://yuzu-emu.org/help/quickstart/'>de yuzu-snelstartgids</a> om al je sleutels, firmware en spellen te krijgen.<br><br><small>(%1)</small></translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4209"/> <source>Deriving keys... This may take up to a minute depending on your system's performance.</source> - <translation>Dit zal misschien een paar minuten duren gebaseerd -op je systeem's performatie.</translation> + <translation>Sleutels afleiden... +Dit kan tot een minuut duren, +afhankelijk van de prestaties van je systeem.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4211"/> <source>Deriving Keys</source> - <translation>Sleutels afleiden</translation> + <translation>Sleutels Afleiden</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4256"/> <source>Select RomFS Dump Target</source> - <translation>Selecteer RomFS Dump Doel</translation> + <translation>Selecteer RomFS-dumpdoel</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4257"/> @@ -5545,7 +5567,7 @@ op je systeem's performatie.</translation> <message> <location filename="../../src/yuzu/main.cpp" line="4369"/> <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source> - <translation>Weet je zeker dat je de emulatie wilt stoppen? Alle onopgeslagen voortgang will verloren gaan.</translation> + <translation>Weet je zeker dat je de emulatie wilt stoppen? Alle niet opgeslagen voortgang zal verloren gaan.</translation> </message> <message> <location filename="../../src/yuzu/main.cpp" line="4378"/> @@ -5554,7 +5576,7 @@ op je systeem's performatie.</translation> Would you like to bypass this and exit anyway?</source> <translation>De momenteel actieve toepassing heeft yuzu gevraagd om niet af te sluiten. -Wilt u dit omzeilen en toch afsluiten?</translation> +Wil je toch afsluiten?</translation> </message> </context> <context> @@ -5563,43 +5585,43 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <location filename="../../src/yuzu/bootmanager.cpp" line="974"/> <location filename="../../src/yuzu/bootmanager.cpp" line="991"/> <source>OpenGL not available!</source> - <translation type="unfinished"/> + <translation>OpenGL niet beschikbaar!</translation> </message> <message> <location filename="../../src/yuzu/bootmanager.cpp" line="975"/> <source>OpenGL shared contexts are not supported.</source> - <translation type="unfinished"/> + <translation>OpenGL gedeelde contexten worden niet ondersteund.</translation> </message> <message> <location filename="../../src/yuzu/bootmanager.cpp" line="992"/> <source>yuzu has not been compiled with OpenGL support.</source> - <translation type="unfinished"/> + <translation>yuzu is niet gecompileerd met OpenGL-ondersteuning.</translation> </message> <message> <location filename="../../src/yuzu/bootmanager.cpp" line="1016"/> <location filename="../../src/yuzu/bootmanager.cpp" line="1036"/> <source>Error while initializing OpenGL!</source> - <translation type="unfinished"/> + <translation>Fout tijdens het initialiseren van OpenGL!</translation> </message> <message> <location filename="../../src/yuzu/bootmanager.cpp" line="1017"/> <source>Your GPU may not support OpenGL, or you do not have the latest graphics driver.</source> - <translation type="unfinished"/> + <translation>Je GPU ondersteunt mogelijk geen OpenGL, of je hebt niet de laatste grafische stuurprogramma.</translation> </message> <message> <location filename="../../src/yuzu/bootmanager.cpp" line="1026"/> <source>Error while initializing OpenGL 4.6!</source> - <translation type="unfinished"/> + <translation>Fout tijdens het initialiseren van OpenGL 4.6!</translation> </message> <message> <location filename="../../src/yuzu/bootmanager.cpp" line="1027"/> <source>Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1</source> - <translation type="unfinished"/> + <translation>Je GPU ondersteunt mogelijk OpenGL 4.6 niet, of je hebt niet het laatste grafische stuurprogramma.<br><br>GL Renderer:<br>%1</translation> </message> <message> <location filename="../../src/yuzu/bootmanager.cpp" line="1037"/> <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2</source> - <translation type="unfinished"/> + <translation>Je GPU ondersteunt mogelijk een of meer vereiste OpenGL-extensies niet. Zorg ervoor dat je het laatste grafische stuurprogramma hebt.<br><br>GL Renderer:<br>%1<br><br>Ondersteunde extensies:<br>%2</translation> </message> </context> <context> @@ -5607,32 +5629,32 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/game_list.cpp" line="532"/> <source>Favorite</source> - <translation type="unfinished"/> + <translation>Favoriet</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="534"/> <source>Start Game</source> - <translation type="unfinished"/> + <translation>Start Spel</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="536"/> <source>Start Game without Custom Configuration</source> - <translation type="unfinished"/> + <translation>Start Spel zonder Aangepaste Configuratie</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="538"/> <source>Open Save Data Location</source> - <translation>Open Locatie Van Save Gegevens </translation> + <translation>Open Locatie van Save-data</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="539"/> <source>Open Mod Data Location</source> - <translation>Open Mod Data Locatie</translation> + <translation>Open Locatie van Mod-data</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="541"/> <source>Open Transferable Pipeline Cache</source> - <translation type="unfinished"/> + <translation>Open Overdraagbare Pijplijn-cache</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="543"/> @@ -5642,37 +5664,37 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/game_list.cpp" line="544"/> <source>Remove Installed Update</source> - <translation type="unfinished"/> + <translation>Verwijder Geïnstalleerde Update</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="545"/> <source>Remove All Installed DLC</source> - <translation type="unfinished"/> + <translation>Verwijder Alle Geïnstalleerde DLC's</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="546"/> <source>Remove Custom Configuration</source> - <translation type="unfinished"/> + <translation>Verwijder Aangepaste Configuraties</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="547"/> <source>Remove OpenGL Pipeline Cache</source> - <translation type="unfinished"/> + <translation>Verwijder OpenGL-pijplijn-cache</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="548"/> <source>Remove Vulkan Pipeline Cache</source> - <translation type="unfinished"/> + <translation>Verwijder Vulkan-pijplijn-cache</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="550"/> <source>Remove All Pipeline Caches</source> - <translation type="unfinished"/> + <translation>Verwijder Alle Pijplijn-caches</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="551"/> <source>Remove All Installed Contents</source> - <translation type="unfinished"/> + <translation>Verwijder Alle Geïnstalleerde Inhoud</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="552"/> @@ -5683,32 +5705,32 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/game_list.cpp" line="554"/> <source>Dump RomFS to SDMC</source> - <translation type="unfinished"/> + <translation>Dump RomFS naar SDMC</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="555"/> <source>Copy Title ID to Clipboard</source> - <translation>Kopieer Titel ID naar Klembord</translation> + <translation>Kopiëer Titel-ID naar Klembord</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="556"/> <source>Navigate to GameDB entry</source> - <translation>Navigeer naar GameDB inzending</translation> + <translation>Navigeer naar GameDB-invoer</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="558"/> <source>Create Shortcut</source> - <translation type="unfinished"/> + <translation>Maak Snelkoppeling</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="559"/> <source>Add to Desktop</source> - <translation type="unfinished"/> + <translation>Toevoegen aan Bureaublad</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="561"/> <source>Add to Applications Menu</source> - <translation type="unfinished"/> + <translation>Toevoegen aan menu Toepassingen</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="564"/> @@ -5718,27 +5740,27 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/game_list.cpp" line="644"/> <source>Scan Subfolders</source> - <translation>Scan Subfolders</translation> + <translation>Scan Submappen</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="645"/> <source>Remove Game Directory</source> - <translation>Verwijder Game Directory</translation> + <translation>Verwijder Spelmap</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="664"/> <source>▲ Move Up</source> - <translation type="unfinished"/> + <translation>▲ Omhoog</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="665"/> <source>▼ Move Down</source> - <translation type="unfinished"/> + <translation>▼ Omlaag</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="666"/> <source>Open Directory Location</source> - <translation>Open Directory Locatie</translation> + <translation>Open Maplocatie</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="711"/> @@ -5758,12 +5780,12 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/game_list.cpp" line="777"/> <source>Add-ons</source> - <translation>Toevoegingen</translation> + <translation>Add-ons</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="778"/> <source>File type</source> - <translation>Bestands type</translation> + <translation>Bestandssoort</translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="779"/> @@ -5776,12 +5798,12 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/game_list_p.h" line="149"/> <source>Ingame</source> - <translation type="unfinished"/> + <translation>In het spel</translation> </message> <message> <location filename="../../src/yuzu/game_list_p.h" line="149"/> <source>Game starts, but crashes or major glitches prevent it from being completed.</source> - <translation type="unfinished"/> + <translation>Het spel start, maar crashes of grote glitches voorkomen dat het wordt voltooid.</translation> </message> <message> <location filename="../../src/yuzu/game_list_p.h" line="151"/> @@ -5791,17 +5813,17 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/game_list_p.h" line="151"/> <source>Game can be played without issues.</source> - <translation type="unfinished"/> + <translation>Het spel kan zonder problemen gespeeld worden.</translation> </message> <message> <location filename="../../src/yuzu/game_list_p.h" line="152"/> <source>Playable</source> - <translation type="unfinished"/> + <translation>Speelbaar</translation> </message> <message> <location filename="../../src/yuzu/game_list_p.h" line="152"/> <source>Game functions with minor graphical or audio glitches and is playable from start to finish.</source> - <translation type="unfinished"/> + <translation>Het spel werkt met kleine grafische of audiofouten en is speelbaar van begin tot eind.</translation> </message> <message> <location filename="../../src/yuzu/game_list_p.h" line="155"/> @@ -5811,17 +5833,17 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/game_list_p.h" line="155"/> <source>Game loads, but is unable to progress past the Start Screen.</source> - <translation type="unfinished"/> + <translation>Het spel wordt geladen, maar komt niet verder dan het startscherm.</translation> </message> <message> <location filename="../../src/yuzu/game_list_p.h" line="156"/> <source>Won't Boot</source> - <translation>Start Niet</translation> + <translation>Start niet op</translation> </message> <message> <location filename="../../src/yuzu/game_list_p.h" line="156"/> <source>The game crashes when attempting to startup.</source> - <translation>De Game crasht wanneer hij probeert op te starten.</translation> + <translation>Het spel loopt vast bij het opstarten.</translation> </message> <message> <location filename="../../src/yuzu/game_list_p.h" line="157"/> @@ -5831,7 +5853,7 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/game_list_p.h" line="157"/> <source>The game has not yet been tested.</source> - <translation>Deze Game is nog niet getest.</translation> + <translation>Het spel is nog niet getest.</translation> </message> </context> <context> @@ -5839,7 +5861,7 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/game_list.cpp" line="952"/> <source>Double-click to add a new folder to the game list</source> - <translation>Dubbel-klik om een nieuwe map toe te voegen aan de lijst met games</translation> + <translation>Dubbel-klik om een nieuwe map toe te voegen aan de spellijst</translation> </message> </context> <context> @@ -5847,7 +5869,7 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message numerus="yes"> <location filename="../../src/yuzu/game_list.cpp" line="86"/> <source>%1 of %n result(s)</source> - <translation type="unfinished"><numerusform></numerusform><numerusform></numerusform></translation> + <translation><numerusform>%1 van %n resultaat(en)</numerusform><numerusform>%1 van %n resultaat(en)</numerusform></translation> </message> <message> <location filename="../../src/yuzu/game_list.cpp" line="791"/> @@ -5857,7 +5879,7 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/game_list.cpp" line="792"/> <source>Enter pattern to filter</source> - <translation>Voer patroon in om te filteren:</translation> + <translation>Voer patroon in om te filteren</translation> </message> </context> <context> @@ -5865,22 +5887,22 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/multiplayer/host_room.ui" line="14"/> <source>Create Room</source> - <translation type="unfinished"/> + <translation>Maak Kamer</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/host_room.ui" line="37"/> <source>Room Name</source> - <translation type="unfinished"/> + <translation>Kamernaam</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/host_room.ui" line="51"/> <source>Preferred Game</source> - <translation type="unfinished"/> + <translation>Voorkeursspel</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/host_room.ui" line="61"/> <source>Max Players</source> - <translation type="unfinished"/> + <translation>Maximum Spelers</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/host_room.ui" line="91"/> @@ -5890,42 +5912,42 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/multiplayer/host_room.ui" line="101"/> <source>(Leave blank for open game)</source> - <translation type="unfinished"/> + <translation>(Laat leeg voor open spel)</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/host_room.ui" line="118"/> <source>Password</source> - <translation type="unfinished"/> + <translation>Wachtwoord</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/host_room.ui" line="125"/> <source>Port</source> - <translation type="unfinished"/> + <translation>Poort</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/host_room.ui" line="139"/> <source>Room Description</source> - <translation>Kamer Beschrijving</translation> + <translation>Kamerbeschrijving</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/host_room.ui" line="153"/> <source>Load Previous Ban List</source> - <translation type="unfinished"/> + <translation>Laad Vorige Banlijst</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/host_room.ui" line="184"/> <source>Public</source> - <translation type="unfinished"/> + <translation>Openbaar</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/host_room.ui" line="189"/> <source>Unlisted</source> - <translation type="unfinished"/> + <translation>Niet Vermeld</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/host_room.ui" line="197"/> <source>Host Room</source> - <translation type="unfinished"/> + <translation>Hostkamer</translation> </message> </context> <context> @@ -5933,13 +5955,14 @@ Wilt u dit omzeilen en toch afsluiten?</translation> <message> <location filename="../../src/yuzu/multiplayer/host_room.cpp" line="182"/> <source>Error</source> - <translation type="unfinished"/> + <translation>Fout</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/host_room.cpp" line="183"/> <source>Failed to announce the room to the public lobby. In order to host a room publicly, you must have a valid yuzu account configured in Emulation -> Configure -> Web. If you do not want to publish a room in the public lobby, then select Unlisted instead. Debug Message: </source> - <translation type="unfinished"/> + <translation>Het is niet gelukt om de kamer aan te kondigen in de openbare lobby. Om een kamer openbaar te hosten, moet je een geldige yuzu-account geconfigureerd hebben in Emulatie -> Configuratie -> Web. Als je geen kamer wilt publiceren in de openbare lobby, selecteer dan in plaats daarvan Niet Vermeld. +Debug-bericht: </translation> </message> </context> <context> @@ -5947,7 +5970,7 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="73"/> <source>Audio Mute/Unmute</source> - <translation type="unfinished"/> + <translation>Audio Dempen/Dempen Opheffen</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="73"/> @@ -5973,52 +5996,52 @@ Debug Message: </source> <location filename="../../src/yuzu/configuration/config.cpp" line="93"/> <location filename="../../src/yuzu/configuration/config.cpp" line="94"/> <source>Main Window</source> - <translation type="unfinished"/> + <translation>Hoofdvenster</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="74"/> <source>Audio Volume Down</source> - <translation type="unfinished"/> + <translation>Audiovolume Omlaag</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="75"/> <source>Audio Volume Up</source> - <translation type="unfinished"/> + <translation>Audiovolume Omhoog</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="76"/> <source>Capture Screenshot</source> - <translation>Screenshot Vastleggen</translation> + <translation>Leg Schermafbeelding Vast</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="77"/> <source>Change Adapting Filter</source> - <translation type="unfinished"/> + <translation>Wijzig Aanpassingsfilter</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="78"/> <source>Change Docked Mode</source> - <translation type="unfinished"/> + <translation>Wijzig Docked-modus</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="79"/> <source>Change GPU Accuracy</source> - <translation type="unfinished"/> + <translation>Wijzig GPU-nauwkeurigheid</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="80"/> <source>Continue/Pause Emulation</source> - <translation type="unfinished"/> + <translation>Emulatie Doorgaan/Onderbreken</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="81"/> <source>Exit Fullscreen</source> - <translation type="unfinished"/> + <translation>Volledig Scherm Afsluiten</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="82"/> <source>Exit yuzu</source> - <translation type="unfinished"/> + <translation>yuzu afsluiten</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="83"/> @@ -6033,52 +6056,52 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="85"/> <source>Load/Remove Amiibo</source> - <translation type="unfinished"/> + <translation>Laad/Verwijder Amiibo</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="86"/> <source>Restart Emulation</source> - <translation type="unfinished"/> + <translation>Herstart Emulatie</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="87"/> <source>Stop Emulation</source> - <translation type="unfinished"/> + <translation>Stop Emulatie</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="88"/> <source>TAS Record</source> - <translation type="unfinished"/> + <translation>TAS Opname</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="89"/> <source>TAS Reset</source> - <translation type="unfinished"/> + <translation>TAS Reset</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="90"/> <source>TAS Start/Stop</source> - <translation type="unfinished"/> + <translation>TAS Start/Stop</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="91"/> <source>Toggle Filter Bar</source> - <translation type="unfinished"/> + <translation>Schakel Filterbalk</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="92"/> <source>Toggle Framerate Limit</source> - <translation type="unfinished"/> + <translation>Schakel Frameratelimiet</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="93"/> <source>Toggle Mouse Panning</source> - <translation type="unfinished"/> + <translation>Schakel Muispanning</translation> </message> <message> <location filename="../../src/yuzu/configuration/config.cpp" line="94"/> <source>Toggle Status Bar</source> - <translation type="unfinished"/> + <translation>Schakel Statusbalk</translation> </message> </context> <context> @@ -6086,17 +6109,17 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/install_dialog.cpp" line="29"/> <source>Please confirm these are the files you wish to install.</source> - <translation type="unfinished"/> + <translation>Bevestig dat dit de bestanden zijn die je wilt installeren.</translation> </message> <message> <location filename="../../src/yuzu/install_dialog.cpp" line="32"/> <source>Installing an Update or DLC will overwrite the previously installed one.</source> - <translation type="unfinished"/> + <translation>Het installeren van een Update of DLC overschrijft de eerder geïnstalleerde.</translation> </message> <message> <location filename="../../src/yuzu/install_dialog.cpp" line="36"/> <source>Install</source> - <translation>Installeren</translation> + <translation>Installeer</translation> </message> <message> <location filename="../../src/yuzu/install_dialog.cpp" line="49"/> @@ -6110,7 +6133,8 @@ Debug Message: </source> <location filename="../../src/yuzu/util/limitable_input_dialog.cpp" line="59"/> <source>The text can't contain any of the following characters: %1</source> - <translation type="unfinished"/> + <translation>De tekst kan geen van de volgende tekens bevatten: +%1</translation> </message> </context> <context> @@ -6118,12 +6142,12 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/loading_screen.ui" line="84"/> <source>Loading Shaders 387 / 1628</source> - <translation>Shaders Laden 387 / 1628</translation> + <translation>Shaders Laden 387 / 1628</translation> </message> <message> <location filename="../../src/yuzu/loading_screen.ui" line="121"/> <source>Loading Shaders %v out of %m</source> - <translation>%v Shaders Laden van de %m</translation> + <translation>Shaders Laden %v van %m</translation> </message> <message> <location filename="../../src/yuzu/loading_screen.ui" line="135"/> @@ -6138,7 +6162,7 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/loading_screen.cpp" line="84"/> <source>Loading Shaders %1 / %2</source> - <translation>Shaders Laden %1 / %2</translation> + <translation>Shaders Laden %1 / %2</translation> </message> <message> <location filename="../../src/yuzu/loading_screen.cpp" line="85"/> @@ -6156,53 +6180,53 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/multiplayer/lobby.ui" line="14"/> <source>Public Room Browser</source> - <translation type="unfinished"/> + <translation>Browser voor Openbare Ruimten</translation> </message> <message> <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>Gebruikersnaam</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby.ui" line="59"/> <source>Filters</source> - <translation type="unfinished"/> + <translation>Filters</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby.ui" line="66"/> <source>Search</source> - <translation type="unfinished"/> + <translation>Zoek</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby.ui" line="76"/> <source>Games I Own</source> - <translation type="unfinished"/> + <translation>Spellen die ik bezit</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby.ui" line="83"/> <source>Hide Empty Rooms</source> - <translation type="unfinished"/> + <translation>Verberg Lege Kamers</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby.ui" line="90"/> <source>Hide Full Rooms</source> - <translation type="unfinished"/> + <translation>Verberg Volle Kamers</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby.ui" line="110"/> <source>Refresh Lobby</source> - <translation type="unfinished"/> + <translation>Vernieuw Lobby</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="113"/> <source>Password Required to Join</source> - <translation type="unfinished"/> + <translation>Wachtwoord vereist om toegang te krijgen</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="113"/> <source>Password:</source> - <translation type="unfinished"/> + <translation>Wachtwoord:</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="216"/> @@ -6212,27 +6236,27 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="217"/> <source>Room Name</source> - <translation type="unfinished"/> + <translation>Kamernaam</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="218"/> <source>Preferred Game</source> - <translation type="unfinished"/> + <translation>Voorkeursspel</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="219"/> <source>Host</source> - <translation type="unfinished"/> + <translation>Host</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="226"/> <source>Refreshing</source> - <translation type="unfinished"/> + <translation>Vernieuwen</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="283"/> <source>Refresh List</source> - <translation type="unfinished"/> + <translation>Vernieuw Lijst</translation> </message> </context> <context> @@ -6250,7 +6274,7 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/main.ui" line="48"/> <source>&Recent Files</source> - <translation type="unfinished"/> + <translation>&Recente Bestanden</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="66"/> @@ -6265,57 +6289,57 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/main.ui" line="81"/> <source>&Reset Window Size</source> - <translation type="unfinished"/> + <translation>&Herstel Venstergrootte</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="86"/> <source>&Debugging</source> - <translation type="unfinished"/> + <translation>&Debuggen</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="91"/> <source>Reset Window Size to &720p</source> - <translation type="unfinished"/> + <translation>Herstel Venstergrootte naar &720p</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="94"/> <source>Reset Window Size to 720p</source> - <translation type="unfinished"/> + <translation>Herstel Venstergrootte naar 720p</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="99"/> <source>Reset Window Size to &900p</source> - <translation type="unfinished"/> + <translation>Herstel Venstergrootte naar &900p</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="102"/> <source>Reset Window Size to 900p</source> - <translation type="unfinished"/> + <translation>Herstel Venstergrootte naar 900p</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="107"/> <source>Reset Window Size to &1080p</source> - <translation type="unfinished"/> + <translation>Herstel Venstergrootte naar &1080p</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="110"/> <source>Reset Window Size to 1080p</source> - <translation type="unfinished"/> + <translation>Herstel Venstergrootte naar 1080p</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="127"/> <source>&Multiplayer</source> - <translation type="unfinished"/> + <translation>&Multiplayer</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="138"/> <source>&Tools</source> - <translation type="unfinished"/> + <translation>&Tools</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="142"/> <source>&TAS</source> - <translation type="unfinished"/> + <translation>&TAS</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="157"/> @@ -6325,17 +6349,17 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/main.ui" line="178"/> <source>&Install Files to NAND...</source> - <translation type="unfinished"/> + <translation>&Installeer Bestanden naar NAND...</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="183"/> <source>L&oad File...</source> - <translation type="unfinished"/> + <translation>L&aad Bestand...</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="188"/> <source>Load &Folder...</source> - <translation type="unfinished"/> + <translation>Laad &Map...</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="193"/> @@ -6345,7 +6369,7 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/main.ui" line="201"/> <source>&Pause</source> - <translation>&Pauzeren</translation> + <translation>&Onderbreken</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="209"/> @@ -6355,122 +6379,122 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/main.ui" line="214"/> <source>&Reinitialize keys...</source> - <translation type="unfinished"/> + <translation>&Herinitialiseer toetsen...</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="219"/> <source>&About yuzu</source> - <translation type="unfinished"/> + <translation>&Over yuzu</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="227"/> <source>Single &Window Mode</source> - <translation type="unfinished"/> + <translation>Modus Enkel Venster</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="232"/> <source>Con&figure...</source> - <translation type="unfinished"/> + <translation>Con&figureer...</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="243"/> <source>Display D&ock Widget Headers</source> - <translation type="unfinished"/> + <translation>Toon Dock Widget Kopteksten</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="251"/> <source>Show &Filter Bar</source> - <translation type="unfinished"/> + <translation>Toon &Filterbalk</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="259"/> <source>Show &Status Bar</source> - <translation type="unfinished"/> + <translation>Toon &Statusbalk</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="262"/> <source>Show Status Bar</source> - <translation>Laat status balk zien</translation> + <translation>Toon Statusbalk</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="270"/> <source>&Browse Public Game Lobby</source> - <translation type="unfinished"/> + <translation>&Bladeren door Openbare Spellobby</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="278"/> <source>&Create Room</source> - <translation type="unfinished"/> + <translation>&Maak Kamer</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="286"/> <source>&Leave Room</source> - <translation type="unfinished"/> + <translation>&Verlaat Kamer</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="291"/> <source>&Direct Connect to Room</source> - <translation type="unfinished"/> + <translation>&Directe Verbinding met Kamer</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="299"/> <source>&Show Current Room</source> - <translation type="unfinished"/> + <translation>&Toon Huidige Kamer</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="307"/> <source>F&ullscreen</source> - <translation type="unfinished"/> + <translation>Volledig Scherm</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="315"/> <source>&Restart</source> - <translation type="unfinished"/> + <translation>&Herstart</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="323"/> <source>Load/Remove &Amiibo...</source> - <translation type="unfinished"/> + <translation>Laad/Verwijder &Amiibo...</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="331"/> <source>&Report Compatibility</source> - <translation type="unfinished"/> + <translation>&Rapporteer Compatibiliteit</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="339"/> <source>Open &Mods Page</source> - <translation type="unfinished"/> + <translation>Open &Mod-pagina</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="344"/> <source>Open &Quickstart Guide</source> - <translation type="unfinished"/> + <translation>Open &Snelstartgids</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="349"/> <source>&FAQ</source> - <translation type="unfinished"/> + <translation>&FAQ</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="354"/> <source>Open &yuzu Folder</source> - <translation type="unfinished"/> + <translation>Open &yuzu-map</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="362"/> <source>&Capture Screenshot</source> - <translation type="unfinished"/> + <translation>&Leg Schermafbeelding Vast</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="367"/> <source>&Configure TAS...</source> - <translation type="unfinished"/> + <translation>&Configureer TAS...</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="378"/> <source>Configure C&urrent Game...</source> - <translation type="unfinished"/> + <translation>Configureer Huidig Spel...</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="389"/> @@ -6480,12 +6504,12 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/main.ui" line="397"/> <source>&Reset</source> - <translation type="unfinished"/> + <translation>&Herstel</translation> </message> <message> <location filename="../../src/yuzu/main.ui" line="405"/> <source>R&ecord</source> - <translation type="unfinished"/> + <translation>Opnemen</translation> </message> </context> <context> @@ -6493,7 +6517,7 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/debugger/profiler.cpp" line="50"/> <source>&MicroProfile</source> - <translation type="unfinished"/> + <translation>&MicroProfile</translation> </message> </context> <context> @@ -6501,48 +6525,48 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/multiplayer/moderation_dialog.ui" line="6"/> <source>Moderation</source> - <translation type="unfinished"/> + <translation>Moderatie</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/moderation_dialog.ui" line="20"/> <source>Ban List</source> - <translation type="unfinished"/> + <translation>Banlijst</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/moderation_dialog.ui" line="41"/> <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="73"/> <source>Refreshing</source> - <translation type="unfinished"/> + <translation>Vernieuwen</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/moderation_dialog.ui" line="51"/> <source>Unban</source> - <translation type="unfinished"/> + <translation>Ontban</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="40"/> <source>Subject</source> - <translation type="unfinished"/> + <translation>Onderwerp</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="41"/> <source>Type</source> - <translation type="unfinished"/> + <translation>Soort</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="83"/> <source>Forum Username</source> - <translation type="unfinished"/> + <translation>Forum Gebruikersnaam</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="88"/> <source>IP Address</source> - <translation type="unfinished"/> + <translation>IP-adres</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="95"/> <source>Refresh</source> - <translation type="unfinished"/> + <translation>Vernieuw</translation> </message> </context> <context> @@ -6550,17 +6574,17 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/multiplayer/state.cpp" line="90"/> <source>Current connection status</source> - <translation type="unfinished"/> + <translation>Huidige verbindingsstatus</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/state.cpp" line="117"/> <source>Not Connected. Click here to find a room!</source> - <translation type="unfinished"/> + <translation>Niet Verbonden. Klik hier om een kamer te vinden!</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/state.cpp" line="123"/> <source>Not Connected</source> - <translation type="unfinished"/> + <translation>Niet Verbonden</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/state.cpp" line="129"/> @@ -6570,18 +6594,19 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/multiplayer/state.cpp" line="136"/> <source>New Messages Received</source> - <translation type="unfinished"/> + <translation>Nieuwe Berichten Ontvangen</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/state.cpp" line="207"/> <source>Error</source> - <translation type="unfinished"/> + <translation>Fout</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/state.cpp" line="208"/> <source>Failed to update the room information. Please check your Internet connection and try hosting the room again. Debug Message: </source> - <translation type="unfinished"/> + <translation>Het is niet gelukt om de kamerinformatie bij te werken. Controleer je internetverbinding en probeer de kamer opnieuw te hosten. +Debug-bericht: </translation> </message> </context> <context> @@ -6589,135 +6614,138 @@ Debug Message: </source> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="11"/> <source>Username is not valid. Must be 4 to 20 alphanumeric characters.</source> - <translation type="unfinished"/> + <translation>Gebruikersnaam is niet geldig. Moet bestaan uit 4 tot 20 alfanumerieke tekens.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="13"/> <source>Room name is not valid. Must be 4 to 20 alphanumeric characters.</source> - <translation type="unfinished"/> + <translation>Kamernaam is niet geldig. Moet bestaan uit 4 tot 20 alfanumerieke tekens.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="15"/> <source>Username is already in use or not valid. Please choose another.</source> - <translation type="unfinished"/> + <translation>Gebruikersnaam is al in gebruik of niet geldig. Kies een andere.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="17"/> <source>IP is not a valid IPv4 address.</source> - <translation type="unfinished"/> + <translation>IP is geen geldig IPv4-adres.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="19"/> <source>Port must be a number between 0 to 65535.</source> - <translation type="unfinished"/> + <translation>De poort moet een getal zijn tussen 0 en 65535.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="20"/> <source>You must choose a Preferred Game to host a room. If you do not have any games in your game list yet, add a game folder by clicking on the plus icon in the game list.</source> - <translation type="unfinished"/> + <translation>Je moet een Voorkeursspel kiezen om een kamer te hosten. Als je nog geen spellen in je spellenlijst hebt, voeg dan een spellenmap toe door op het plus-icoon in de spellenlijst te klikken.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="24"/> <source>Unable to find an internet connection. Check your internet settings.</source> - <translation type="unfinished"/> + <translation>Kan geen internetverbinding vinden. Controleer je Internetinstellingen.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="26"/> <source>Unable to connect to the host. Verify that the connection settings are correct. If you still cannot connect, contact the room host and verify that the host is properly configured with the external port forwarded.</source> - <translation type="unfinished"/> + <translation>Kan geen verbinding maken met de host. Controleer of de verbindingsinstellingen correct zijn. Als je nog steeds geen verbinding kunt maken, neem dan contact op met de ruimtehost en controleer of de host correct is geconfigureerd met de externe poort doorgestuurd.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="30"/> <source>Unable to connect to the room because it is already full.</source> - <translation type="unfinished"/> + <translation>Kan geen verbinding maken met de kamer omdat deze al vol is.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="32"/> <source>Creating a room failed. Please retry. Restarting yuzu might be necessary.</source> - <translation type="unfinished"/> + <translation>Het aanmaken van een kamer is mislukt. Probeer het opnieuw. Het herstarten van yuzu kan nodig zijn.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="34"/> <source>The host of the room has banned you. Speak with the host to unban you or try a different room.</source> - <translation type="unfinished"/> + <translation>De host van de kamer heeft je verbannen. Praat met de host om je ban op te heffen of probeer een andere kamer.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="37"/> <source>Version mismatch! Please update to the latest version of yuzu. If the problem persists, contact the room host and ask them to update the server.</source> - <translation type="unfinished"/> + <translation>Versie komt niet overeen! Update naar de laatste versie van yuzu. Als het probleem aanhoudt, neem dan contact op met de room host en vraag hen om de server bij te werken.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="39"/> <source>Incorrect password.</source> - <translation type="unfinished"/> + <translation>Verkeerd wachtwoord.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="40"/> <source>An unknown error occurred. If this error continues to occur, please open an issue</source> - <translation type="unfinished"/> + <translation>Er is een onbekende fout opgetreden. Als deze fout zich blijft voordoen, open dan een ticket</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="43"/> <source>Connection to room lost. Try to reconnect.</source> - <translation type="unfinished"/> + <translation>Verbinding met kamer verloren. Probeer opnieuw verbinding te maken.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="45"/> <source>You have been kicked by the room host.</source> - <translation type="unfinished"/> + <translation>Je bent gekickt door de kamerhost.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="47"/> <source>IP address is already in use. Please choose another.</source> - <translation type="unfinished"/> + <translation>Het IP-adres is al in gebruik. Kies een ander.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="49"/> <source>You do not have enough permission to perform this action.</source> - <translation type="unfinished"/> + <translation>Je hebt niet genoeg rechten om deze actie uit te voeren.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="50"/> <source>The user you are trying to kick/ban could not be found. They may have left the room.</source> - <translation type="unfinished"/> + <translation>De gebruiker die je probeert te kicken/bannen kon niet gevonden worden. +Ze kunnen de ruimte hebben verlaten.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="52"/> <source>No valid network interface is selected. Please go to Configure -> System -> Network and make a selection.</source> - <translation type="unfinished"/> + <translation>Er is geen geldige netwerkinterface geselecteerd. +Ga naar Configuratie -> Systeem -> Netwerk en maak een selectie.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="68"/> <source>Game already running</source> - <translation type="unfinished"/> + <translation>Het spel draait al</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="69"/> <source>Joining a room when the game is already running is discouraged and can cause the room feature not to work correctly. Proceed anyway?</source> - <translation type="unfinished"/> + <translation>Het wordt afgeraden om aan een kamer deel te nemen als het spel al bezig is en kan ervoor zorgen dat de kamerfunctie niet correct werkt. +Toch doorgaan?</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="75"/> <source>Leave Room</source> - <translation type="unfinished"/> + <translation>Verlaat Kamer</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="76"/> <source>You are about to close the room. Any network connections will be closed.</source> - <translation type="unfinished"/> + <translation>Je staat op het punt de kamer te sluiten. Alle netwerkverbindingen worden afgesloten.</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="81"/> <source>Disconnect</source> - <translation type="unfinished"/> + <translation>Verbinding Verbreken</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="82"/> <source>You are about to leave the room. Any network connections will be closed.</source> - <translation type="unfinished"/> + <translation>Je staat op het punt de kamer te verlaten. Alle netwerkverbindingen worden afgesloten.</translation> </message> </context> <context> @@ -6725,7 +6753,7 @@ Proceed anyway?</source> <message> <location filename="../../src/yuzu/multiplayer/message.cpp" line="63"/> <source>Error</source> - <translation type="unfinished"/> + <translation>Fout</translation> </message> </context> <context> @@ -6754,7 +6782,11 @@ Proceed anyway?</source> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:18pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></source> - <translation type="unfinished"/> + <translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:18pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></translation> </message> </context> <context> @@ -6762,7 +6794,7 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/configuration/configure_input_player_widget.cpp" line="1579"/> <source>START/PAUSE</source> - <translation type="unfinished"/> + <translation>START/ONDERBREKEN</translation> </message> </context> <context> @@ -6770,42 +6802,42 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/multiplayer/lobby_p.h" line="236"/> <source>%1 is not playing a game</source> - <translation type="unfinished"/> + <translation>%1 speelt geen spel</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/lobby_p.h" line="238"/> <source>%1 is playing %2</source> - <translation type="unfinished"/> + <translation>%1 speelt %2</translation> </message> <message> <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="142"/> <source>Not playing a game</source> - <translation type="unfinished"/> + <translation>Geen spel aan het spelen</translation> </message> <message> <location filename="../../src/yuzu/game_list_p.h" line="244"/> <source>Installed SD Titles</source> - <translation>Geïnstalleerde SD Titels</translation> + <translation>Geïnstalleerde SD-titels</translation> </message> <message> <location filename="../../src/yuzu/game_list_p.h" line="252"/> <source>Installed NAND Titles</source> - <translation>Geïnstalleerde NAND Titels</translation> + <translation>Geïnstalleerde NAND-titels</translation> </message> <message> <location filename="../../src/yuzu/game_list_p.h" line="260"/> <source>System Titles</source> - <translation>Systeem Titels</translation> + <translation>Systeemtitels</translation> </message> <message> <location filename="../../src/yuzu/game_list_p.h" line="303"/> <source>Add New Game Directory</source> - <translation>Voeg Nieuwe Game Map Toe</translation> + <translation>Voeg Nieuwe Spelmap Toe</translation> </message> <message> <location filename="../../src/yuzu/game_list_p.h" line="326"/> <source>Favorites</source> - <translation type="unfinished"/> + <translation>Favorieten</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="21"/> @@ -6876,28 +6908,28 @@ p, li { white-space: pre-wrap; } <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="56"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="144"/> <source>Left</source> - <translation>Links:</translation> + <translation>Links</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="49"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="58"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/> <source>Right</source> - <translation>Rechts:</translation> + <translation>Rechts</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="51"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="60"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="153"/> <source>Down</source> - <translation>Beneden:</translation> + <translation>Omlaag</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="53"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="62"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="150"/> <source>Up</source> - <translation>Boven:</translation> + <translation>Omhoog</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="55"/> @@ -6909,7 +6941,7 @@ p, li { white-space: pre-wrap; } <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="57"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="66"/> <source>R</source> - <translation>R:</translation> + <translation>R</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="59"/> @@ -6951,79 +6983,79 @@ p, li { white-space: pre-wrap; } <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="71"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/> <source>L1</source> - <translation type="unfinished"/> + <translation>L1</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="73"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/> <source>L2</source> - <translation type="unfinished"/> + <translation>L2</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="75"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/> <source>L3</source> - <translation type="unfinished"/> + <translation>L3</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="77"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="106"/> <source>R1</source> - <translation type="unfinished"/> + <translation>R1</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="79"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="108"/> <source>R2</source> - <translation type="unfinished"/> + <translation>R2</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="81"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="110"/> <source>R3</source> - <translation type="unfinished"/> + <translation>R3</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="83"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="112"/> <source>Circle</source> - <translation type="unfinished"/> + <translation>Cirkel</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="85"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="114"/> <source>Cross</source> - <translation type="unfinished"/> + <translation>Kruis</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="87"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="116"/> <source>Square</source> - <translation type="unfinished"/> + <translation>Vierkant</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="89"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="118"/> <source>Triangle</source> - <translation type="unfinished"/> + <translation>Driehoek</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="91"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="120"/> <source>Share</source> - <translation type="unfinished"/> + <translation>Deel</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="93"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="122"/> <source>Options</source> - <translation type="unfinished"/> + <translation>Opties</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="95"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="138"/> <source>[undefined]</source> - <translation type="unfinished"/> + <translation>[ongedefinieerd]</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="399"/> @@ -7034,13 +7066,13 @@ p, li { white-space: pre-wrap; } <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="403"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="195"/> <source>[invalid]</source> - <translation type="unfinished"/> + <translation>[ongeldig]</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="413"/> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="437"/> <source>%1%2Hat %3</source> - <translation type="unfinished"/> + <translation>%1%2Hat %3</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="417"/> @@ -7050,25 +7082,25 @@ p, li { white-space: pre-wrap; } <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="232"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="235"/> <source>%1%2Axis %3</source> - <translation type="unfinished"/> + <translation>%1%2As %3</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="423"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="215"/> <source>%1%2Axis %3,%4,%5</source> - <translation type="unfinished"/> + <translation>%1%2As %3,%4,%5</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="427"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="219"/> <source>%1%2Motion %3</source> - <translation type="unfinished"/> + <translation>%1%2Beweging %3</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="431"/> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="446"/> <source>%1%2Button %3</source> - <translation type="unfinished"/> + <translation>%1%2Knop %3</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="473"/> @@ -7099,17 +7131,17 @@ 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>Stick L</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="80"/> <source>Stick R</source> - <translation type="unfinished"/> + <translation>Stick R</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/> <source>Plus</source> - <translation>Plus:</translation> + <translation>Plus</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/> @@ -7120,7 +7152,7 @@ p, li { white-space: pre-wrap; } <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="124"/> <source>Home</source> - <translation>Home:</translation> + <translation>Home</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/> @@ -7136,44 +7168,44 @@ p, li { white-space: pre-wrap; } <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="128"/> <source>Wheel</source> <comment>Indicates the mouse wheel</comment> - <translation type="unfinished"/> + <translation>Wiel</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="130"/> <source>Backward</source> - <translation type="unfinished"/> + <translation>Achteruit</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="132"/> <source>Forward</source> - <translation type="unfinished"/> + <translation>Vooruit</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="134"/> <source>Task</source> - <translation type="unfinished"/> + <translation>Taak</translation> </message> <message> <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="136"/> <source>Extra</source> - <translation type="unfinished"/> + <translation>Extra</translation> </message> <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%3Hat %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%3Knop %4</translation> </message> </context> <context> @@ -7181,22 +7213,22 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="14"/> <source>Amiibo Settings</source> - <translation type="unfinished"/> + <translation>Amiibo-instellingen</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="169"/> <source>Amiibo Info</source> - <translation type="unfinished"/> + <translation>Amiibo-info</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="177"/> <source>Series</source> - <translation type="unfinished"/> + <translation>Serie</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="197"/> <source>Type</source> - <translation type="unfinished"/> + <translation>Soort</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="217"/> @@ -7206,52 +7238,52 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="242"/> <source>Amiibo Data</source> - <translation type="unfinished"/> + <translation>Amiibo-gegevens</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="250"/> <source>Custom Name</source> - <translation type="unfinished"/> + <translation>Aangepaste Naam</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="270"/> <source>Owner</source> - <translation type="unfinished"/> + <translation>Eigenaar</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="290"/> <source>Creation Date</source> - <translation type="unfinished"/> + <translation>Aanmaakdatum</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="307"/> <source>dd/MM/yyyy</source> - <translation type="unfinished"/> + <translation>dd/MM/yyyy</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="314"/> <source>Modification Date</source> - <translation type="unfinished"/> + <translation>Modificatiedatum</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="331"/> <source>dd/MM/yyyy </source> - <translation type="unfinished"/> + <translation>dd/MM/yyyy </translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="349"/> <source>Game Data</source> - <translation type="unfinished"/> + <translation>Spelgegevens</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="355"/> <source>Game Id</source> - <translation type="unfinished"/> + <translation>Spel-ID</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="384"/> <source>Mount Amiibo</source> - <translation type="unfinished"/> + <translation>Gebruik Amiibo</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="390"/> @@ -7261,32 +7293,32 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.ui" line="413"/> <source>File Path</source> - <translation type="unfinished"/> + <translation>Bestandspad</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.cpp" line="191"/> <source>No game data present</source> - <translation type="unfinished"/> + <translation>Geen spelgegevens aanwezig</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.cpp" line="231"/> <source>The following amiibo data will be formatted:</source> - <translation type="unfinished"/> + <translation>De volgende amiibo-gegevens worden zo geformatteerd:</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.cpp" line="234"/> <source>The following game data will removed:</source> - <translation type="unfinished"/> + <translation>De volgende spelgegevens worden verwijderd:</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.cpp" line="237"/> <source>Set nickname and owner:</source> - <translation type="unfinished"/> + <translation>Stel gebruikersnaam en eigenaar in:</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_amiibo_settings.cpp" line="240"/> <source>Do you wish to restore this amiibo?</source> - <translation type="unfinished"/> + <translation>Wil je deze amiibo herstellen?</translation> </message> </context> <context> @@ -7294,27 +7326,27 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="14"/> <source>Controller Applet</source> - <translation type="unfinished"/> + <translation>Controller Applet</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="129"/> <source>Supported Controller Types:</source> - <translation type="unfinished"/> + <translation>Ondersteunde Controller-typen:</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="282"/> <source>Players:</source> - <translation type="unfinished"/> + <translation>Spelers:</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="300"/> <source>1 - 8</source> - <translation type="unfinished"/> + <translation>1 - 8</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="418"/> <source>P4</source> - <translation type="unfinished"/> + <translation>P4</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="514"/> @@ -7378,59 +7410,59 @@ p, li { white-space: pre-wrap; } <location filename="../../src/yuzu/applets/qt_controller.ui" line="1881"/> <location filename="../../src/yuzu/applets/qt_controller.ui" line="2078"/> <source>Use Current Config</source> - <translation type="unfinished"/> + <translation>Gebruik Huidige Configuratie</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="615"/> <source>P2</source> - <translation type="unfinished"/> + <translation>P2</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="812"/> <source>P1</source> - <translation type="unfinished"/> + <translation>P1</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="932"/> <location filename="../../src/yuzu/applets/qt_controller.ui" line="2303"/> <location filename="../../src/yuzu/applets/qt_controller.cpp" line="430"/> <source>Handheld</source> - <translation>Mobiel</translation> + <translation>Handheld</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="1126"/> <source>P3</source> - <translation type="unfinished"/> + <translation>P3</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="1363"/> <source>P7</source> - <translation type="unfinished"/> + <translation>P7</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="1560"/> <source>P8</source> - <translation type="unfinished"/> + <translation>P8</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="1757"/> <source>P5</source> - <translation type="unfinished"/> + <translation>P5</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="1958"/> <source>P6</source> - <translation type="unfinished"/> + <translation>P6</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="2272"/> <source>Console Mode</source> - <translation>Console ID:</translation> + <translation>Console-ID:</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="2293"/> <source>Docked</source> - <translation>GeDocked</translation> + <translation>Docked</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="2313"/> @@ -7456,7 +7488,7 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="2432"/> <source>Create</source> - <translation type="unfinished"/> + <translation>Maak</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.ui" line="2467"/> @@ -7511,32 +7543,32 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/applets/qt_controller.cpp" line="434"/> <source>GameCube Controller</source> - <translation>GameCube Controller</translation> + <translation>GameCube-controller</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.cpp" line="443"/> <source>Poke Ball Plus</source> - <translation type="unfinished"/> + <translation>Poke Ball Plus</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.cpp" line="447"/> <source>NES Controller</source> - <translation type="unfinished"/> + <translation>NES-controller</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.cpp" line="451"/> <source>SNES Controller</source> - <translation type="unfinished"/> + <translation>SNES-controller</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.cpp" line="455"/> <source>N64 Controller</source> - <translation type="unfinished"/> + <translation>N64-controller</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_controller.cpp" line="459"/> <source>Sega Genesis</source> - <translation type="unfinished"/> + <translation>Sega Genesis</translation> </message> </context> <context> @@ -7546,19 +7578,21 @@ p, li { white-space: pre-wrap; } <location filename="../../src/yuzu/applets/qt_error.cpp" line="40"/> <location filename="../../src/yuzu/applets/qt_error.cpp" line="55"/> <source>Error Code: %1-%2 (0x%3)</source> - <translation type="unfinished"/> + <translation>Foutcode: %1-%2 (0x%3)</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_error.cpp" line="31"/> <source>An error has occurred. Please try again or contact the developer of the software.</source> - <translation type="unfinished"/> + <translation>Er is een fout opgetreden. +Probeer het opnieuw of neem contact op met de software-ontwikkelaar.</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_error.cpp" line="44"/> <source>An error occurred on %1 at %2. Please try again or contact the developer of the software.</source> - <translation type="unfinished"/> + <translation>Er is een fout opgetreden op %1 bij %2. +Probeer het opnieuw of neem contact op met de software-ontwikkelaar.</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_error.cpp" line="59"/> @@ -7567,7 +7601,11 @@ Please try again or contact the developer of the software.</source> %1 %2</source> - <translation type="unfinished"/> + <translation>Er is een fout opgetreden. + +%1 + +%2</translation> </message> </context> <context> @@ -7588,68 +7626,68 @@ 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>Profielmaker</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="169"/> <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="187"/> <source>Profile Selector</source> - <translation>Profiel keuzeschakelaar</translation> + <translation>Profielkiezer</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="172"/> <source>Profile Icon Editor</source> - <translation type="unfinished"/> + <translation>Profielicoon-editor</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="175"/> <source>Profile Nickname Editor</source> - <translation type="unfinished"/> + <translation>Profielnaam-editor</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>Wie krijgt de punten?</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>Wie gebruikt de 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>Wie doet deze aankoop?</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="206"/> <source>Who is posting?</source> - <translation type="unfinished"/> + <translation>Wie post er?</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>Selecteer een gebruiker om te koppelen aan een Nintendo-account.</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>Instellingen wijzigen voor welke gebruiker?</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>Formatteer gegevens voor welke gebruiker?</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>Welke gebruiker wordt overgezet naar een andere 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>Gegevens verzenden voor welke gebruiker?</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="225"/> @@ -7662,12 +7700,12 @@ Please try again or contact the developer of the software.</source> <message> <location filename="../../src/yuzu/applets/qt_software_keyboard.ui" line="14"/> <source>Software Keyboard</source> - <translation>Software Toetsenbord</translation> + <translation>Softwaretoetsenbord</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_software_keyboard.ui" line="199"/> <source>Enter Text</source> - <translation type="unfinished"/> + <translation>Voer Tekst In</translation> </message> <message> <location filename="../../src/yuzu/applets/qt_software_keyboard.ui" line="479"/> @@ -7676,7 +7714,11 @@ Please try again or contact the developer of the software.</source> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:26pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></source> - <translation type="unfinished"/> + <translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:26pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></translation> </message> <message> <location filename="../../src/yuzu/applets/qt_software_keyboard.cpp" line="403"/> @@ -7695,7 +7737,7 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="10"/> <source>Enter a hotkey</source> - <translation>Voer een hotkey in</translation> + <translation>Voer een sneltoets in</translation> </message> </context> <context> @@ -7711,12 +7753,12 @@ 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"/> <source>waited by no thread</source> - <translation>wachtend door geen draad</translation> + <translation>wachtend door geen thread</translation> </message> </context> <context> @@ -7729,7 +7771,7 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="204"/> <source>paused</source> - <translation>gepauzeerd</translation> + <translation>onderbroken</translation> </message> <message> <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="210"/> @@ -7739,7 +7781,7 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="213"/> <source>waiting for IPC reply</source> - <translation>wachten op IPC antwoord</translation> + <translation>wachten op IPC-antwoord</translation> </message> <message> <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="216"/> @@ -7759,7 +7801,7 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="225"/> <source>waiting for suspend resume</source> - <translation type="unfinished"/> + <translation>wachtend op hervatten onderbreking</translation> </message> <message> <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="228"/> @@ -7769,7 +7811,7 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="233"/> <source>initialized</source> - <translation>geinitialiseerd</translation> + <translation>geïnitialiseerd</translation> </message> <message> <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="236"/> @@ -7809,7 +7851,7 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="304"/> <source>thread id = %1</source> - <translation>draad id = %1</translation> + <translation>thread-id = %1</translation> </message> <message> <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="305"/> @@ -7827,7 +7869,7 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="325"/> <source>waited by thread</source> - <translation>Wachtend door draad</translation> + <translation>wachtend door thread</translation> </message> </context> <context> @@ -7835,7 +7877,7 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="399"/> <source>&Wait Tree</source> - <translation type="unfinished"/> + <translation>&Wait Tree</translation> </message> </context> </TS>
\ No newline at end of file diff --git a/dist/languages/pl.ts b/dist/languages/pl.ts index 36daf71d2..e822fcac8 100644 --- a/dist/languages/pl.ts +++ b/dist/languages/pl.ts @@ -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"/> diff --git a/dist/languages/pt_BR.ts b/dist/languages/pt_BR.ts index 98f1345a2..50213db36 100644 --- a/dist/languages/pt_BR.ts +++ b/dist/languages/pt_BR.ts @@ -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"/> diff --git a/dist/languages/pt_PT.ts b/dist/languages/pt_PT.ts index 293283ac9..da8aa6d13 100644 --- a/dist/languages/pt_PT.ts +++ b/dist/languages/pt_PT.ts @@ -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"/> diff --git a/dist/languages/ru_RU.ts b/dist/languages/ru_RU.ts index eee646732..898c1800c 100644 --- a/dist/languages/ru_RU.ts +++ b/dist/languages/ru_RU.ts @@ -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> diff --git a/dist/languages/sv.ts b/dist/languages/sv.ts index b81ed7fd6..8c60a4942 100644 --- a/dist/languages/sv.ts +++ b/dist/languages/sv.ts @@ -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"/> diff --git a/dist/languages/tr_TR.ts b/dist/languages/tr_TR.ts index 98a7f479f..47a0ca9c8 100644 --- a/dist/languages/tr_TR.ts +++ b/dist/languages/tr_TR.ts @@ -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"/> diff --git a/dist/languages/uk.ts b/dist/languages/uk.ts index 02bdf2233..3c4e788f6 100644 --- a/dist/languages/uk.ts +++ b/dist/languages/uk.ts @@ -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"/> diff --git a/dist/languages/zh_CN.ts b/dist/languages/zh_CN.ts index 9da8b0b32..2de1e921e 100644 --- a/dist/languages/zh_CN.ts +++ b/dist/languages/zh_CN.ts @@ -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"/> diff --git a/dist/languages/zh_TW.ts b/dist/languages/zh_TW.ts index 472b00114..f6cd1934d 100644 --- a/dist/languages/zh_TW.ts +++ b/dist/languages/zh_TW.ts @@ -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"/> diff --git a/externals/dynarmic b/externals/dynarmic -Subproject c08c5a9362bb224dc343c2f616c24df027dfdf1 +Subproject 7da378033a7764f955516f75194856d87bbcd7a diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 312a49f42..5e3a74c0f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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> ) diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp index ad869facb..53b258c4f 100644 --- a/src/audio_core/renderer/system.cpp +++ b/src/audio_core/renderer/system.cpp @@ -436,10 +436,7 @@ void System::Stop() { } if (execution_mode == ExecutionMode::Auto) { - // Should wait for the system to terminate here, but core timing (should have) already - // stopped, so this isn't needed. Find a way to make this definite. - - // terminate_event.Wait(); + terminate_event.Wait(); } } diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp index ee1a0652f..c1529d1f9 100644 --- a/src/audio_core/sink/sdl2_sink.cpp +++ b/src/audio_core/sink/sdl2_sink.cpp @@ -3,6 +3,7 @@ #include <span> #include <vector> +#include <SDL.h> #include "audio_core/common/common.h" #include "audio_core/sink/sdl2_sink.h" @@ -10,16 +11,6 @@ #include "common/logging/log.h" #include "core/core.h" -// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wimplicit-fallthrough" -#endif -#include <SDL.h> -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - namespace AudioCore::Sink { /** * SDL sink stream, responsible for sinking samples to hardware. diff --git a/src/common/address_space.inc b/src/common/address_space.inc index 2195dabd5..1ee82df53 100644 --- a/src/common/address_space.inc +++ b/src/common/address_space.inc @@ -72,7 +72,7 @@ MAP_MEMBER(void)::MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInf } }()}; - if (block_end_predecessor->virt >= virt) { + if (block_end_predecessor != blocks.begin() && block_end_predecessor->virt >= virt) { // If this block's start would be overlapped by the map then reuse it as a tail // block block_end_predecessor->virt = virt_end; @@ -336,7 +336,7 @@ ALLOC_MEMBER(VaType)::Allocate(VaType size) { ASSERT_MSG(false, "Unexpected allocator state!"); } - auto search_predecessor{this->blocks.begin()}; + auto search_predecessor{std::next(this->blocks.begin())}; auto search_successor{std::next(search_predecessor)}; while (search_successor != this->blocks.end() && diff --git a/src/common/input.h b/src/common/input.h index 51b277c1f..66fb15f0a 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -111,6 +111,8 @@ struct AnalogProperties { float offset{}; // Invert direction of the sensor data bool inverted{}; + // Invert the state if it's converted to a button + bool inverted_button{}; // Press once to activate, press again to release bool toggle{}; }; diff --git a/src/common/intrusive_list.h b/src/common/intrusive_list.h new file mode 100644 index 000000000..d330dc1c2 --- /dev/null +++ b/src/common/intrusive_list.h @@ -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 diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 84955030b..db1774c71 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -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()); @@ -60,7 +61,8 @@ void LogSettings() { log_setting("Renderer_NvdecEmulation", values.nvdec_emulation.GetValue()); log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue()); log_setting("Renderer_AsyncASTC", values.async_astc.GetValue()); - log_setting("Renderer_UseVsync", values.use_vsync.GetValue()); + log_setting("Renderer_UseVsync", values.vsync_mode.GetValue()); + log_setting("Renderer_UseReactiveFlushing", values.use_reactive_flushing.GetValue()); log_setting("Renderer_ShaderBackend", values.shader_backend.GetValue()); log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue()); @@ -191,7 +193,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 +207,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); @@ -221,11 +224,10 @@ void RestoreGlobalState(bool is_powered_on) { values.nvdec_emulation.SetGlobal(true); values.accelerate_astc.SetGlobal(true); values.async_astc.SetGlobal(true); - values.use_vsync.SetGlobal(true); + values.use_reactive_flushing.SetGlobal(true); 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); diff --git a/src/common/settings.h b/src/common/settings.h index b77a1580a..f4eb4e3cd 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -16,6 +16,13 @@ namespace Settings { +enum class VSyncMode : u32 { + Immediate = 0, + Mailbox = 1, + FIFO = 2, + FIFORelaxed = 3, +}; + enum class RendererBackend : u32 { OpenGL = 0, Vulkan = 1, @@ -388,7 +395,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 +430,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"}; @@ -454,12 +463,13 @@ struct Values { SwitchableSetting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"}; SwitchableSetting<bool> accelerate_astc{true, "accelerate_astc"}; SwitchableSetting<bool> async_astc{false, "async_astc"}; - SwitchableSetting<bool> use_vsync{true, "use_vsync"}; + Setting<VSyncMode, true> vsync_mode{VSyncMode::FIFO, VSyncMode::Immediate, + VSyncMode::FIFORelaxed, "use_vsync"}; + SwitchableSetting<bool> use_reactive_flushing{true, "use_reactive_flushing"}; SwitchableSetting<ShaderBackend, true> shader_backend{ShaderBackend::GLSL, ShaderBackend::GLSL, 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"}; diff --git a/src/common/vector_math.h b/src/common/vector_math.h index 0e2095c45..b4885835d 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -259,6 +259,20 @@ public: return *this; } + void RotateFromOrigin(float roll, float pitch, float yaw) { + float temp = y; + y = std::cos(roll) * y - std::sin(roll) * z; + z = std::sin(roll) * temp + std::cos(roll) * z; + + temp = x; + x = std::cos(pitch) * x + std::sin(pitch) * z; + z = -std::sin(pitch) * temp + std::cos(pitch) * z; + + temp = x; + x = std::cos(yaw) * x - std::sin(yaw) * y; + y = std::sin(yaw) * temp + std::cos(yaw) * y; + } + [[nodiscard]] constexpr T Length2() const { return x * x + y * y + z * z; } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 8817a99c9..45328158f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -555,21 +555,22 @@ add_library(core STATIC hle/service/mnpp/mnpp_app.h hle/service/ncm/ncm.cpp hle/service/ncm/ncm.h - hle/service/nfc/mifare_user.cpp - hle/service/nfc/mifare_user.h + hle/service/nfc/common/amiibo_crypto.cpp + hle/service/nfc/common/amiibo_crypto.h + hle/service/nfc/common/device.cpp + hle/service/nfc/common/device.h + hle/service/nfc/common/device_manager.cpp + hle/service/nfc/common/device_manager.h + hle/service/nfc/mifare_result.h + hle/service/nfc/mifare_types.h hle/service/nfc/nfc.cpp hle/service/nfc/nfc.h - hle/service/nfc/nfc_device.cpp - hle/service/nfc/nfc_device.h + hle/service/nfc/nfc_interface.cpp + hle/service/nfc/nfc_interface.h hle/service/nfc/nfc_result.h - hle/service/nfc/nfc_user.cpp - hle/service/nfc/nfc_user.h - hle/service/nfp/amiibo_crypto.cpp - hle/service/nfp/amiibo_crypto.h + hle/service/nfc/nfc_types.h hle/service/nfp/nfp.cpp 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 diff --git a/src/core/core.cpp b/src/core/core.cpp index caa6a77be..b5f62690e 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -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) { @@ -611,6 +612,10 @@ void System::PrepareReschedule(const u32 core_index) { impl->kernel.PrepareReschedule(core_index); } +size_t System::GetCurrentHostThreadID() const { + return impl->kernel.GetCurrentHostThreadID(); +} + PerfStatsResults System::GetAndResetPerfStats() { return impl->GetAndResetPerfStats(); } diff --git a/src/core/core.h b/src/core/core.h index 4a5aba032..4f153154f 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -222,6 +222,8 @@ public: /// Prepare the core emulation for a reschedule void PrepareReschedule(u32 core_index); + [[nodiscard]] size_t GetCurrentHostThreadID() const; + /// Gets and resets core performance statistics [[nodiscard]] PerfStatsResults GetAndResetPerfStats(); diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp index da05dd395..3e6426afc 100644 --- a/src/core/file_sys/vfs_layered.cpp +++ b/src/core/file_sys/vfs_layered.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <algorithm> +#include <set> #include <utility> #include "core/file_sys/vfs_layered.h" @@ -58,11 +59,13 @@ std::string LayeredVfsDirectory::GetFullPath() const { std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const { std::vector<VirtualFile> out; + std::set<std::string, std::less<>> out_names; + for (const auto& layer : dirs) { for (const auto& file : layer->GetFiles()) { - if (std::find_if(out.begin(), out.end(), [&file](const VirtualFile& comp) { - return comp->GetName() == file->GetName(); - }) == out.end()) { + auto file_name = file->GetName(); + if (!out_names.contains(file_name)) { + out_names.emplace(std::move(file_name)); out.push_back(file); } } diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp index 251d9d7c9..af1df4c51 100644 --- a/src/core/file_sys/vfs_vector.cpp +++ b/src/core/file_sys/vfs_vector.cpp @@ -67,6 +67,23 @@ VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_, VectorVfsDirectory::~VectorVfsDirectory() = default; +VirtualFile VectorVfsDirectory::GetFile(std::string_view file_name) const { + if (!optimized_file_index_built) { + optimized_file_index.clear(); + for (size_t i = 0; i < files.size(); i++) { + optimized_file_index.emplace(files[i]->GetName(), i); + } + optimized_file_index_built = true; + } + + const auto it = optimized_file_index.find(file_name); + if (it != optimized_file_index.end()) { + return files[it->second]; + } + + return nullptr; +} + std::vector<VirtualFile> VectorVfsDirectory::GetFiles() const { return files; } @@ -107,6 +124,7 @@ bool VectorVfsDirectory::DeleteSubdirectory(std::string_view subdir_name) { } bool VectorVfsDirectory::DeleteFile(std::string_view file_name) { + optimized_file_index_built = false; return FindAndRemoveVectorElement(files, file_name); } @@ -124,6 +142,7 @@ VirtualFile VectorVfsDirectory::CreateFile(std::string_view file_name) { } void VectorVfsDirectory::AddFile(VirtualFile file) { + optimized_file_index_built = false; files.push_back(std::move(file)); } diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h index bfedb6e42..c9955755b 100644 --- a/src/core/file_sys/vfs_vector.h +++ b/src/core/file_sys/vfs_vector.h @@ -105,6 +105,7 @@ public: VirtualDir parent = nullptr); ~VectorVfsDirectory() override; + VirtualFile GetFile(std::string_view file_name) const override; std::vector<VirtualFile> GetFiles() const override; std::vector<VirtualDir> GetSubdirectories() const override; bool IsWritable() const override; @@ -126,6 +127,9 @@ private: VirtualDir parent; std::string name; + + mutable std::map<std::string, size_t, std::less<>> optimized_file_index; + mutable bool optimized_file_index_built{}; }; } // namespace FileSys diff --git a/src/core/frontend/applets/cabinet.cpp b/src/core/frontend/applets/cabinet.cpp index 2d501eeae..c33ce248b 100644 --- a/src/core/frontend/applets/cabinet.cpp +++ b/src/core/frontend/applets/cabinet.cpp @@ -14,7 +14,7 @@ void DefaultCabinetApplet::Close() const {} void DefaultCabinetApplet::ShowCabinetApplet( const CabinetCallback& callback, const CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const { + std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const { LOG_WARNING(Service_AM, "(STUBBED) called"); callback(false, {}); } diff --git a/src/core/frontend/applets/cabinet.h b/src/core/frontend/applets/cabinet.h index 74dc5a4f6..af3fc6c3d 100644 --- a/src/core/frontend/applets/cabinet.h +++ b/src/core/frontend/applets/cabinet.h @@ -7,9 +7,9 @@ #include "core/frontend/applets/applet.h" #include "core/hle/service/nfp/nfp_types.h" -namespace Service::NFP { -class NfpDevice; -} // namespace Service::NFP +namespace Service::NFC { +class NfcDevice; +} // namespace Service::NFC namespace Core::Frontend { @@ -26,14 +26,14 @@ public: virtual ~CabinetApplet(); virtual void ShowCabinetApplet(const CabinetCallback& callback, const CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const = 0; + std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const = 0; }; class DefaultCabinetApplet final : public CabinetApplet { public: void Close() const override; void ShowCabinetApplet(const CabinetCallback& callback, const CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const override; + std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const override; }; } // namespace Core::Frontend diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index a70f8807c..366880711 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -376,6 +376,7 @@ void EmulatedController::ReloadInput() { motion.accel = emulated_motion.GetAcceleration(); motion.gyro = emulated_motion.GetGyroscope(); motion.rotation = emulated_motion.GetRotations(); + motion.euler = emulated_motion.GetEulerAngles(); motion.orientation = emulated_motion.GetOrientation(); motion.is_at_rest = !emulated_motion.IsMoving(motion_sensitivity); } @@ -551,6 +552,8 @@ void EmulatedController::EnableSystemButtons() { void EmulatedController::DisableSystemButtons() { std::scoped_lock lock{mutex}; system_buttons_enabled = false; + controller.home_button_state.raw = 0; + controller.capture_button_state.raw = 0; } void EmulatedController::ResetSystemButtons() { @@ -685,6 +688,12 @@ void EmulatedController::SetMotionParam(std::size_t index, Common::ParamPackage ReloadInput(); } +void EmulatedController::StartMotionCalibration() { + for (ControllerMotionInfo& motion : controller.motion_values) { + motion.emulated.Calibrate(); + } +} + void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index, Common::UUID uuid) { if (index >= controller.button_values.size()) { @@ -734,6 +743,8 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback if (is_configuring) { controller.npad_button_state.raw = NpadButton::None; controller.debug_pad_button_state.raw = 0; + controller.home_button_state.raw = 0; + controller.capture_button_state.raw = 0; lock.unlock(); TriggerOnChange(ControllerTriggerType::Button, false); return; @@ -974,16 +985,12 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback emulated.SetUserGyroThreshold(raw_status.gyro.x.properties.threshold); emulated.UpdateRotation(raw_status.delta_timestamp); emulated.UpdateOrientation(raw_status.delta_timestamp); - force_update_motion = raw_status.force_update; - - if (is_configuring) { - return; - } auto& motion = controller.motion_state[index]; motion.accel = emulated.GetAcceleration(); motion.gyro = emulated.GetGyroscope(); motion.rotation = emulated.GetRotations(); + motion.euler = emulated.GetEulerAngles(); motion.orientation = emulated.GetOrientation(); motion.is_at_rest = !emulated.IsMoving(motion_sensitivity); } @@ -1616,19 +1623,6 @@ NpadGcTriggerState EmulatedController::GetTriggers() const { MotionState EmulatedController::GetMotions() const { std::unique_lock lock{mutex}; - - // Some drivers like mouse motion need constant refreshing - if (force_update_motion) { - for (auto& device : motion_devices) { - if (!device) { - continue; - } - lock.unlock(); - device->ForceUpdate(); - lock.lock(); - } - } - return controller.motion_state; } @@ -1694,8 +1688,21 @@ void EmulatedController::DeleteCallback(int key) { callback_list.erase(iterator); } -void EmulatedController::TurboButtonUpdate() { +void EmulatedController::StatusUpdate() { turbo_button_state = (turbo_button_state + 1) % (TURBO_BUTTON_DELAY * 2); + + // Some drivers like key motion need constant refreshing + for (std::size_t index = 0; index < motion_devices.size(); ++index) { + const auto& raw_status = controller.motion_values[index].raw_status; + auto& device = motion_devices[index]; + if (!raw_status.force_update) { + continue; + } + if (!device) { + continue; + } + device->ForceUpdate(); + } } NpadButton EmulatedController::GetTurboButtonMask() const { diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index 429655355..88fad2f56 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h @@ -106,6 +106,7 @@ struct ControllerMotion { Common::Vec3f accel{}; Common::Vec3f gyro{}; Common::Vec3f rotation{}; + Common::Vec3f euler{}; std::array<Common::Vec3f, 3> orientation{}; bool is_at_rest{}; }; @@ -289,6 +290,9 @@ public: */ void SetMotionParam(std::size_t index, Common::ParamPackage param); + /// Auto calibrates the current motion devices + void StartMotionCalibration(); + /// Returns the latest button status from the controller with parameters ButtonValues GetButtonsValues() const; @@ -414,8 +418,8 @@ public: */ void DeleteCallback(int key); - /// Swaps the state of the turbo buttons - void TurboButtonUpdate(); + /// Swaps the state of the turbo buttons and updates motion input + void StatusUpdate(); private: /// creates input devices from params @@ -527,7 +531,6 @@ private: bool is_configuring{false}; bool system_buttons_enabled{true}; f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard}; - bool force_update_motion{false}; u32 turbo_button_state{0}; // Temporary values to avoid doing changes while the controller is in configuring mode diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index 7cee39a53..4ccb1c596 100644 --- a/src/core/hid/input_converter.cpp +++ b/src/core/hid/input_converter.cpp @@ -54,6 +54,7 @@ Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatu case Common::Input::InputType::Analog: status.value = TransformToTrigger(callback).pressed.value; status.toggle = callback.analog_status.properties.toggle; + status.inverted = callback.analog_status.properties.inverted_button; break; case Common::Input::InputType::Trigger: status.value = TransformToTrigger(callback).pressed.value; @@ -61,6 +62,9 @@ Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatu case Common::Input::InputType::Button: status = callback.button_status; break; + case Common::Input::InputType::Motion: + status.value = std::abs(callback.motion_status.gyro.x.raw_value) > 1.0f; + break; default: LOG_ERROR(Input, "Conversion from type {} to button not implemented", callback.type); break; @@ -82,7 +86,7 @@ Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatu .range = 1.0f, .offset = 0.0f, }; - status.delta_timestamp = 5000; + status.delta_timestamp = 1000; status.force_update = true; status.accel.x = { .value = 0.0f, @@ -226,6 +230,10 @@ Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackSta status = callback.trigger_status; calculate_button_value = false; break; + case Common::Input::InputType::Motion: + status.analog.properties.range = 1.0f; + raw_value = callback.motion_status.accel.x.raw_value; + break; default: LOG_ERROR(Input, "Conversion from type {} to trigger not implemented", callback.type); break; diff --git a/src/core/hid/motion_input.cpp b/src/core/hid/motion_input.cpp index 0dd66c1cc..f56f2ae1d 100644 --- a/src/core/hid/motion_input.cpp +++ b/src/core/hid/motion_input.cpp @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <cmath> + #include "common/math_util.h" #include "core/hid/motion_input.h" @@ -35,11 +37,17 @@ void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) { gyro.y = std::clamp(gyro.y, -GyroMaxValue, GyroMaxValue); gyro.z = std::clamp(gyro.z, -GyroMaxValue, GyroMaxValue); - // Auto adjust drift to minimize drift + // Auto adjust gyro_bias to minimize drift if (!IsMoving(IsAtRestRelaxed)) { gyro_bias = (gyro_bias * 0.9999f) + (gyroscope * 0.0001f); } + // Adjust drift when calibration mode is enabled + if (calibration_mode) { + gyro_bias = (gyro_bias * 0.99f) + (gyroscope * 0.01f); + StopCalibration(); + } + if (gyro.Length() < gyro_threshold * user_gyro_threshold) { gyro = {}; } else { @@ -51,6 +59,20 @@ void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) { quat = quaternion; } +void MotionInput::SetEulerAngles(const Common::Vec3f& euler_angles) { + const float cr = std::cos(euler_angles.x * 0.5f); + const float sr = std::sin(euler_angles.x * 0.5f); + const float cp = std::cos(euler_angles.y * 0.5f); + const float sp = std::sin(euler_angles.y * 0.5f); + const float cy = std::cos(euler_angles.z * 0.5f); + const float sy = std::sin(euler_angles.z * 0.5f); + + quat.w = cr * cp * cy + sr * sp * sy; + quat.xyz.x = sr * cp * cy - cr * sp * sy; + quat.xyz.y = cr * sp * cy + sr * cp * sy; + quat.xyz.z = cr * cp * sy - sr * sp * cy; +} + void MotionInput::SetGyroBias(const Common::Vec3f& bias) { gyro_bias = bias; } @@ -91,6 +113,19 @@ void MotionInput::UpdateRotation(u64 elapsed_time) { rotations += gyro * sample_period; } +void MotionInput::Calibrate() { + calibration_mode = true; + calibration_counter = 0; +} + +void MotionInput::StopCalibration() { + if (calibration_counter++ > CalibrationSamples) { + calibration_mode = false; + ResetQuaternion(); + ResetRotations(); + } +} + // Based on Madgwick's implementation of Mayhony's AHRS algorithm. // https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs void MotionInput::UpdateOrientation(u64 elapsed_time) { @@ -222,6 +257,26 @@ Common::Vec3f MotionInput::GetRotations() const { return rotations; } +Common::Vec3f MotionInput::GetEulerAngles() const { + // roll (x-axis rotation) + const float sinr_cosp = 2 * (quat.w * quat.xyz.x + quat.xyz.y * quat.xyz.z); + const float cosr_cosp = 1 - 2 * (quat.xyz.x * quat.xyz.x + quat.xyz.y * quat.xyz.y); + + // pitch (y-axis rotation) + const float sinp = std::sqrt(1 + 2 * (quat.w * quat.xyz.y - quat.xyz.x * quat.xyz.z)); + const float cosp = std::sqrt(1 - 2 * (quat.w * quat.xyz.y - quat.xyz.x * quat.xyz.z)); + + // yaw (z-axis rotation) + const float siny_cosp = 2 * (quat.w * quat.xyz.z + quat.xyz.x * quat.xyz.y); + const float cosy_cosp = 1 - 2 * (quat.xyz.y * quat.xyz.y + quat.xyz.z * quat.xyz.z); + + return { + std::atan2(sinr_cosp, cosr_cosp), + 2 * std::atan2(sinp, cosp) - Common::PI / 2, + std::atan2(siny_cosp, cosy_cosp), + }; +} + void MotionInput::ResetOrientation() { if (!reset_enabled || only_accelerometer) { return; diff --git a/src/core/hid/motion_input.h b/src/core/hid/motion_input.h index 9f3fc1cf7..11678983d 100644 --- a/src/core/hid/motion_input.h +++ b/src/core/hid/motion_input.h @@ -23,6 +23,8 @@ public: static constexpr float GyroMaxValue = 5.0f; static constexpr float AccelMaxValue = 7.0f; + static constexpr std::size_t CalibrationSamples = 300; + explicit MotionInput(); MotionInput(const MotionInput&) = default; @@ -35,6 +37,7 @@ public: void SetAcceleration(const Common::Vec3f& acceleration); void SetGyroscope(const Common::Vec3f& gyroscope); void SetQuaternion(const Common::Quaternion<f32>& quaternion); + void SetEulerAngles(const Common::Vec3f& euler_angles); void SetGyroBias(const Common::Vec3f& bias); void SetGyroThreshold(f32 threshold); @@ -48,17 +51,21 @@ public: void UpdateRotation(u64 elapsed_time); void UpdateOrientation(u64 elapsed_time); + void Calibrate(); + [[nodiscard]] std::array<Common::Vec3f, 3> GetOrientation() const; [[nodiscard]] Common::Vec3f GetAcceleration() const; [[nodiscard]] Common::Vec3f GetGyroscope() const; [[nodiscard]] Common::Vec3f GetGyroBias() const; [[nodiscard]] Common::Vec3f GetRotations() const; [[nodiscard]] Common::Quaternion<f32> GetQuaternion() const; + [[nodiscard]] Common::Vec3f GetEulerAngles() const; [[nodiscard]] bool IsMoving(f32 sensitivity) const; [[nodiscard]] bool IsCalibrated(f32 sensitivity) const; private: + void StopCalibration(); void ResetOrientation(); void SetOrientationFromAccelerometer(); @@ -101,6 +108,12 @@ private: // Use accelerometer values to calculate position bool only_accelerometer = true; + + // When enabled it will aggressively adjust for gyro drift + bool calibration_mode = false; + + // Used to auto disable calibration mode + std::size_t calibration_counter = 0; }; } // namespace Core::HID diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp index 36d0d20d2..49bdc671e 100644 --- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp +++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp @@ -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 diff --git a/src/core/hle/kernel/k_auto_object.h b/src/core/hle/kernel/k_auto_object.h index 9b71fe371..f384b1568 100644 --- a/src/core/hle/kernel/k_auto_object.h +++ b/src/core/hle/kernel/k_auto_object.h @@ -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; diff --git a/src/core/hle/kernel/k_event_info.h b/src/core/hle/kernel/k_event_info.h index 25b3ff594..eacfa5dc6 100644 --- a/src/core/hle/kernel/k_event_info.h +++ b/src/core/hle/kernel/k_event_info.h @@ -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{}; diff --git a/src/core/hle/kernel/k_object_name.h b/src/core/hle/kernel/k_object_name.h index 2d97fc777..a8876fe37 100644 --- a/src/core/hle/kernel/k_object_name.h +++ b/src/core/hle/kernel/k_object_name.h @@ -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); diff --git a/src/core/hle/kernel/k_server_port.h b/src/core/hle/kernel/k_server_port.h index 21c040e62..625280290 100644 --- a/src/core/hle/kernel/k_server_port.h +++ b/src/core/hle/kernel/k_server_port.h @@ -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(); diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h index 5ee02f556..403891919 100644 --- a/src/core/hle/kernel/k_server_session.h +++ b/src/core/hle/kernel/k_server_session.h @@ -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; diff --git a/src/core/hle/kernel/k_session_request.h b/src/core/hle/kernel/k_session_request.h index b5f04907b..283669e0a 100644 --- a/src/core/hle/kernel/k_session_request.h +++ b/src/core/hle/kernel/k_session_request.h @@ -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: diff --git a/src/core/hle/kernel/k_shared_memory_info.h b/src/core/hle/kernel/k_shared_memory_info.h index 75b73ba39..2d8ff20d6 100644 --- a/src/core/hle/kernel/k_shared_memory_info.h +++ b/src/core/hle/kernel/k_shared_memory_info.h @@ -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&) {} diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index 9c1a41128..f9814ac8f 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -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{}; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 4f3366c9d..f33600ca5 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -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())); } diff --git a/src/core/hle/service/am/applets/applet_cabinet.cpp b/src/core/hle/service/am/applets/applet_cabinet.cpp index 93c9f2a55..8b754e9d4 100644 --- a/src/core/hle/service/am/applets/applet_cabinet.cpp +++ b/src/core/hle/service/am/applets/applet_cabinet.cpp @@ -11,7 +11,7 @@ #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/applet_cabinet.h" #include "core/hle/service/mii/mii_manager.h" -#include "core/hle/service/nfp/nfp_device.h" +#include "core/hle/service/nfc/common/device.h" namespace Service::AM::Applets { @@ -72,10 +72,10 @@ void Cabinet::Execute() { // TODO: listen on all controllers if (nfp_device == nullptr) { - nfp_device = std::make_shared<Service::NFP::NfpDevice>( + nfp_device = std::make_shared<Service::NFC::NfcDevice>( system.HIDCore().GetFirstNpadId(), system, service_context, availability_change_event); nfp_device->Initialize(); - nfp_device->StartDetection(Service::NFP::TagProtocol::All); + nfp_device->StartDetection(Service::NFC::NfcProtocol::All); } const Core::Frontend::CabinetParameters parameters{ @@ -106,20 +106,22 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name) Cancel(); } - if (nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagFound && - nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagMounted) { + if (nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagFound && + nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagMounted) { Cancel(); } - if (nfp_device->GetCurrentState() == Service::NFP::DeviceState::TagFound) { - nfp_device->Mount(Service::NFP::MountTarget::All); + if (nfp_device->GetCurrentState() == Service::NFC::DeviceState::TagFound) { + nfp_device->Mount(Service::NFP::ModelType::Amiibo, Service::NFP::MountTarget::All); } switch (applet_input_common.applet_mode) { case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings: { - Service::NFP::AmiiboName name{}; - std::memcpy(name.data(), amiibo_name.data(), std::min(amiibo_name.size(), name.size() - 1)); - nfp_device->SetRegisterInfoPrivate(name); + Service::NFP::RegisterInfoPrivate register_info{}; + std::memcpy(register_info.amiibo_name.data(), amiibo_name.data(), + std::min(amiibo_name.size(), register_info.amiibo_name.size() - 1)); + + nfp_device->SetRegisterInfoPrivate(register_info); break; } case Service::NFP::CabinetMode::StartGameDataEraser: @@ -139,7 +141,7 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name) applet_output.device_handle = applet_input_common.device_handle; applet_output.result = CabinetResult::Cancel; const auto reg_result = nfp_device->GetRegisterInfo(applet_output.register_info); - const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info); + const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info, false); nfp_device->Finalize(); if (reg_result.IsSuccess()) { diff --git a/src/core/hle/service/am/applets/applet_cabinet.h b/src/core/hle/service/am/applets/applet_cabinet.h index edd295a27..b56427021 100644 --- a/src/core/hle/service/am/applets/applet_cabinet.h +++ b/src/core/hle/service/am/applets/applet_cabinet.h @@ -19,8 +19,8 @@ namespace Core { class System; } // namespace Core -namespace Service::NFP { -class NfpDevice; +namespace Service::NFC { +class NfcDevice; } namespace Service::AM::Applets { @@ -96,7 +96,7 @@ private: Core::System& system; bool is_complete{false}; - std::shared_ptr<Service::NFP::NfpDevice> nfp_device; + std::shared_ptr<Service::NFC::NfcDevice> nfp_device; Kernel::KEvent* availability_change_event; KernelHelpers::ServiceContext service_context; StartParamForAmiiboSettings applet_input_common{}; diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 8abf71608..ef4aec4ea 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -423,8 +423,8 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { return; } - // This function is unique to yuzu for the turbo buttons to work properly - controller.device->TurboButtonUpdate(); + // This function is unique to yuzu for the turbo buttons and motion to work properly + controller.device->StatusUpdate(); auto& pad_entry = controller.npad_pad_state; auto& trigger_entry = controller.npad_trigger_state; diff --git a/src/core/hle/service/ipc_helpers.h b/src/core/hle/service/ipc_helpers.h index e4cb4e1f2..0e222362e 100644 --- a/src/core/hle/service/ipc_helpers.h +++ b/src/core/hle/service/ipc_helpers.h @@ -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()); diff --git a/src/core/hle/service/kernel_helpers.cpp b/src/core/hle/service/kernel_helpers.cpp index a39ce5212..6a313a03b 100644 --- a/src/core/hle/service/kernel_helpers.cpp +++ b/src/core/hle/service/kernel_helpers.cpp @@ -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; } diff --git a/src/core/hle/service/mutex.cpp b/src/core/hle/service/mutex.cpp index 07589a0f0..b0ff71d1b 100644 --- a/src/core/hle/service/mutex.cpp +++ b/src/core/hle/service/mutex.cpp @@ -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())); } diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfc/common/amiibo_crypto.cpp index a3622e792..f3901ee8d 100644 --- a/src/core/hle/service/nfp/amiibo_crypto.cpp +++ b/src/core/hle/service/nfc/common/amiibo_crypto.cpp @@ -12,7 +12,7 @@ #include "common/fs/fs.h" #include "common/fs/path_util.h" #include "common/logging/log.h" -#include "core/hle/service/nfp/amiibo_crypto.h" +#include "core/hle/service/nfc/common/amiibo_crypto.h" namespace Service::NFP::AmiiboCrypto { @@ -55,7 +55,7 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) { if (amiibo_data.constant_value != 0xA5) { return false; } - if (amiibo_data.model_info.tag_type != PackedTagType::Type2) { + if (amiibo_data.model_info.tag_type != NFC::PackedTagType::Type2) { return false; } if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001U) { diff --git a/src/core/hle/service/nfp/amiibo_crypto.h b/src/core/hle/service/nfc/common/amiibo_crypto.h index f6208ee6b..bf3044ed9 100644 --- a/src/core/hle/service/nfp/amiibo_crypto.h +++ b/src/core/hle/service/nfc/common/amiibo_crypto.h @@ -24,9 +24,9 @@ using DrgbOutput = std::array<u8, 0x20>; struct HashSeed { u16_be magic; std::array<u8, 0xE> padding; - UniqueSerialNumber uid_1; + NFC::UniqueSerialNumber uid_1; u8 nintendo_id_1; - UniqueSerialNumber uid_2; + NFC::UniqueSerialNumber uid_2; u8 nintendo_id_2; std::array<u8, 0x20> keygen_salt; }; diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfc/common/device.cpp index 3f9af53c8..e5de65ce0 100644 --- a/src/core/hle/service/nfp/nfp_device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp @@ -1,8 +1,6 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <array> - #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4701) // Potentially uninitialized local variable 'result' used @@ -26,21 +24,22 @@ #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/mii/mii_manager.h" #include "core/hle/service/mii/types.h" -#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/nfc/common/amiibo_crypto.h" +#include "core/hle/service/nfc/common/device.h" +#include "core/hle/service/nfc/mifare_result.h" +#include "core/hle/service/nfc/nfc_result.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" -namespace Service::NFP { -NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, +namespace Service::NFC { +NfcDevice::NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, KernelHelpers::ServiceContext& service_context_, Kernel::KEvent* availability_change_event_) : npad_id{npad_id_}, system{system_}, service_context{service_context_}, availability_change_event{availability_change_event_} { - activate_event = service_context.CreateEvent("IUser:NFPActivateEvent"); - deactivate_event = service_context.CreateEvent("IUser:NFPDeactivateEvent"); + activate_event = service_context.CreateEvent("NFC:ActivateEvent"); + deactivate_event = service_context.CreateEvent("NFC:DeactivateEvent"); npad_device = system.HIDCore().GetEmulatedController(npad_id); Core::HID::ControllerUpdateCallback engine_callback{ @@ -54,9 +53,9 @@ NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, current_posix_time = standard_steady_clock.GetCurrentTimePoint(system).time_point; } -NfpDevice::~NfpDevice() { - activate_event->Close(); - deactivate_event->Close(); +NfcDevice::~NfcDevice() { + service_context.CloseEvent(activate_event); + service_context.CloseEvent(deactivate_event); if (!is_controller_set) { return; } @@ -64,7 +63,7 @@ NfpDevice::~NfpDevice() { is_controller_set = false; }; -void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { +void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { if (!is_initalized) { return; } @@ -92,14 +91,14 @@ void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { const auto nfc_status = npad_device->GetNfc(); switch (nfc_status.state) { case Common::Input::NfcState::NewAmiibo: - LoadAmiibo(nfc_status.data); + LoadNfcTag(nfc_status.data); break; case Common::Input::NfcState::AmiiboRemoved: if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) { break; } if (device_state != DeviceState::SearchingForTag) { - CloseAmiibo(); + CloseNfcTag(); } break; default: @@ -107,28 +106,29 @@ void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { } } -bool NfpDevice::LoadAmiibo(std::span<const u8> data) { +bool NfcDevice::LoadNfcTag(std::span<const u8> data) { if (device_state != DeviceState::SearchingForTag) { - LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state); + LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state); return false; } - if (data.size() != sizeof(EncryptedNTAG215File)) { - LOG_ERROR(Service_NFP, "Not an amiibo, size={}", data.size()); + if (data.size() < sizeof(NFP::EncryptedNTAG215File)) { + LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size()); return false; } - // TODO: Filter by allowed_protocols here + mifare_data.resize(data.size()); + memcpy(mifare_data.data(), data.data(), data.size()); - memcpy(&tag_data, data.data(), sizeof(EncryptedNTAG215File)); - is_plain_amiibo = AmiiboCrypto::IsAmiiboValid(tag_data); + memcpy(&tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); + is_plain_amiibo = NFP::AmiiboCrypto::IsAmiiboValid(tag_data); if (is_plain_amiibo) { - encrypted_tag_data = AmiiboCrypto::EncodedDataToNfcData(tag_data); + encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(tag_data); LOG_INFO(Service_NFP, "Using plain amiibo"); } else { tag_data = {}; - memcpy(&encrypted_tag_data, data.data(), sizeof(EncryptedNTAG215File)); + memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); } device_state = DeviceState::TagFound; @@ -137,8 +137,8 @@ bool NfpDevice::LoadAmiibo(std::span<const u8> data) { return true; } -void NfpDevice::CloseAmiibo() { - LOG_INFO(Service_NFP, "Remove amiibo"); +void NfcDevice::CloseNfcTag() { + LOG_INFO(Service_NFC, "Remove nfc tag"); if (device_state == DeviceState::TagMounted) { Unmount(); @@ -147,26 +147,28 @@ void NfpDevice::CloseAmiibo() { device_state = DeviceState::TagRemoved; encrypted_tag_data = {}; tag_data = {}; + mifare_data = {}; activate_event->GetReadableEvent().Clear(); deactivate_event->Signal(); } -Kernel::KReadableEvent& NfpDevice::GetActivateEvent() const { +Kernel::KReadableEvent& NfcDevice::GetActivateEvent() const { return activate_event->GetReadableEvent(); } -Kernel::KReadableEvent& NfpDevice::GetDeactivateEvent() const { +Kernel::KReadableEvent& NfcDevice::GetDeactivateEvent() const { return deactivate_event->GetReadableEvent(); } -void NfpDevice::Initialize() { +void NfcDevice::Initialize() { device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable; encrypted_tag_data = {}; tag_data = {}; + mifare_data = {}; is_initalized = true; } -void NfpDevice::Finalize() { +void NfcDevice::Finalize() { if (device_state == DeviceState::TagMounted) { Unmount(); } @@ -177,17 +179,17 @@ void NfpDevice::Finalize() { is_initalized = false; } -Result NfpDevice::StartDetection(TagProtocol allowed_protocol) { +Result NfcDevice::StartDetection(NfcProtocol allowed_protocol) { if (device_state != DeviceState::Initialized && device_state != DeviceState::TagRemoved) { - LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); - return WrongDeviceState; + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + return ResultWrongDeviceState; } if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::NFC) != Common::Input::DriverResult::Success) { - LOG_ERROR(Service_NFP, "Nfc not supported"); - return NfcDisabled; + LOG_ERROR(Service_NFC, "Nfc not supported"); + return ResultNfcDisabled; } device_state = DeviceState::SearchingForTag; @@ -195,7 +197,7 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) { return ResultSuccess; } -Result NfpDevice::StopDetection() { +Result NfcDevice::StopDetection() { npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::Active); @@ -204,7 +206,7 @@ Result NfpDevice::StopDetection() { } if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) { - CloseAmiibo(); + CloseNfcTag(); } if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { @@ -212,94 +214,129 @@ Result NfpDevice::StopDetection() { return ResultSuccess; } - LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); - return WrongDeviceState; + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + return ResultWrongDeviceState; } -Result NfpDevice::Flush() { - if (device_state != DeviceState::TagMounted) { - LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); +Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const { + if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { - LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); - return WrongDeviceState; + if (is_mifare) { + tag_info = { + .uuid = encrypted_tag_data.uuid.uid, + .uuid_extension = {}, + .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()), + .protocol = NfcProtocol::TypeA, + .tag_type = TagType::Type4, + }; + return ResultSuccess; } - auto& settings = tag_data.settings; - - const auto& current_date = GetAmiiboDate(current_posix_time); - if (settings.write_date.raw_date != current_date.raw_date) { - settings.write_date = current_date; - UpdateSettingsCrc(); - } + // Protocol and tag type may change here + tag_info = { + .uuid = encrypted_tag_data.uuid.uid, + .uuid_extension = {}, + .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()), + .protocol = NfcProtocol::TypeA, + .tag_type = TagType::Type2, + }; - tag_data.write_counter++; + return ResultSuccess; +} - FlushWithBreak(BreakType::Normal); +Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters, + std::span<MifareReadBlockData> read_block_data) const { + Result result = ResultSuccess; - is_data_moddified = false; + for (std::size_t i = 0; i < parameters.size(); i++) { + result = ReadMifare(parameters[i], read_block_data[i]); + if (result.IsError()) { + break; + } + } - return ResultSuccess; + return result; } -Result NfpDevice::FlushDebug() { - if (device_state != DeviceState::TagMounted) { +Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter, + MifareReadBlockData& read_block_data) const { + const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock); + read_block_data.sector_number = parameter.sector_number; + + if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { - LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); - return WrongDeviceState; + if (mifare_data.size() < sector_index + sizeof(DataBlock)) { + return Mifare::ResultReadError; } - tag_data.write_counter++; - - FlushWithBreak(BreakType::Normal); - - is_data_moddified = false; + // TODO: Use parameter.sector_key to read encrypted data + memcpy(read_block_data.data.data(), mifare_data.data() + sector_index, sizeof(DataBlock)); 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; - } +Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) { + Result result = ResultSuccess; - std::vector<u8> data(sizeof(EncryptedNTAG215File)); - if (is_plain_amiibo) { - memcpy(data.data(), &tag_data, sizeof(tag_data)); - } else { - if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) { - LOG_ERROR(Service_NFP, "Failed to encode data"); - return WriteAmiiboFailed; + for (std::size_t i = 0; i < parameters.size(); i++) { + result = WriteMifare(parameters[i]); + if (result.IsError()) { + break; } - - memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); } - if (!npad_device->WriteNfc(data)) { + if (!npad_device->WriteNfc(mifare_data)) { LOG_ERROR(Service_NFP, "Error writing to file"); - return WriteAmiiboFailed; + return Mifare::ResultReadError; + } + + return result; +} + +Result NfcDevice::WriteMifare(const MifareWriteBlockParameter& parameter) { + const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock); + + if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mifare_data.size() < sector_index + sizeof(DataBlock)) { + return Mifare::ResultReadError; } + // TODO: Use parameter.sector_key to encrypt the data + memcpy(mifare_data.data() + sector_index, parameter.data.data(), sizeof(DataBlock)); + return ResultSuccess; } -Result NfpDevice::Mount(MountTarget mount_target_) { +Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout, + std::span<const u8> command_data, + std::span<u8> out_data) { + // Not implemented + return ResultSuccess; +} + +Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target_) { if (device_state != DeviceState::TagFound) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } // The loaded amiibo is not encrypted @@ -309,22 +346,22 @@ Result NfpDevice::Mount(MountTarget mount_target_) { return ResultSuccess; } - if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { + if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { LOG_ERROR(Service_NFP, "Not an amiibo"); - return NotAnAmiibo; + return ResultNotAnAmiibo; } // Mark amiibos as read only when keys are missing - if (!AmiiboCrypto::IsKeyAvailable()) { + if (!NFP::AmiiboCrypto::IsKeyAvailable()) { LOG_ERROR(Service_NFP, "No keys detected"); device_state = DeviceState::TagMounted; - mount_target = MountTarget::Rom; + mount_target = NFP::MountTarget::Rom; return ResultSuccess; } - if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { + if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state); - return CorruptedData; + return ResultCorruptedData; } device_state = DeviceState::TagMounted; @@ -332,13 +369,13 @@ Result NfpDevice::Mount(MountTarget mount_target_) { return ResultSuccess; } -Result NfpDevice::Unmount() { +Result NfcDevice::Unmount() { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } // Save data before unloading the amiibo @@ -347,43 +384,123 @@ Result NfpDevice::Unmount() { } device_state = DeviceState::TagFound; - mount_target = MountTarget::None; + mount_target = NFP::MountTarget::None; is_app_area_open = false; return ResultSuccess; } -Result NfpDevice::GetTagInfo(TagInfo& tag_info) const { - if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { +Result NfcDevice::Flush() { + if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - tag_info = { - .uuid = encrypted_tag_data.uuid.uid, - .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()), - .protocol = TagProtocol::TypeA, - .tag_type = TagType::Type2, - }; + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + auto& settings = tag_data.settings; + + const auto& current_date = GetAmiiboDate(current_posix_time); + if (settings.write_date.raw_date != current_date.raw_date) { + settings.write_date = current_date; + UpdateSettingsCrc(); + } + + tag_data.write_counter++; + + FlushWithBreak(NFP::BreakType::Normal); + + is_data_moddified = false; return ResultSuccess; } -Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const { +Result NfcDevice::FlushDebug() { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + tag_data.write_counter++; + + FlushWithBreak(NFP::BreakType::Normal); + + is_data_moddified = false; + + return ResultSuccess; +} + +Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) { + if (break_type != NFP::BreakType::Normal) { + LOG_ERROR(Service_NFC, "Break type not implemented {}", break_type); + return ResultWrongDeviceState; + } + + std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); + if (is_plain_amiibo) { + memcpy(data.data(), &tag_data, sizeof(tag_data)); + } else { + if (!NFP::AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) { + LOG_ERROR(Service_NFP, "Failed to encode data"); + return ResultWriteAmiiboFailed; + } + + memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); + } + + if (!npad_device->WriteNfc(data)) { + LOG_ERROR(Service_NFP, "Error writing to file"); + return ResultWriteAmiiboFailed; + } + + return ResultSuccess; +} + +Result NfcDevice::Restore() { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + // TODO: Load amiibo from backup on system + LOG_ERROR(Service_NFP, "Not Implemented"); + return ResultSuccess; +} + +Result NfcDevice::GetCommonInfo(NFP::CommonInfo& common_info) const { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } const auto& settings = tag_data.settings; @@ -393,18 +510,18 @@ Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const { .last_write_date = settings.write_date.GetWriteDate(), .write_counter = tag_data.write_counter, .version = tag_data.amiibo_version, - .application_area_size = sizeof(ApplicationArea), + .application_area_size = sizeof(NFP::ApplicationArea), }; return ResultSuccess; } -Result NfpDevice::GetModelInfo(ModelInfo& model_info) const { +Result NfcDevice::GetModelInfo(NFP::ModelInfo& model_info) const { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } const auto& model_info_data = encrypted_tag_data.user_memory.model_info; @@ -418,22 +535,22 @@ Result NfpDevice::GetModelInfo(ModelInfo& model_info) const { return ResultSuccess; } -Result NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const { +Result NfcDevice::GetRegisterInfo(NFP::RegisterInfo& 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 ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } if (tag_data.settings.settings.amiibo_initialized == 0) { - return RegistrationIsNotInitialized; + return ResultRegistrationIsNotInitialized; } Service::Mii::MiiManager manager; @@ -450,22 +567,22 @@ Result NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const { return ResultSuccess; } -Result NfpDevice::GetRegisterInfoPrivate(RegisterInfoPrivate& register_info) const { +Result NfcDevice::GetRegisterInfoPrivate(NFP::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 ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } if (tag_data.settings.settings.amiibo_initialized == 0) { - return RegistrationIsNotInitialized; + return ResultRegistrationIsNotInitialized; } Service::Mii::MiiManager manager; @@ -482,18 +599,18 @@ Result NfpDevice::GetRegisterInfoPrivate(RegisterInfoPrivate& register_info) con return ResultSuccess; } -Result NfpDevice::GetAdminInfo(AdminInfo& admin_info) const { +Result NfcDevice::GetAdminInfo(NFP::AdminInfo& admin_info) const { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } u8 flags = static_cast<u8>(tag_data.settings.settings.raw >> 0x4); @@ -503,17 +620,18 @@ Result NfpDevice::GetAdminInfo(AdminInfo& admin_info) const { u64 application_id = 0; u32 application_area_id = 0; - AppAreaVersion app_area_version = AppAreaVersion::NotSet; + NFP::AppAreaVersion app_area_version = NFP::AppAreaVersion::NotSet; if (tag_data.settings.settings.appdata_initialized != 0) { application_id = tag_data.application_id; - app_area_version = - static_cast<AppAreaVersion>(application_id >> application_id_version_offset & 0xf); + app_area_version = static_cast<NFP::AppAreaVersion>( + application_id >> NFP::application_id_version_offset & 0xf); // Restore application id to original value if (application_id >> 0x38 != 0) { const u8 application_byte = tag_data.application_id_byte & 0xf; - application_id = RemoveVersionByte(application_id) | - (static_cast<u64>(application_byte) << application_id_version_offset); + application_id = + RemoveVersionByte(application_id) | + (static_cast<u64>(application_byte) << NFP::application_id_version_offset); } application_area_id = tag_data.application_area_id; @@ -532,22 +650,22 @@ Result NfpDevice::GetAdminInfo(AdminInfo& admin_info) const { return ResultSuccess; } -Result NfpDevice::DeleteRegisterInfo() { +Result NfcDevice::DeleteRegisterInfo() { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } if (tag_data.settings.settings.amiibo_initialized == 0) { - return RegistrationIsNotInitialized; + return ResultRegistrationIsNotInitialized; } Common::TinyMT rng{}; @@ -564,18 +682,18 @@ Result NfpDevice::DeleteRegisterInfo() { return Flush(); } -Result NfpDevice::SetRegisterInfoPrivate(const AmiiboName& amiibo_name) { +Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } Service::Mii::MiiManager manager; @@ -587,7 +705,7 @@ Result NfpDevice::SetRegisterInfoPrivate(const AmiiboName& amiibo_name) { settings.write_date.raw_date = 0; } - SetAmiiboName(settings, amiibo_name); + SetAmiiboName(settings, register_info.amiibo_name); tag_data.owner_mii = manager.BuildFromStoreData(mii); tag_data.mii_extension = manager.SetFromStoreData(mii); tag_data.unknown = 0; @@ -601,18 +719,18 @@ Result NfpDevice::SetRegisterInfoPrivate(const AmiiboName& amiibo_name) { return Flush(); } -Result NfpDevice::RestoreAmiibo() { +Result NfcDevice::RestoreAmiibo() { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } // TODO: Load amiibo from backup on system @@ -620,7 +738,7 @@ Result NfpDevice::RestoreAmiibo() { return ResultSuccess; } -Result NfpDevice::Format() { +Result NfcDevice::Format() { auto result1 = DeleteApplicationArea(); auto result2 = DeleteRegisterInfo(); @@ -635,28 +753,28 @@ Result NfpDevice::Format() { return Flush(); } -Result NfpDevice::OpenApplicationArea(u32 access_id) { +Result NfcDevice::OpenApplicationArea(u32 access_id) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } if (tag_data.settings.settings.appdata_initialized.Value() == 0) { LOG_WARNING(Service_NFP, "Application area is not initialized"); - return ApplicationAreaIsNotInitialized; + return ResultApplicationAreaIsNotInitialized; } if (tag_data.application_area_id != access_id) { LOG_WARNING(Service_NFP, "Wrong application area id"); - return WrongApplicationAreaId; + return ResultWrongApplicationAreaId; } is_app_area_open = true; @@ -664,25 +782,25 @@ Result NfpDevice::OpenApplicationArea(u32 access_id) { return ResultSuccess; } -Result NfpDevice::GetApplicationAreaId(u32& application_area_id) const { +Result NfcDevice::GetApplicationAreaId(u32& application_area_id) const { application_area_id = {}; if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } if (tag_data.settings.settings.appdata_initialized.Value() == 0) { LOG_WARNING(Service_NFP, "Application area is not initialized"); - return ApplicationAreaIsNotInitialized; + return ResultApplicationAreaIsNotInitialized; } application_area_id = tag_data.application_area_id; @@ -690,64 +808,61 @@ Result NfpDevice::GetApplicationAreaId(u32& application_area_id) const { return ResultSuccess; } -Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const { +Result NfcDevice::GetApplicationArea(std::span<u8> data) const { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } if (!is_app_area_open) { LOG_ERROR(Service_NFP, "Application area is not open"); - return WrongDeviceState; + return ResultWrongDeviceState; } if (tag_data.settings.settings.appdata_initialized.Value() == 0) { LOG_ERROR(Service_NFP, "Application area is not initialized"); - return ApplicationAreaIsNotInitialized; - } - - if (data.size() > sizeof(ApplicationArea)) { - data.resize(sizeof(ApplicationArea)); + return ResultApplicationAreaIsNotInitialized; } - memcpy(data.data(), tag_data.application_area.data(), data.size()); + memcpy(data.data(), tag_data.application_area.data(), + std::min(data.size(), sizeof(NFP::ApplicationArea))); return ResultSuccess; } -Result NfpDevice::SetApplicationArea(std::span<const u8> data) { +Result NfcDevice::SetApplicationArea(std::span<const u8> data) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } if (!is_app_area_open) { LOG_ERROR(Service_NFP, "Application area is not open"); - return WrongDeviceState; + return ResultWrongDeviceState; } if (tag_data.settings.settings.appdata_initialized.Value() == 0) { LOG_ERROR(Service_NFP, "Application area is not initialized"); - return ApplicationAreaIsNotInitialized; + return ResultApplicationAreaIsNotInitialized; } - if (data.size() > sizeof(ApplicationArea)) { + if (data.size() > sizeof(NFP::ApplicationArea)) { LOG_ERROR(Service_NFP, "Wrong data size {}", data.size()); return ResultUnknown; } @@ -756,9 +871,9 @@ Result NfpDevice::SetApplicationArea(std::span<const u8> data) { std::memcpy(tag_data.application_area.data(), data.data(), data.size()); // Fill remaining data with random numbers rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(), - sizeof(ApplicationArea) - data.size()); + sizeof(NFP::ApplicationArea) - data.size()); - if (tag_data.application_write_counter != counter_limit) { + if (tag_data.application_write_counter != NFP::counter_limit) { tag_data.application_write_counter++; } @@ -767,64 +882,64 @@ Result NfpDevice::SetApplicationArea(std::span<const u8> data) { return ResultSuccess; } -Result NfpDevice::CreateApplicationArea(u32 access_id, std::span<const u8> data) { +Result NfcDevice::CreateApplicationArea(u32 access_id, std::span<const u8> data) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } if (tag_data.settings.settings.appdata_initialized.Value() != 0) { LOG_ERROR(Service_NFP, "Application area already exist"); - return ApplicationAreaExist; + return ResultApplicationAreaExist; } return RecreateApplicationArea(access_id, data); } -Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> data) { +Result NfcDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> data) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } if (is_app_area_open) { LOG_ERROR(Service_NFP, "Application area is open"); - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } - if (data.size() > sizeof(ApplicationArea)) { + if (data.size() > sizeof(NFP::ApplicationArea)) { LOG_ERROR(Service_NFP, "Wrong data size {}", data.size()); - return WrongApplicationAreaSize; + return ResultWrongApplicationAreaSize; } Common::TinyMT rng{}; std::memcpy(tag_data.application_area.data(), data.data(), data.size()); // Fill remaining data with random numbers rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(), - sizeof(ApplicationArea) - data.size()); + sizeof(NFP::ApplicationArea) - data.size()); - if (tag_data.application_write_counter != counter_limit) { + if (tag_data.application_write_counter != NFP::counter_limit) { tag_data.application_write_counter++; } const u64 application_id = system.GetApplicationProcessProgramID(); tag_data.application_id_byte = - static_cast<u8>(application_id >> application_id_version_offset & 0xf); + static_cast<u8>(application_id >> NFP::application_id_version_offset & 0xf); tag_data.application_id = - RemoveVersionByte(application_id) | - (static_cast<u64>(AppAreaVersion::NintendoSwitch) << application_id_version_offset); + RemoveVersionByte(application_id) | (static_cast<u64>(NFP::AppAreaVersion::NintendoSwitch) + << NFP::application_id_version_offset); tag_data.settings.settings.appdata_initialized.Assign(1); tag_data.application_area_id = access_id; tag_data.unknown = {}; @@ -835,30 +950,30 @@ Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> dat return Flush(); } -Result NfpDevice::DeleteApplicationArea() { +Result NfcDevice::DeleteApplicationArea() { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } if (tag_data.settings.settings.appdata_initialized == 0) { - return ApplicationAreaIsNotInitialized; + return ResultApplicationAreaIsNotInitialized; } - if (tag_data.application_write_counter != counter_limit) { + if (tag_data.application_write_counter != NFP::counter_limit) { tag_data.application_write_counter++; } Common::TinyMT rng{}; - rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(ApplicationArea)); + rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(NFP::ApplicationArea)); rng.GenerateRandomBytes(&tag_data.application_id, sizeof(u64)); rng.GenerateRandomBytes(&tag_data.application_area_id, sizeof(u32)); rng.GenerateRandomBytes(&tag_data.application_id_byte, sizeof(u8)); @@ -872,18 +987,18 @@ Result NfpDevice::DeleteApplicationArea() { return Flush(); } -Result NfpDevice::ExistApplicationArea(bool& has_application_area) { +Result NfcDevice::ExistsApplicationArea(bool& has_application_area) const { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } has_application_area = tag_data.settings.settings.appdata_initialized.Value() != 0; @@ -891,21 +1006,21 @@ Result NfpDevice::ExistApplicationArea(bool& has_application_area) { return ResultSuccess; } -Result NfpDevice::GetAll(NfpData& data) const { +Result NfcDevice::GetAll(NFP::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 ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } - CommonInfo common_info{}; + NFP::CommonInfo common_info{}; Service::Mii::MiiManager manager; const u64 application_id = tag_data.application_id; @@ -930,8 +1045,8 @@ Result NfpDevice::GetAll(NfpData& data) const { .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), + .console_type = static_cast<NFP::AppAreaVersion>( + application_id >> NFP::application_id_version_offset & 0xf), .application_id_byte = tag_data.application_id_byte, .application_area = tag_data.application_area, }; @@ -939,18 +1054,18 @@ Result NfpDevice::GetAll(NfpData& data) const { return ResultSuccess; } -Result NfpDevice::SetAll(const NfpData& data) { +Result NfcDevice::SetAll(const NFP::NfpData& data) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } tag_data.constant_value = data.magic; @@ -977,18 +1092,18 @@ Result NfpDevice::SetAll(const NfpData& data) { return ResultSuccess; } -Result NfpDevice::BreakTag(BreakType break_type) { +Result NfcDevice::BreakTag(NFP::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 ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } // TODO: Complete this implementation @@ -996,28 +1111,28 @@ Result NfpDevice::BreakTag(BreakType break_type) { return FlushWithBreak(break_type); } -Result NfpDevice::ReadBackupData() { +Result NfcDevice::ReadBackupData(std::span<u8> data) const { // Not implemented return ResultSuccess; } -Result NfpDevice::WriteBackupData() { +Result NfcDevice::WriteBackupData(std::span<const u8> data) { // Not implemented return ResultSuccess; } -Result NfpDevice::WriteNtf() { +Result NfcDevice::WriteNtf(std::span<const u8> data) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { - return TagRemoved; + return ResultTagRemoved; } - return WrongDeviceState; + return ResultWrongDeviceState; } - if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); - return WrongDeviceState; + return ResultWrongDeviceState; } // Not implemented @@ -1025,29 +1140,12 @@ Result NfpDevice::WriteNtf() { return ResultSuccess; } -u64 NfpDevice::GetHandle() const { - // Generate a handle based of the npad id - return static_cast<u64>(npad_id); -} - -u32 NfpDevice::GetApplicationAreaSize() const { - return sizeof(ApplicationArea); -} - -DeviceState NfpDevice::GetCurrentState() const { - return device_state; -} - -Core::HID::NpadIdType NfpDevice::GetNpadId() const { - return npad_id; -} - -AmiiboName NfpDevice::GetAmiiboName(const AmiiboSettings& settings) const { - std::array<char16_t, amiibo_name_length> settings_amiibo_name{}; - AmiiboName amiibo_name{}; +NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) const { + std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{}; + NFP::AmiiboName amiibo_name{}; // Convert from big endian to little endian - for (std::size_t i = 0; i < amiibo_name_length; i++) { + for (std::size_t i = 0; i < NFP::amiibo_name_length; i++) { settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]); } @@ -1058,8 +1156,8 @@ AmiiboName NfpDevice::GetAmiiboName(const AmiiboSettings& settings) const { return amiibo_name; } -void NfpDevice::SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name) { - std::array<char16_t, amiibo_name_length> settings_amiibo_name{}; +void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) { + std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{}; // Convert from utf8 to utf16 const auto amiibo_name_utf16 = Common::UTF8ToUTF16(amiibo_name.data()); @@ -1067,16 +1165,16 @@ void NfpDevice::SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo amiibo_name_utf16.size() * sizeof(char16_t)); // Convert from little endian to big endian - for (std::size_t i = 0; i < amiibo_name_length; i++) { + for (std::size_t i = 0; i < NFP::amiibo_name_length; i++) { settings.amiibo_name[i] = static_cast<u16_be>(settings_amiibo_name[i]); } } -AmiiboDate NfpDevice::GetAmiiboDate(s64 posix_time) const { +NFP::AmiiboDate NfcDevice::GetAmiiboDate(s64 posix_time) const { const auto& time_zone_manager = system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager(); Time::TimeZone::CalendarInfo calendar_info{}; - AmiiboDate amiibo_date{}; + NFP::AmiiboDate amiibo_date{}; amiibo_date.SetYear(2000); amiibo_date.SetMonth(1); @@ -1091,14 +1189,14 @@ AmiiboDate NfpDevice::GetAmiiboDate(s64 posix_time) const { return amiibo_date; } -u64 NfpDevice::RemoveVersionByte(u64 application_id) const { - return application_id & ~(0xfULL << application_id_version_offset); +u64 NfcDevice::RemoveVersionByte(u64 application_id) const { + return application_id & ~(0xfULL << NFP::application_id_version_offset); } -void NfpDevice::UpdateSettingsCrc() { +void NfcDevice::UpdateSettingsCrc() { auto& settings = tag_data.settings; - if (settings.crc_counter != counter_limit) { + if (settings.crc_counter != NFP::counter_limit) { settings.crc_counter++; } @@ -1109,7 +1207,7 @@ void NfpDevice::UpdateSettingsCrc() { settings.crc = crc.checksum(); } -void NfpDevice::UpdateRegisterInfoCrc() { +void NfcDevice::UpdateRegisterInfoCrc() { #pragma pack(push, 1) struct CrcData { Mii::Ver3StoreData mii; @@ -1134,4 +1232,18 @@ void NfpDevice::UpdateRegisterInfoCrc() { tag_data.register_info_crc = crc.checksum(); } -} // namespace Service::NFP +u64 NfcDevice::GetHandle() const { + // Generate a handle based of the npad id + return static_cast<u64>(npad_id); +} + +DeviceState NfcDevice::GetCurrentState() const { + return device_state; +} + +Result NfcDevice::GetNpadId(Core::HID::NpadIdType& out_npad_id) const { + out_npad_id = npad_id; + return ResultSuccess; +} + +} // namespace Service::NFC diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h new file mode 100644 index 000000000..654eda98e --- /dev/null +++ b/src/core/hle/service/nfc/common/device.h @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "common/common_types.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/nfc/mifare_types.h" +#include "core/hle/service/nfc/nfc_types.h" +#include "core/hle/service/nfp/nfp_types.h" +#include "core/hle/service/service.h" +#include "core/hle/service/time/clock_types.h" + +namespace Kernel { +class KEvent; +class KReadableEvent; +} // namespace Kernel + +namespace Core { +class System; +} // namespace Core + +namespace Core::HID { +class EmulatedController; +enum class ControllerTriggerType; +enum class NpadIdType : u32; +} // namespace Core::HID + +namespace Service::NFC { +class NfcDevice { +public: + NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, + KernelHelpers::ServiceContext& service_context_, + Kernel::KEvent* availability_change_event_); + ~NfcDevice(); + + void Initialize(); + void Finalize(); + + Result StartDetection(NfcProtocol allowed_protocol); + Result StopDetection(); + + Result GetTagInfo(TagInfo& tag_info, bool is_mifare) const; + + Result ReadMifare(std::span<const MifareReadBlockParameter> parameters, + std::span<MifareReadBlockData> read_block_data) const; + Result ReadMifare(const MifareReadBlockParameter& parameter, + MifareReadBlockData& read_block_data) const; + + Result WriteMifare(std::span<const MifareWriteBlockParameter> parameters); + Result WriteMifare(const MifareWriteBlockParameter& parameter); + + Result SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout, + std::span<const u8> command_data, std::span<u8> out_data); + + Result Mount(NFP::ModelType model_type, NFP::MountTarget mount_target); + Result Unmount(); + + Result Flush(); + Result FlushDebug(); + Result FlushWithBreak(NFP::BreakType break_type); + Result Restore(); + + Result GetCommonInfo(NFP::CommonInfo& common_info) const; + Result GetModelInfo(NFP::ModelInfo& model_info) const; + Result GetRegisterInfo(NFP::RegisterInfo& register_info) const; + Result GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info) const; + Result GetAdminInfo(NFP::AdminInfo& admin_info) const; + + Result DeleteRegisterInfo(); + Result SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info); + Result RestoreAmiibo(); + Result Format(); + + Result OpenApplicationArea(u32 access_id); + Result GetApplicationAreaId(u32& application_area_id) const; + Result GetApplicationArea(std::span<u8> data) const; + Result SetApplicationArea(std::span<const u8> data); + Result CreateApplicationArea(u32 access_id, std::span<const u8> data); + Result RecreateApplicationArea(u32 access_id, std::span<const u8> data); + Result DeleteApplicationArea(); + Result ExistsApplicationArea(bool& has_application_area) const; + + Result GetAll(NFP::NfpData& data) const; + Result SetAll(const NFP::NfpData& data); + Result BreakTag(NFP::BreakType break_type); + Result ReadBackupData(std::span<u8> data) const; + Result WriteBackupData(std::span<const u8> data); + Result WriteNtf(std::span<const u8> data); + + u64 GetHandle() const; + DeviceState GetCurrentState() const; + Result GetNpadId(Core::HID::NpadIdType& out_npad_id) const; + + Kernel::KReadableEvent& GetActivateEvent() const; + Kernel::KReadableEvent& GetDeactivateEvent() const; + +private: + void NpadUpdate(Core::HID::ControllerTriggerType type); + bool LoadNfcTag(std::span<const u8> data); + void CloseNfcTag(); + + NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const; + void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name); + NFP::AmiiboDate GetAmiiboDate(s64 posix_time) const; + u64 RemoveVersionByte(u64 application_id) const; + void UpdateSettingsCrc(); + void UpdateRegisterInfoCrc(); + + bool is_controller_set{}; + int callback_key; + const Core::HID::NpadIdType npad_id; + Core::System& system; + Core::HID::EmulatedController* npad_device = nullptr; + KernelHelpers::ServiceContext& service_context; + Kernel::KEvent* activate_event = nullptr; + Kernel::KEvent* deactivate_event = nullptr; + Kernel::KEvent* availability_change_event = nullptr; + + bool is_initalized{}; + NfcProtocol allowed_protocols{}; + DeviceState device_state{DeviceState::Unavailable}; + + // NFP data + bool is_data_moddified{}; + bool is_app_area_open{}; + bool is_plain_amiibo{}; + s64 current_posix_time{}; + NFP::MountTarget mount_target{NFP::MountTarget::None}; + + NFP::NTAG215File tag_data{}; + std::vector<u8> mifare_data{}; + NFP::EncryptedNTAG215File encrypted_tag_data{}; +}; + +} // namespace Service::NFC diff --git a/src/core/hle/service/nfc/common/device_manager.cpp b/src/core/hle/service/nfc/common/device_manager.cpp new file mode 100644 index 000000000..d5deaaf27 --- /dev/null +++ b/src/core/hle/service/nfc/common/device_manager.cpp @@ -0,0 +1,695 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "common/logging/log.h" +#include "core/core.h" +#include "core/hid/hid_types.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/nfc/common/device.h" +#include "core/hle/service/nfc/common/device_manager.h" +#include "core/hle/service/nfc/nfc_result.h" +#include "core/hle/service/time/clock_types.h" + +namespace Service::NFC { + +DeviceManager::DeviceManager(Core::System& system_, KernelHelpers::ServiceContext& service_context_) + : system{system_}, service_context{service_context_} { + + availability_change_event = + service_context.CreateEvent("Nfc:DeviceManager:AvailabilityChangeEvent"); + + for (u32 device_index = 0; device_index < devices.size(); device_index++) { + devices[device_index] = + std::make_shared<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system, + service_context, availability_change_event); + } + + is_initialized = false; +} + +DeviceManager ::~DeviceManager() { + service_context.CloseEvent(availability_change_event); +} + +Result DeviceManager::Initialize() { + for (auto& device : devices) { + device->Initialize(); + } + is_initialized = true; + return ResultSuccess; +} + +Result DeviceManager::Finalize() { + for (auto& device : devices) { + device->Finalize(); + } + is_initialized = false; + return ResultSuccess; +} + +Result DeviceManager::ListDevices(std::vector<u64>& nfp_devices, + std::size_t max_allowed_devices) const { + for (auto& device : devices) { + if (nfp_devices.size() >= max_allowed_devices) { + continue; + } + if (device->GetCurrentState() != DeviceState::Unavailable) { + nfp_devices.push_back(device->GetHandle()); + } + } + + if (nfp_devices.empty()) { + return ResultDeviceNotFound; + } + + return ResultSuccess; +} + +DeviceState DeviceManager::GetDeviceState(u64 device_handle) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + const auto result = GetDeviceFromHandle(device_handle, device, false); + + if (result.IsSuccess()) { + return device->GetCurrentState(); + } + + return DeviceState::Unavailable; +} + +Result DeviceManager::GetNpadId(u64 device_handle, Core::HID::NpadIdType& npad_id) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->GetNpadId(npad_id); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Kernel::KReadableEvent& DeviceManager::AttachAvailabilityChangeEvent() const { + return availability_change_event->GetReadableEvent(); +} + +Result DeviceManager::StartDetection(u64 device_handle, NfcProtocol tag_protocol) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->StartDetection(tag_protocol); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::StopDetection(u64 device_handle) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->StopDetection(); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::GetTagInfo(u64 device_handle, TagInfo& tag_info, bool is_mifare) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->GetTagInfo(tag_info, is_mifare); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Kernel::KReadableEvent& DeviceManager::AttachActivateEvent(u64 device_handle) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + GetDeviceFromHandle(device_handle, device, false); + + // TODO: Return proper error code on failure + return device->GetActivateEvent(); +} + +Kernel::KReadableEvent& DeviceManager::AttachDeactivateEvent(u64 device_handle) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + GetDeviceFromHandle(device_handle, device, false); + + // TODO: Return proper error code on failure + return device->GetDeactivateEvent(); +} + +Result DeviceManager::ReadMifare(u64 device_handle, + std::span<const MifareReadBlockParameter> read_parameters, + std::span<MifareReadBlockData> read_data) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->ReadMifare(read_parameters, read_data); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::WriteMifare(u64 device_handle, + std::span<const MifareWriteBlockParameter> write_parameters) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->WriteMifare(write_parameters); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::SendCommandByPassThrough(u64 device_handle, + const Time::Clock::TimeSpanType& timeout, + std::span<const u8> command_data, + std::span<u8> out_data) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->SendCommandByPassThrough(timeout, command_data, out_data); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::Mount(u64 device_handle, NFP::ModelType model_type, + NFP::MountTarget mount_target) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->Mount(model_type, mount_target); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::Unmount(u64 device_handle) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->Unmount(); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::OpenApplicationArea(u64 device_handle, u32 access_id) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->OpenApplicationArea(access_id); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::GetApplicationArea(u64 device_handle, std::span<u8> data) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->GetApplicationArea(data); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::SetApplicationArea(u64 device_handle, std::span<const u8> data) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->SetApplicationArea(data); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::Flush(u64 device_handle) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->Flush(); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::Restore(u64 device_handle) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->Restore(); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::CreateApplicationArea(u64 device_handle, u32 access_id, + std::span<const u8> data) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->CreateApplicationArea(access_id, data); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::GetRegisterInfo(u64 device_handle, NFP::RegisterInfo& register_info) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->GetRegisterInfo(register_info); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::GetCommonInfo(u64 device_handle, NFP::CommonInfo& common_info) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->GetCommonInfo(common_info); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::GetModelInfo(u64 device_handle, NFP::ModelInfo& model_info) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->GetModelInfo(model_info); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +u32 DeviceManager::GetApplicationAreaSize() const { + return sizeof(NFP::ApplicationArea); +} + +Result DeviceManager::RecreateApplicationArea(u64 device_handle, u32 access_id, + std::span<const u8> data) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->RecreateApplicationArea(access_id, data); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::Format(u64 device_handle) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->Format(); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::GetAdminInfo(u64 device_handle, NFP::AdminInfo& admin_info) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->GetAdminInfo(admin_info); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::GetRegisterInfoPrivate(u64 device_handle, + NFP::RegisterInfoPrivate& register_info) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->GetRegisterInfoPrivate(register_info); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::SetRegisterInfoPrivate(u64 device_handle, + const NFP::RegisterInfoPrivate& register_info) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->SetRegisterInfoPrivate(register_info); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::DeleteRegisterInfo(u64 device_handle) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->DeleteRegisterInfo(); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::DeleteApplicationArea(u64 device_handle) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->DeleteApplicationArea(); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::ExistsApplicationArea(u64 device_handle, bool& has_application_area) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->ExistsApplicationArea(has_application_area); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::GetAll(u64 device_handle, NFP::NfpData& nfp_data) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->GetAll(nfp_data); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::SetAll(u64 device_handle, const NFP::NfpData& nfp_data) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->SetAll(nfp_data); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::FlushDebug(u64 device_handle) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->FlushDebug(); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::BreakTag(u64 device_handle, NFP::BreakType break_type) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->BreakTag(break_type); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) const { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->ReadBackupData(data); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> data) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->WriteBackupData(data); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::WriteNtf(u64 device_handle, NFP::WriteType, std::span<const u8> data) { + std::scoped_lock lock{mutex}; + + std::shared_ptr<NfcDevice> device = nullptr; + auto result = GetDeviceHandle(device_handle, device); + + if (result.IsSuccess()) { + result = device->WriteNtf(data); + result = VerifyDeviceResult(device, result); + } + + return result; +} + +Result DeviceManager::GetDeviceFromHandle(u64 handle, std::shared_ptr<NfcDevice>& nfc_device, + bool check_state) const { + if (check_state) { + const Result is_parameter_set = IsNfcParameterSet(); + if (is_parameter_set.IsError()) { + return is_parameter_set; + } + const Result is_enabled = IsNfcEnabled(); + if (is_enabled.IsError()) { + return is_enabled; + } + const Result is_nfc_initialized = IsNfcInitialized(); + if (is_nfc_initialized.IsError()) { + return is_nfc_initialized; + } + } + + for (auto& device : devices) { + if (device->GetHandle() == handle) { + nfc_device = device; + return ResultSuccess; + } + } + + return ResultDeviceNotFound; +} + +std::optional<std::shared_ptr<NfcDevice>> DeviceManager::GetNfcDevice(u64 handle) { + for (auto& device : devices) { + if (device->GetHandle() == handle) { + return device; + } + } + return std::nullopt; +} + +const std::optional<std::shared_ptr<NfcDevice>> DeviceManager::GetNfcDevice(u64 handle) const { + for (auto& device : devices) { + if (device->GetHandle() == handle) { + return device; + } + } + return std::nullopt; +} + +Result DeviceManager::GetDeviceHandle(u64 handle, std::shared_ptr<NfcDevice>& device) const { + const auto result = GetDeviceFromHandle(handle, device, true); + if (result.IsError()) { + return result; + } + return CheckDeviceState(device); +} + +Result DeviceManager::VerifyDeviceResult(std::shared_ptr<NfcDevice> device, + Result operation_result) const { + if (operation_result.IsSuccess()) { + return operation_result; + } + + const Result is_parameter_set = IsNfcParameterSet(); + if (is_parameter_set.IsError()) { + return is_parameter_set; + } + const Result is_enabled = IsNfcEnabled(); + if (is_enabled.IsError()) { + return is_enabled; + } + const Result is_nfc_initialized = IsNfcInitialized(); + if (is_nfc_initialized.IsError()) { + return is_nfc_initialized; + } + const Result device_state = CheckDeviceState(device); + if (device_state.IsError()) { + return device_state; + } + + return operation_result; +} + +Result DeviceManager::CheckDeviceState(std::shared_ptr<NfcDevice> device) const { + if (device == nullptr) { + return ResultInvalidArgument; + } + + return ResultSuccess; +} + +Result DeviceManager::IsNfcEnabled() const { + // TODO: This calls nn::settings::detail::GetNfcEnableFlag + const bool is_enabled = true; + if (!is_enabled) { + return ResultNfcDisabled; + } + return ResultSuccess; +} + +Result DeviceManager::IsNfcParameterSet() const { + // TODO: This calls checks against a bool on offset 0x450 + const bool is_set = true; + if (!is_set) { + return ResultUnknown76; + } + return ResultSuccess; +} + +Result DeviceManager::IsNfcInitialized() const { + if (!is_initialized) { + return ResultNfcNotInitialized; + } + return ResultSuccess; +} + +} // namespace Service::NFC diff --git a/src/core/hle/service/nfc/common/device_manager.h b/src/core/hle/service/nfc/common/device_manager.h new file mode 100644 index 000000000..2971e280f --- /dev/null +++ b/src/core/hle/service/nfc/common/device_manager.h @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <array> +#include <memory> +#include <optional> +#include <span> + +#include "core/hid/hid_types.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/nfc/mifare_types.h" +#include "core/hle/service/nfc/nfc_types.h" +#include "core/hle/service/nfp/nfp_types.h" +#include "core/hle/service/service.h" +#include "core/hle/service/time/clock_types.h" + +namespace Service::NFC { +class NfcDevice; + +class DeviceManager { +public: + explicit DeviceManager(Core::System& system_, KernelHelpers::ServiceContext& service_context_); + ~DeviceManager(); + + // Nfc device manager + Result Initialize(); + Result Finalize(); + Result ListDevices(std::vector<u64>& nfp_devices, std::size_t max_allowed_devices) const; + DeviceState GetDeviceState(u64 device_handle) const; + Result GetNpadId(u64 device_handle, Core::HID::NpadIdType& npad_id) const; + Kernel::KReadableEvent& AttachAvailabilityChangeEvent() const; + Result StartDetection(u64 device_handle, NfcProtocol tag_protocol); + Result StopDetection(u64 device_handle); + Result GetTagInfo(u64 device_handle, NFP::TagInfo& tag_info, bool is_mifare) const; + Kernel::KReadableEvent& AttachActivateEvent(u64 device_handle) const; + Kernel::KReadableEvent& AttachDeactivateEvent(u64 device_handle) const; + Result ReadMifare(u64 device_handle, + const std::span<const MifareReadBlockParameter> read_parameters, + std::span<MifareReadBlockData> read_data); + Result WriteMifare(u64 device_handle, + std::span<const MifareWriteBlockParameter> write_parameters); + Result SendCommandByPassThrough(u64 device_handle, const Time::Clock::TimeSpanType& timeout, + std::span<const u8> command_data, std::span<u8> out_data); + + // Nfp device manager + Result Mount(u64 device_handle, NFP::ModelType model_type, NFP::MountTarget mount_target); + Result Unmount(u64 device_handle); + Result OpenApplicationArea(u64 device_handle, u32 access_id); + Result GetApplicationArea(u64 device_handle, std::span<u8> data) const; + Result SetApplicationArea(u64 device_handle, std::span<const u8> data); + Result Flush(u64 device_handle); + Result Restore(u64 device_handle); + Result CreateApplicationArea(u64 device_handle, u32 access_id, std::span<const u8> data); + Result GetRegisterInfo(u64 device_handle, NFP::RegisterInfo& register_info) const; + Result GetCommonInfo(u64 device_handle, NFP::CommonInfo& common_info) const; + Result GetModelInfo(u64 device_handle, NFP::ModelInfo& model_info) const; + u32 GetApplicationAreaSize() const; + Result RecreateApplicationArea(u64 device_handle, u32 access_id, std::span<const u8> data); + Result Format(u64 device_handle); + Result GetAdminInfo(u64 device_handle, NFP::AdminInfo& admin_info) const; + Result GetRegisterInfoPrivate(u64 device_handle, NFP::RegisterInfoPrivate& register_info) const; + Result SetRegisterInfoPrivate(u64 device_handle, const NFP::RegisterInfoPrivate& register_info); + Result DeleteRegisterInfo(u64 device_handle); + Result DeleteApplicationArea(u64 device_handle); + Result ExistsApplicationArea(u64 device_handle, bool& has_application_area) const; + Result GetAll(u64 device_handle, NFP::NfpData& nfp_data) const; + Result SetAll(u64 device_handle, const NFP::NfpData& nfp_data); + Result FlushDebug(u64 device_handle); + Result BreakTag(u64 device_handle, NFP::BreakType break_type); + Result ReadBackupData(u64 device_handle, std::span<u8> data) const; + Result WriteBackupData(u64 device_handle, std::span<const u8> data); + Result WriteNtf(u64 device_handle, NFP::WriteType, std::span<const u8> data); + +private: + Result IsNfcEnabled() const; + Result IsNfcParameterSet() const; + Result IsNfcInitialized() const; + + Result GetDeviceFromHandle(u64 handle, std::shared_ptr<NfcDevice>& device, + bool check_state) const; + + Result GetDeviceHandle(u64 handle, std::shared_ptr<NfcDevice>& device) const; + Result VerifyDeviceResult(std::shared_ptr<NfcDevice> device, Result operation_result) const; + Result CheckDeviceState(std::shared_ptr<NfcDevice> device) const; + + std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle); + const std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle) const; + + bool is_initialized = false; + mutable std::mutex mutex; + std::array<std::shared_ptr<NfcDevice>, 10> devices{}; + + Core::System& system; + KernelHelpers::ServiceContext service_context; + Kernel::KEvent* availability_change_event; +}; + +} // namespace Service::NFC diff --git a/src/core/hle/service/nfc/mifare_result.h b/src/core/hle/service/nfc/mifare_result.h new file mode 100644 index 000000000..4b60048a5 --- /dev/null +++ b/src/core/hle/service/nfc/mifare_result.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "core/hle/result.h" + +namespace Service::NFC::Mifare { + +constexpr Result ResultDeviceNotFound(ErrorModule::NFCMifare, 64); +constexpr Result ResultInvalidArgument(ErrorModule::NFCMifare, 65); +constexpr Result ResultWrongDeviceState(ErrorModule::NFCMifare, 73); +constexpr Result ResultNfcDisabled(ErrorModule::NFCMifare, 80); +constexpr Result ResultTagRemoved(ErrorModule::NFCMifare, 97); +constexpr Result ResultReadError(ErrorModule::NFCMifare, 288); + +} // namespace Service::NFC::Mifare diff --git a/src/core/hle/service/nfc/mifare_types.h b/src/core/hle/service/nfc/mifare_types.h new file mode 100644 index 000000000..75b59f021 --- /dev/null +++ b/src/core/hle/service/nfc/mifare_types.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <array> + +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Service::NFC { + +enum class MifareCmd : u8 { + AuthA = 0x60, + AuthB = 0x61, + Read = 0x30, + Write = 0xA0, + Transfer = 0xB0, + Decrement = 0xC0, + Increment = 0xC1, + Store = 0xC2 +}; + +using DataBlock = std::array<u8, 0x10>; +using KeyData = std::array<u8, 0x6>; + +struct SectorKey { + MifareCmd command; + u8 unknown; // Usually 1 + INSERT_PADDING_BYTES(0x6); + KeyData sector_key; + INSERT_PADDING_BYTES(0x2); +}; +static_assert(sizeof(SectorKey) == 0x10, "SectorKey is an invalid size"); + +// This is nn::nfc::MifareReadBlockParameter +struct MifareReadBlockParameter { + u8 sector_number; + INSERT_PADDING_BYTES(0x7); + SectorKey sector_key; +}; +static_assert(sizeof(MifareReadBlockParameter) == 0x18, + "MifareReadBlockParameter is an invalid size"); + +// This is nn::nfc::MifareReadBlockData +struct MifareReadBlockData { + DataBlock data; + u8 sector_number; + INSERT_PADDING_BYTES(0x7); +}; +static_assert(sizeof(MifareReadBlockData) == 0x18, "MifareReadBlockData is an invalid size"); + +// This is nn::nfc::MifareWriteBlockParameter +struct MifareWriteBlockParameter { + DataBlock data; + u8 sector_number; + INSERT_PADDING_BYTES(0x7); + SectorKey sector_key; +}; +static_assert(sizeof(MifareWriteBlockParameter) == 0x28, + "MifareWriteBlockParameter is an invalid size"); + +} // namespace Service::NFC diff --git a/src/core/hle/service/nfc/mifare_user.cpp b/src/core/hle/service/nfc/mifare_user.cpp deleted file mode 100644 index e0bbd46e1..000000000 --- a/src/core/hle/service/nfc/mifare_user.cpp +++ /dev/null @@ -1,400 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/logging/log.h" -#include "core/core.h" -#include "core/hid/hid_types.h" -#include "core/hle/kernel/k_event.h" -#include "core/hle/service/ipc_helpers.h" -#include "core/hle/service/nfc/mifare_user.h" -#include "core/hle/service/nfc/nfc_device.h" -#include "core/hle/service/nfc/nfc_result.h" - -namespace Service::NFC { - -MFIUser::MFIUser(Core::System& system_) - : ServiceFramework{system_, "NFC::MFIUser"}, service_context{system_, service_name} { - static const FunctionInfo functions[] = { - {0, &MFIUser::Initialize, "Initialize"}, - {1, &MFIUser::Finalize, "Finalize"}, - {2, &MFIUser::ListDevices, "ListDevices"}, - {3, &MFIUser::StartDetection, "StartDetection"}, - {4, &MFIUser::StopDetection, "StopDetection"}, - {5, &MFIUser::Read, "Read"}, - {6, &MFIUser::Write, "Write"}, - {7, &MFIUser::GetTagInfo, "GetTagInfo"}, - {8, &MFIUser::GetActivateEventHandle, "GetActivateEventHandle"}, - {9, &MFIUser::GetDeactivateEventHandle, "GetDeactivateEventHandle"}, - {10, &MFIUser::GetState, "GetState"}, - {11, &MFIUser::GetDeviceState, "GetDeviceState"}, - {12, &MFIUser::GetNpadId, "GetNpadId"}, - {13, &MFIUser::GetAvailabilityChangeEventHandle, "GetAvailabilityChangeEventHandle"}, - }; - RegisterHandlers(functions); - - availability_change_event = service_context.CreateEvent("MFIUser:AvailabilityChangeEvent"); - - for (u32 device_index = 0; device_index < 10; device_index++) { - devices[device_index] = - std::make_shared<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system, - service_context, availability_change_event); - } -} - -MFIUser ::~MFIUser() { - availability_change_event->Close(); -} - -void MFIUser::Initialize(HLERequestContext& ctx) { - LOG_INFO(Service_NFC, "called"); - - state = State::Initialized; - - for (auto& device : devices) { - device->Initialize(); - } - - IPC::ResponseBuilder rb{ctx, 2, 0}; - rb.Push(ResultSuccess); -} - -void MFIUser::Finalize(HLERequestContext& ctx) { - LOG_INFO(Service_NFC, "called"); - - state = State::NonInitialized; - - for (auto& device : devices) { - device->Finalize(); - } - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} - -void MFIUser::ListDevices(HLERequestContext& ctx) { - LOG_DEBUG(Service_NFC, "called"); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareNfcDisabled); - return; - } - - if (!ctx.CanWriteBuffer()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareInvalidArgument); - return; - } - - if (ctx.GetWriteBufferSize() == 0) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareInvalidArgument); - return; - } - - std::vector<u64> nfp_devices; - const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>(); - - for (const auto& device : devices) { - if (nfp_devices.size() >= max_allowed_devices) { - continue; - } - if (device->GetCurrentState() != NFP::DeviceState::Unavailable) { - nfp_devices.push_back(device->GetHandle()); - } - } - - if (nfp_devices.empty()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareDeviceNotFound); - return; - } - - ctx.WriteBuffer(nfp_devices); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(static_cast<s32>(nfp_devices.size())); -} - -void MFIUser::StartDetection(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareNfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareDeviceNotFound); - return; - } - - const auto result = device.value()->StartDetection(NFP::TagProtocol::All); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); -} - -void MFIUser::StopDetection(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareNfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareDeviceNotFound); - return; - } - - const auto result = device.value()->StopDetection(); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); -} - -void MFIUser::Read(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - const auto buffer{ctx.ReadBuffer()}; - const auto number_of_commands{ctx.GetReadBufferNumElements<NFP::MifareReadBlockParameter>()}; - std::vector<NFP::MifareReadBlockParameter> read_commands(number_of_commands); - - memcpy(read_commands.data(), buffer.data(), - number_of_commands * sizeof(NFP::MifareReadBlockParameter)); - - LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, read_commands_size={}", - device_handle, number_of_commands); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareNfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareDeviceNotFound); - return; - } - - Result result = ResultSuccess; - std::vector<NFP::MifareReadBlockData> out_data(number_of_commands); - for (std::size_t i = 0; i < number_of_commands; i++) { - result = device.value()->MifareRead(read_commands[i], out_data[i]); - if (result.IsError()) { - break; - } - } - - ctx.WriteBuffer(out_data); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); -} - -void MFIUser::Write(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - const auto buffer{ctx.ReadBuffer()}; - const auto number_of_commands{ctx.GetReadBufferNumElements<NFP::MifareWriteBlockParameter>()}; - std::vector<NFP::MifareWriteBlockParameter> write_commands(number_of_commands); - - memcpy(write_commands.data(), buffer.data(), - number_of_commands * sizeof(NFP::MifareWriteBlockParameter)); - - LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, write_commands_size={}", - device_handle, number_of_commands); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareNfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareDeviceNotFound); - return; - } - - Result result = ResultSuccess; - std::vector<NFP::MifareReadBlockData> out_data(number_of_commands); - for (std::size_t i = 0; i < number_of_commands; i++) { - result = device.value()->MifareWrite(write_commands[i]); - if (result.IsError()) { - break; - } - } - - if (result.IsSuccess()) { - result = device.value()->Flush(); - } - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); -} - -void MFIUser::GetTagInfo(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareNfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareDeviceNotFound); - return; - } - - NFP::TagInfo tag_info{}; - const auto result = device.value()->GetTagInfo(tag_info, true); - ctx.WriteBuffer(tag_info); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); -} - -void MFIUser::GetActivateEventHandle(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareNfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareDeviceNotFound); - return; - } - - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(device.value()->GetActivateEvent()); -} - -void MFIUser::GetDeactivateEventHandle(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareNfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareDeviceNotFound); - return; - } - - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(device.value()->GetDeactivateEvent()); -} - -void MFIUser::GetState(HLERequestContext& ctx) { - LOG_DEBUG(Service_NFC, "called"); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(state); -} - -void MFIUser::GetDeviceState(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareDeviceNotFound); - return; - } - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(device.value()->GetCurrentState()); -} - -void MFIUser::GetNpadId(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareNfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareDeviceNotFound); - return; - } - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(device.value()->GetNpadId()); -} - -void MFIUser::GetAvailabilityChangeEventHandle(HLERequestContext& ctx) { - LOG_INFO(Service_NFC, "called"); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(MifareNfcDisabled); - return; - } - - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(availability_change_event->GetReadableEvent()); -} - -std::optional<std::shared_ptr<NfcDevice>> MFIUser::GetNfcDevice(u64 handle) { - for (auto& device : devices) { - if (device->GetHandle() == handle) { - return device; - } - } - return std::nullopt; -} - -} // namespace Service::NFC diff --git a/src/core/hle/service/nfc/mifare_user.h b/src/core/hle/service/nfc/mifare_user.h deleted file mode 100644 index 9701f1d7f..000000000 --- a/src/core/hle/service/nfc/mifare_user.h +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <array> -#include <memory> -#include <optional> - -#include "core/hle/service/kernel_helpers.h" -#include "core/hle/service/service.h" - -namespace Service::NFC { -class NfcDevice; - -class MFIUser final : public ServiceFramework<MFIUser> { -public: - explicit MFIUser(Core::System& system_); - ~MFIUser(); - -private: - enum class State : u32 { - NonInitialized, - Initialized, - }; - - void Initialize(HLERequestContext& ctx); - void Finalize(HLERequestContext& ctx); - void ListDevices(HLERequestContext& ctx); - void StartDetection(HLERequestContext& ctx); - void StopDetection(HLERequestContext& ctx); - void Read(HLERequestContext& ctx); - void Write(HLERequestContext& ctx); - void GetTagInfo(HLERequestContext& ctx); - void GetActivateEventHandle(HLERequestContext& ctx); - void GetDeactivateEventHandle(HLERequestContext& ctx); - void GetState(HLERequestContext& ctx); - void GetDeviceState(HLERequestContext& ctx); - void GetNpadId(HLERequestContext& ctx); - void GetAvailabilityChangeEventHandle(HLERequestContext& ctx); - - std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle); - - KernelHelpers::ServiceContext service_context; - - std::array<std::shared_ptr<NfcDevice>, 10> devices{}; - - State state{State::NonInitialized}; - Kernel::KEvent* availability_change_event; -}; - -} // namespace Service::NFC diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp index 6595e34ed..30ae989b9 100644 --- a/src/core/hle/service/nfc/nfc.cpp +++ b/src/core/hle/service/nfc/nfc.cpp @@ -6,14 +6,115 @@ #include "common/logging/log.h" #include "common/settings.h" #include "core/hle/service/ipc_helpers.h" -#include "core/hle/service/nfc/mifare_user.h" #include "core/hle/service/nfc/nfc.h" -#include "core/hle/service/nfc/nfc_user.h" +#include "core/hle/service/nfc/nfc_interface.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" namespace Service::NFC { +class IUser final : public NfcInterface { +public: + explicit IUser(Core::System& system_) : NfcInterface(system_, "NFC::IUser", BackendType::Nfc) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &NfcInterface::Initialize, "InitializeOld"}, + {1, &NfcInterface::Finalize, "FinalizeOld"}, + {2, &NfcInterface::GetState, "GetStateOld"}, + {3, &NfcInterface::IsNfcEnabled, "IsNfcEnabledOld"}, + {400, &NfcInterface::Initialize, "Initialize"}, + {401, &NfcInterface::Finalize, "Finalize"}, + {402, &NfcInterface::GetState, "GetState"}, + {403, &NfcInterface::IsNfcEnabled, "IsNfcEnabled"}, + {404, &NfcInterface::ListDevices, "ListDevices"}, + {405, &NfcInterface::GetDeviceState, "GetDeviceState"}, + {406, &NfcInterface::GetNpadId, "GetNpadId"}, + {407, &NfcInterface::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, + {408, &NfcInterface::StartDetection, "StartDetection"}, + {409, &NfcInterface::StopDetection, "StopDetection"}, + {410, &NfcInterface::GetTagInfo, "GetTagInfo"}, + {411, &NfcInterface::AttachActivateEvent, "AttachActivateEvent"}, + {412, &NfcInterface::AttachDeactivateEvent, "AttachDeactivateEvent"}, + {1000, &NfcInterface::ReadMifare, "ReadMifare"}, + {1001, &NfcInterface::WriteMifare ,"WriteMifare"}, + {1300, &NfcInterface::SendCommandByPassThrough, "SendCommandByPassThrough"}, + {1301, nullptr, "KeepPassThroughSession"}, + {1302, nullptr, "ReleasePassThroughSession"}, + }; + // clang-format on + + RegisterHandlers(functions); + } +}; + +class ISystem final : public NfcInterface { +public: + explicit ISystem(Core::System& system_) + : NfcInterface{system_, "NFC::ISystem", BackendType::Nfc} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &NfcInterface::Initialize, "InitializeOld"}, + {1, &NfcInterface::Finalize, "FinalizeOld"}, + {2, &NfcInterface::GetState, "GetStateOld"}, + {3, &NfcInterface::IsNfcEnabled, "IsNfcEnabledOld"}, + {100, nullptr, "SetNfcEnabledOld"}, + {400, &NfcInterface::Initialize, "Initialize"}, + {401, &NfcInterface::Finalize, "Finalize"}, + {402, &NfcInterface::GetState, "GetState"}, + {403, &NfcInterface::IsNfcEnabled, "IsNfcEnabled"}, + {404, &NfcInterface::ListDevices, "ListDevices"}, + {405, &NfcInterface::GetDeviceState, "GetDeviceState"}, + {406, &NfcInterface::GetNpadId, "GetNpadId"}, + {407, &NfcInterface::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, + {408, &NfcInterface::StartDetection, "StartDetection"}, + {409, &NfcInterface::StopDetection, "StopDetection"}, + {410, &NfcInterface::GetTagInfo, "GetTagInfo"}, + {411, &NfcInterface::AttachActivateEvent, "AttachActivateEvent"}, + {412, &NfcInterface::AttachDeactivateEvent, "AttachDeactivateEvent"}, + {500, nullptr, "SetNfcEnabled"}, + {510, nullptr, "OutputTestWave"}, + {1000, &NfcInterface::ReadMifare, "ReadMifare"}, + {1001, &NfcInterface::WriteMifare, "WriteMifare"}, + {1300, &NfcInterface::SendCommandByPassThrough, "SendCommandByPassThrough"}, + {1301, nullptr, "KeepPassThroughSession"}, + {1302, nullptr, "ReleasePassThroughSession"}, + }; + // clang-format on + + RegisterHandlers(functions); + } +}; + +// MFInterface has an unique interface but it's identical to NfcInterface so we can keep the code +// simpler +using MFInterface = NfcInterface; +class MFIUser final : public MFInterface { +public: + explicit MFIUser(Core::System& system_) + : MFInterface{system_, "NFC::MFInterface", BackendType::Mifare} { + // clang-format off + static const FunctionInfoTyped<MFIUser> functions[] = { + {0, &MFIUser::Initialize, "Initialize"}, + {1, &MFIUser::Finalize, "Finalize"}, + {2, &MFIUser::ListDevices, "ListDevices"}, + {3, &MFIUser::StartDetection, "StartDetection"}, + {4, &MFIUser::StopDetection, "StopDetection"}, + {5, &MFIUser::ReadMifare, "Read"}, + {6, &MFIUser::WriteMifare, "Write"}, + {7, &MFIUser::GetTagInfo, "GetTagInfo"}, + {8, &MFIUser::AttachActivateEvent, "GetActivateEventHandle"}, + {9, &MFIUser::AttachDeactivateEvent, "GetDeactivateEventHandle"}, + {10, &MFIUser::GetState, "GetState"}, + {11, &MFIUser::GetDeviceState, "GetDeviceState"}, + {12, &MFIUser::GetNpadId, "GetNpadId"}, + {13, &MFIUser::AttachAvailabilityChangeEvent, "GetAvailabilityChangeEventHandle"}, + }; + // clang-format on + + RegisterHandlers(functions); + } +}; + class IAm final : public ServiceFramework<IAm> { public: explicit IAm(Core::System& system_) : ServiceFramework{system_, "NFC::IAm"} { @@ -34,7 +135,7 @@ public: explicit NFC_AM(Core::System& system_) : ServiceFramework{system_, "nfc:am"} { // clang-format off static const FunctionInfo functions[] = { - {0, &NFC_AM::CreateAmInterface, "CreateAmInterface"}, + {0, &NFC_AM::CreateAmNfcInterface, "CreateAmNfcInterface"}, }; // clang-format on @@ -42,7 +143,7 @@ public: } private: - void CreateAmInterface(HLERequestContext& ctx) { + void CreateAmNfcInterface(HLERequestContext& ctx) { LOG_DEBUG(Service_NFC, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; @@ -56,7 +157,7 @@ public: explicit NFC_MF_U(Core::System& system_) : ServiceFramework{system_, "nfc:mf:u"} { // clang-format off static const FunctionInfo functions[] = { - {0, &NFC_MF_U::CreateUserInterface, "CreateUserInterface"}, + {0, &NFC_MF_U::CreateUserNfcInterface, "CreateUserNfcInterface"}, }; // clang-format on @@ -64,7 +165,7 @@ public: } private: - void CreateUserInterface(HLERequestContext& ctx) { + void CreateUserNfcInterface(HLERequestContext& ctx) { LOG_DEBUG(Service_NFC, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; @@ -78,7 +179,7 @@ public: explicit NFC_U(Core::System& system_) : ServiceFramework{system_, "nfc:user"} { // clang-format off static const FunctionInfo functions[] = { - {0, &NFC_U::CreateUserInterface, "CreateUserInterface"}, + {0, &NFC_U::CreateUserNfcInterface, "CreateUserNfcInterface"}, }; // clang-format on @@ -86,7 +187,7 @@ public: } private: - void CreateUserInterface(HLERequestContext& ctx) { + void CreateUserNfcInterface(HLERequestContext& ctx) { LOG_DEBUG(Service_NFC, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; @@ -95,49 +196,12 @@ private: } }; -class ISystem final : public ServiceFramework<ISystem> { -public: - explicit ISystem(Core::System& system_) : ServiceFramework{system_, "ISystem"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, nullptr, "Initialize"}, - {1, nullptr, "Finalize"}, - {2, nullptr, "GetStateOld"}, - {3, nullptr, "IsNfcEnabledOld"}, - {100, nullptr, "SetNfcEnabledOld"}, - {400, nullptr, "InitializeSystem"}, - {401, nullptr, "FinalizeSystem"}, - {402, nullptr, "GetState"}, - {403, nullptr, "IsNfcEnabled"}, - {404, nullptr, "ListDevices"}, - {405, nullptr, "GetDeviceState"}, - {406, nullptr, "GetNpadId"}, - {407, nullptr, "AttachAvailabilityChangeEvent"}, - {408, nullptr, "StartDetection"}, - {409, nullptr, "StopDetection"}, - {410, nullptr, "GetTagInfo"}, - {411, nullptr, "AttachActivateEvent"}, - {412, nullptr, "AttachDeactivateEvent"}, - {500, nullptr, "SetNfcEnabled"}, - {510, nullptr, "OutputTestWave"}, - {1000, nullptr, "ReadMifare"}, - {1001, nullptr, "WriteMifare"}, - {1300, nullptr, "SendCommandByPassThrough"}, - {1301, nullptr, "KeepPassThroughSession"}, - {1302, nullptr, "ReleasePassThroughSession"}, - }; - // clang-format on - - RegisterHandlers(functions); - } -}; - class NFC_SYS final : public ServiceFramework<NFC_SYS> { public: explicit NFC_SYS(Core::System& system_) : ServiceFramework{system_, "nfc:sys"} { // clang-format off static const FunctionInfo functions[] = { - {0, &NFC_SYS::CreateSystemInterface, "CreateSystemInterface"}, + {0, &NFC_SYS::CreateSystemNfcInterface, "CreateSystemNfcInterface"}, }; // clang-format on @@ -145,7 +209,7 @@ public: } private: - void CreateSystemInterface(HLERequestContext& ctx) { + void CreateSystemNfcInterface(HLERequestContext& ctx) { LOG_DEBUG(Service_NFC, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; @@ -161,6 +225,7 @@ void LoopProcess(Core::System& system) { server_manager->RegisterNamedService("nfc:mf:u", std::make_shared<NFC_MF_U>(system)); server_manager->RegisterNamedService("nfc:user", std::make_shared<NFC_U>(system)); server_manager->RegisterNamedService("nfc:sys", std::make_shared<NFC_SYS>(system)); + ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/nfc/nfc_device.cpp b/src/core/hle/service/nfc/nfc_device.cpp deleted file mode 100644 index c7db74d14..000000000 --- a/src/core/hle/service/nfc/nfc_device.cpp +++ /dev/null @@ -1,288 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/input.h" -#include "common/logging/log.h" -#include "core/core.h" -#include "core/hid/emulated_controller.h" -#include "core/hid/hid_core.h" -#include "core/hid/hid_types.h" -#include "core/hle/kernel/k_event.h" -#include "core/hle/service/ipc_helpers.h" -#include "core/hle/service/nfc/nfc_device.h" -#include "core/hle/service/nfc/nfc_result.h" -#include "core/hle/service/nfc/nfc_user.h" - -namespace Service::NFC { -NfcDevice::NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, - KernelHelpers::ServiceContext& service_context_, - Kernel::KEvent* availability_change_event_) - : npad_id{npad_id_}, system{system_}, service_context{service_context_}, - availability_change_event{availability_change_event_} { - activate_event = service_context.CreateEvent("IUser:NFCActivateEvent"); - deactivate_event = service_context.CreateEvent("IUser:NFCDeactivateEvent"); - npad_device = system.HIDCore().GetEmulatedController(npad_id); - - Core::HID::ControllerUpdateCallback engine_callback{ - .on_change = [this](Core::HID::ControllerTriggerType type) { NpadUpdate(type); }, - .is_npad_service = false, - }; - is_controller_set = true; - callback_key = npad_device->SetCallback(engine_callback); -} - -NfcDevice::~NfcDevice() { - activate_event->Close(); - deactivate_event->Close(); - if (!is_controller_set) { - return; - } - npad_device->DeleteCallback(callback_key); - is_controller_set = false; -}; - -void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { - if (!is_initalized) { - return; - } - - if (type == Core::HID::ControllerTriggerType::Connected) { - Initialize(); - availability_change_event->Signal(); - return; - } - - if (type == Core::HID::ControllerTriggerType::Disconnected) { - device_state = NFP::DeviceState::Unavailable; - availability_change_event->Signal(); - return; - } - - if (type != Core::HID::ControllerTriggerType::Nfc) { - return; - } - - if (!npad_device->IsConnected()) { - return; - } - - const auto nfc_status = npad_device->GetNfc(); - switch (nfc_status.state) { - case Common::Input::NfcState::NewAmiibo: - LoadNfcTag(nfc_status.data); - break; - case Common::Input::NfcState::AmiiboRemoved: - if (device_state != NFP::DeviceState::SearchingForTag) { - CloseNfcTag(); - } - break; - default: - break; - } -} - -bool NfcDevice::LoadNfcTag(std::span<const u8> data) { - if (device_state != NFP::DeviceState::SearchingForTag) { - LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state); - return false; - } - - if (data.size() < sizeof(NFP::EncryptedNTAG215File)) { - LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size()); - return false; - } - - tag_data.resize(data.size()); - memcpy(tag_data.data(), data.data(), data.size()); - memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); - - device_state = NFP::DeviceState::TagFound; - deactivate_event->GetReadableEvent().Clear(); - activate_event->Signal(); - return true; -} - -void NfcDevice::CloseNfcTag() { - LOG_INFO(Service_NFC, "Remove nfc tag"); - - device_state = NFP::DeviceState::TagRemoved; - encrypted_tag_data = {}; - activate_event->GetReadableEvent().Clear(); - deactivate_event->Signal(); -} - -Kernel::KReadableEvent& NfcDevice::GetActivateEvent() const { - return activate_event->GetReadableEvent(); -} - -Kernel::KReadableEvent& NfcDevice::GetDeactivateEvent() const { - return deactivate_event->GetReadableEvent(); -} - -void NfcDevice::Initialize() { - device_state = - npad_device->HasNfc() ? NFP::DeviceState::Initialized : NFP::DeviceState::Unavailable; - encrypted_tag_data = {}; - is_initalized = true; -} - -void NfcDevice::Finalize() { - if (device_state == NFP::DeviceState::SearchingForTag || - device_state == NFP::DeviceState::TagRemoved) { - StopDetection(); - } - device_state = NFP::DeviceState::Unavailable; - is_initalized = false; -} - -Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) { - if (device_state != NFP::DeviceState::Initialized && - device_state != NFP::DeviceState::TagRemoved) { - LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); - return WrongDeviceState; - } - - if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, - Common::Input::PollingMode::NFC) != - Common::Input::DriverResult::Success) { - LOG_ERROR(Service_NFC, "Nfc not supported"); - return NfcDisabled; - } - - device_state = NFP::DeviceState::SearchingForTag; - allowed_protocols = allowed_protocol; - return ResultSuccess; -} - -Result NfcDevice::StopDetection() { - npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, - Common::Input::PollingMode::Active); - - if (device_state == NFP::DeviceState::Initialized) { - return ResultSuccess; - } - - if (device_state == NFP::DeviceState::TagFound || - device_state == NFP::DeviceState::TagMounted) { - CloseNfcTag(); - return ResultSuccess; - } - if (device_state == NFP::DeviceState::SearchingForTag || - device_state == NFP::DeviceState::TagRemoved) { - device_state = NFP::DeviceState::Initialized; - return ResultSuccess; - } - - LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); - return WrongDeviceState; -} - -Result NfcDevice::Flush() { - if (device_state != NFP::DeviceState::TagFound && - device_state != NFP::DeviceState::TagMounted) { - LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); - if (device_state == NFP::DeviceState::TagRemoved) { - return TagRemoved; - } - return WrongDeviceState; - } - - if (!npad_device->WriteNfc(tag_data)) { - LOG_ERROR(Service_NFP, "Error writing to file"); - return MifareReadError; - } - - return ResultSuccess; -} - -Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const { - if (device_state != NFP::DeviceState::TagFound && - device_state != NFP::DeviceState::TagMounted) { - LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); - if (device_state == NFP::DeviceState::TagRemoved) { - return TagRemoved; - } - return WrongDeviceState; - } - - if (is_mifare) { - tag_info = { - .uuid = encrypted_tag_data.uuid.uid, - .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()), - .protocol = NFP::TagProtocol::TypeA, - .tag_type = NFP::TagType::Type4, - }; - return ResultSuccess; - } - - // Protocol and tag type may change here - tag_info = { - .uuid = encrypted_tag_data.uuid.uid, - .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()), - .protocol = NFP::TagProtocol::TypeA, - .tag_type = NFP::TagType::Type2, - }; - - return ResultSuccess; -} - -Result NfcDevice::MifareRead(const NFP::MifareReadBlockParameter& parameter, - NFP::MifareReadBlockData& read_block_data) { - const std::size_t sector_index = parameter.sector_number * sizeof(NFP::DataBlock); - read_block_data.sector_number = parameter.sector_number; - - if (device_state != NFP::DeviceState::TagFound && - device_state != NFP::DeviceState::TagMounted) { - LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); - if (device_state == NFP::DeviceState::TagRemoved) { - return TagRemoved; - } - return WrongDeviceState; - } - - if (tag_data.size() < sector_index + sizeof(NFP::DataBlock)) { - return MifareReadError; - } - - // TODO: Use parameter.sector_key to read encrypted data - memcpy(read_block_data.data.data(), tag_data.data() + sector_index, sizeof(NFP::DataBlock)); - - return ResultSuccess; -} - -Result NfcDevice::MifareWrite(const NFP::MifareWriteBlockParameter& parameter) { - const std::size_t sector_index = parameter.sector_number * sizeof(NFP::DataBlock); - - if (device_state != NFP::DeviceState::TagFound && - device_state != NFP::DeviceState::TagMounted) { - LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); - if (device_state == NFP::DeviceState::TagRemoved) { - return TagRemoved; - } - return WrongDeviceState; - } - - if (tag_data.size() < sector_index + sizeof(NFP::DataBlock)) { - return MifareReadError; - } - - // TODO: Use parameter.sector_key to encrypt the data - memcpy(tag_data.data() + sector_index, parameter.data.data(), sizeof(NFP::DataBlock)); - - return ResultSuccess; -} - -u64 NfcDevice::GetHandle() const { - // Generate a handle based of the npad id - return static_cast<u64>(npad_id); -} - -NFP::DeviceState NfcDevice::GetCurrentState() const { - return device_state; -} - -Core::HID::NpadIdType NfcDevice::GetNpadId() const { - return npad_id; -} - -} // namespace Service::NFC diff --git a/src/core/hle/service/nfc/nfc_device.h b/src/core/hle/service/nfc/nfc_device.h deleted file mode 100644 index ea63f0537..000000000 --- a/src/core/hle/service/nfc/nfc_device.h +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/common_types.h" -#include "core/hle/service/kernel_helpers.h" -#include "core/hle/service/nfp/nfp_types.h" -#include "core/hle/service/service.h" - -namespace Kernel { -class KEvent; -class KReadableEvent; -} // namespace Kernel - -namespace Core { -class System; -} // namespace Core - -namespace Core::HID { -class EmulatedController; -enum class ControllerTriggerType; -enum class NpadIdType : u32; -} // namespace Core::HID - -namespace Service::NFC { -class NfcDevice { -public: - NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, - KernelHelpers::ServiceContext& service_context_, - Kernel::KEvent* availability_change_event_); - ~NfcDevice(); - - void Initialize(); - void Finalize(); - - Result StartDetection(NFP::TagProtocol allowed_protocol); - Result StopDetection(); - Result Flush(); - - Result GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const; - - Result MifareRead(const NFP::MifareReadBlockParameter& parameter, - NFP::MifareReadBlockData& read_block_data); - - Result MifareWrite(const NFP::MifareWriteBlockParameter& parameter); - - u64 GetHandle() const; - NFP::DeviceState GetCurrentState() const; - Core::HID::NpadIdType GetNpadId() const; - - Kernel::KReadableEvent& GetActivateEvent() const; - Kernel::KReadableEvent& GetDeactivateEvent() const; - -private: - void NpadUpdate(Core::HID::ControllerTriggerType type); - bool LoadNfcTag(std::span<const u8> data); - void CloseNfcTag(); - - bool is_controller_set{}; - int callback_key; - const Core::HID::NpadIdType npad_id; - Core::System& system; - Core::HID::EmulatedController* npad_device = nullptr; - KernelHelpers::ServiceContext& service_context; - Kernel::KEvent* activate_event = nullptr; - Kernel::KEvent* deactivate_event = nullptr; - Kernel::KEvent* availability_change_event = nullptr; - - bool is_initalized{}; - NFP::TagProtocol allowed_protocols{}; - NFP::DeviceState device_state{NFP::DeviceState::Unavailable}; - - NFP::EncryptedNTAG215File encrypted_tag_data{}; - std::vector<u8> tag_data{}; -}; - -} // namespace Service::NFC diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp new file mode 100644 index 000000000..0fa29d398 --- /dev/null +++ b/src/core/hle/service/nfc/nfc_interface.cpp @@ -0,0 +1,382 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "core/core.h" +#include "core/hid/hid_types.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/nfc/common/device.h" +#include "core/hle/service/nfc/common/device_manager.h" +#include "core/hle/service/nfc/mifare_result.h" +#include "core/hle/service/nfc/mifare_types.h" +#include "core/hle/service/nfc/nfc_interface.h" +#include "core/hle/service/nfc/nfc_result.h" +#include "core/hle/service/nfc/nfc_types.h" +#include "core/hle/service/nfp/nfp_result.h" +#include "core/hle/service/time/clock_types.h" + +namespace Service::NFC { + +NfcInterface::NfcInterface(Core::System& system_, const char* name, BackendType service_backend) + : ServiceFramework{system_, name}, service_context{system_, service_name}, + backend_type{service_backend} {} + +NfcInterface ::~NfcInterface() = default; + +void NfcInterface::Initialize(HLERequestContext& ctx) { + LOG_INFO(Service_NFC, "called"); + + auto manager = GetManager(); + auto result = manager->Initialize(); + + if (result.IsSuccess()) { + state = State::Initialized; + } else { + manager->Finalize(); + } + + IPC::ResponseBuilder rb{ctx, 2, 0}; + rb.Push(result); +} + +void NfcInterface::Finalize(HLERequestContext& ctx) { + LOG_INFO(Service_NFC, "called"); + + if (state != State::NonInitialized) { + if (GetBackendType() != BackendType::None) { + GetManager()->Finalize(); + } + device_manager = nullptr; + state = State::NonInitialized; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void NfcInterface::GetState(HLERequestContext& ctx) { + LOG_DEBUG(Service_NFC, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(state); +} + +void NfcInterface::IsNfcEnabled(HLERequestContext& ctx) { + LOG_DEBUG(Service_NFC, "called"); + + // TODO: This calls nn::settings::detail::GetNfcEnableFlag + const bool is_enabled = true; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(is_enabled); +} + +void NfcInterface::ListDevices(HLERequestContext& ctx) { + std::vector<u64> nfp_devices; + const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>(); + LOG_DEBUG(Service_NFC, "called"); + + auto result = GetManager()->ListDevices(nfp_devices, max_allowed_devices); + result = TranslateResultToServiceError(result); + + if (result.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + ctx.WriteBuffer(nfp_devices); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast<s32>(nfp_devices.size())); +} + +void NfcInterface::GetDeviceState(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto device_handle{rp.Pop<u64>()}; + LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); + + const auto device_state = GetManager()->GetDeviceState(device_handle); + + if (device_state > DeviceState::Finalized) { + ASSERT_MSG(false, "Invalid device state"); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(device_state); +} + +void NfcInterface::GetNpadId(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto device_handle{rp.Pop<u64>()}; + LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); + + Core::HID::NpadIdType npad_id{}; + auto result = GetManager()->GetNpadId(device_handle, npad_id); + result = TranslateResultToServiceError(result); + + if (result.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(npad_id); +} + +void NfcInterface::AttachAvailabilityChangeEvent(HLERequestContext& ctx) { + LOG_INFO(Service_NFC, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(GetManager()->AttachAvailabilityChangeEvent()); +} + +void NfcInterface::StartDetection(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto device_handle{rp.Pop<u64>()}; + const auto tag_protocol{rp.PopEnum<NfcProtocol>()}; + LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol); + + auto result = GetManager()->StartDetection(device_handle, tag_protocol); + result = TranslateResultToServiceError(result); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); +} + +void NfcInterface::StopDetection(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto device_handle{rp.Pop<u64>()}; + LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); + + auto result = GetManager()->StopDetection(device_handle); + result = TranslateResultToServiceError(result); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); +} + +void NfcInterface::GetTagInfo(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto device_handle{rp.Pop<u64>()}; + LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); + + TagInfo tag_info{}; + auto result = + GetManager()->GetTagInfo(device_handle, tag_info, backend_type == BackendType::Mifare); + result = TranslateResultToServiceError(result); + + if (result.IsSuccess()) { + ctx.WriteBuffer(tag_info); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); +} + +void NfcInterface::AttachActivateEvent(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto device_handle{rp.Pop<u64>()}; + LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(GetManager()->AttachActivateEvent(device_handle)); +} + +void NfcInterface::AttachDeactivateEvent(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto device_handle{rp.Pop<u64>()}; + LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(GetManager()->AttachDeactivateEvent(device_handle)); +} + +void NfcInterface::ReadMifare(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto device_handle{rp.Pop<u64>()}; + const auto buffer{ctx.ReadBuffer()}; + const auto number_of_commands{ctx.GetReadBufferNumElements<MifareReadBlockParameter>()}; + std::vector<MifareReadBlockParameter> read_commands(number_of_commands); + + memcpy(read_commands.data(), buffer.data(), + number_of_commands * sizeof(MifareReadBlockParameter)); + + LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, read_commands_size={}", + device_handle, number_of_commands); + + std::vector<MifareReadBlockData> out_data(number_of_commands); + auto result = GetManager()->ReadMifare(device_handle, read_commands, out_data); + result = TranslateResultToServiceError(result); + + if (result.IsSuccess()) { + ctx.WriteBuffer(out_data); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); +} + +void NfcInterface::WriteMifare(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto device_handle{rp.Pop<u64>()}; + const auto buffer{ctx.ReadBuffer()}; + const auto number_of_commands{ctx.GetReadBufferNumElements<MifareWriteBlockParameter>()}; + std::vector<MifareWriteBlockParameter> write_commands(number_of_commands); + + memcpy(write_commands.data(), buffer.data(), + number_of_commands * sizeof(MifareWriteBlockParameter)); + + LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, write_commands_size={}", + device_handle, number_of_commands); + + auto result = GetManager()->WriteMifare(device_handle, write_commands); + result = TranslateResultToServiceError(result); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); +} + +void NfcInterface::SendCommandByPassThrough(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto device_handle{rp.Pop<u64>()}; + const auto timeout{rp.PopRaw<Time::Clock::TimeSpanType>()}; + const auto command_data{ctx.ReadBuffer()}; + LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, timeout={}, data_size={}", + device_handle, timeout.ToSeconds(), command_data.size()); + + std::vector<u8> out_data(1); + auto result = + GetManager()->SendCommandByPassThrough(device_handle, timeout, command_data, out_data); + result = TranslateResultToServiceError(result); + + if (result.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + ctx.WriteBuffer(out_data); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast<u32>(out_data.size())); +} + +std::shared_ptr<DeviceManager> NfcInterface::GetManager() { + if (device_manager == nullptr) { + device_manager = std::make_shared<DeviceManager>(system, service_context); + } + return device_manager; +} + +BackendType NfcInterface::GetBackendType() const { + return backend_type; +} + +Result NfcInterface::TranslateResultToServiceError(Result result) const { + const auto backend = GetBackendType(); + + if (result.IsSuccess()) { + return result; + } + + if (result.module != ErrorModule::NFC) { + return result; + } + + switch (backend) { + case BackendType::Mifare: + return TranslateResultToNfp(result); + case BackendType::Nfp: { + return TranslateResultToNfp(result); + } + default: + if (result != ResultUnknown216) { + return result; + } + return ResultUnknown74; + } +} + +Result NfcInterface::TranslateResultToNfp(Result result) const { + if (result == ResultDeviceNotFound) { + return NFP::ResultDeviceNotFound; + } + if (result == ResultInvalidArgument) { + return NFP::ResultInvalidArgument; + } + if (result == ResultWrongApplicationAreaSize) { + return NFP::ResultWrongApplicationAreaSize; + } + if (result == ResultWrongDeviceState) { + return NFP::ResultWrongDeviceState; + } + if (result == ResultUnknown74) { + return NFP::ResultUnknown74; + } + if (result == ResultNfcDisabled) { + return NFP::ResultNfcDisabled; + } + if (result == ResultNfcNotInitialized) { + return NFP::ResultNfcDisabled; + } + if (result == ResultWriteAmiiboFailed) { + return NFP::ResultWriteAmiiboFailed; + } + if (result == ResultTagRemoved) { + return NFP::ResultTagRemoved; + } + if (result == ResultRegistrationIsNotInitialized) { + return NFP::ResultRegistrationIsNotInitialized; + } + if (result == ResultApplicationAreaIsNotInitialized) { + return NFP::ResultApplicationAreaIsNotInitialized; + } + if (result == ResultCorruptedData) { + return NFP::ResultCorruptedData; + } + if (result == ResultWrongApplicationAreaId) { + return NFP::ResultWrongApplicationAreaId; + } + if (result == ResultApplicationAreaExist) { + return NFP::ResultApplicationAreaExist; + } + if (result == ResultNotAnAmiibo) { + return NFP::ResultNotAnAmiibo; + } + LOG_WARNING(Service_NFC, "Result conversion not handled"); + return result; +} + +Result NfcInterface::TranslateResultToMifare(Result result) const { + if (result == ResultDeviceNotFound) { + return Mifare::ResultDeviceNotFound; + } + if (result == ResultInvalidArgument) { + return Mifare::ResultInvalidArgument; + } + if (result == ResultWrongDeviceState) { + return Mifare::ResultWrongDeviceState; + } + if (result == ResultNfcDisabled) { + return Mifare::ResultNfcDisabled; + } + if (result == ResultTagRemoved) { + return Mifare::ResultTagRemoved; + } + LOG_WARNING(Service_NFC, "Result conversion not handled"); + return result; +} + +} // namespace Service::NFC diff --git a/src/core/hle/service/nfc/nfc_user.h b/src/core/hle/service/nfc/nfc_interface.h index aee046ae8..08be174d8 100644 --- a/src/core/hle/service/nfc/nfc_user.h +++ b/src/core/hle/service/nfc/nfc_interface.h @@ -3,26 +3,17 @@ #pragma once -#include <array> -#include <memory> -#include <optional> - #include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/nfc/nfc_types.h" #include "core/hle/service/service.h" namespace Service::NFC { -class NfcDevice; +class DeviceManager; -class IUser final : public ServiceFramework<IUser> { +class NfcInterface : public ServiceFramework<NfcInterface> { public: - explicit IUser(Core::System& system_); - ~IUser(); - -private: - enum class State : u32 { - NonInitialized, - Initialized, - }; + explicit NfcInterface(Core::System& system_, const char* name, BackendType service_backend); + ~NfcInterface(); void Initialize(HLERequestContext& ctx); void Finalize(HLERequestContext& ctx); @@ -37,16 +28,22 @@ private: void GetTagInfo(HLERequestContext& ctx); void AttachActivateEvent(HLERequestContext& ctx); void AttachDeactivateEvent(HLERequestContext& ctx); + void ReadMifare(HLERequestContext& ctx); + void WriteMifare(HLERequestContext& ctx); void SendCommandByPassThrough(HLERequestContext& ctx); - std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle); +protected: + std::shared_ptr<DeviceManager> GetManager(); + BackendType GetBackendType() const; + Result TranslateResultToServiceError(Result result) const; + Result TranslateResultToNfp(Result result) const; + Result TranslateResultToMifare(Result result) const; KernelHelpers::ServiceContext service_context; - std::array<std::shared_ptr<NfcDevice>, 10> devices{}; - + BackendType backend_type; State state{State::NonInitialized}; - Kernel::KEvent* availability_change_event; + std::shared_ptr<DeviceManager> device_manager = nullptr; }; } // namespace Service::NFC diff --git a/src/core/hle/service/nfc/nfc_result.h b/src/core/hle/service/nfc/nfc_result.h index 146b8ba61..917d79ef8 100644 --- a/src/core/hle/service/nfc/nfc_result.h +++ b/src/core/hle/service/nfc/nfc_result.h @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -7,17 +7,22 @@ namespace Service::NFC { -constexpr Result DeviceNotFound(ErrorModule::NFC, 64); -constexpr Result InvalidArgument(ErrorModule::NFC, 65); -constexpr Result WrongDeviceState(ErrorModule::NFC, 73); -constexpr Result NfcDisabled(ErrorModule::NFC, 80); -constexpr Result TagRemoved(ErrorModule::NFC, 97); - -constexpr Result MifareDeviceNotFound(ErrorModule::NFCMifare, 64); -constexpr Result MifareInvalidArgument(ErrorModule::NFCMifare, 65); -constexpr Result MifareWrongDeviceState(ErrorModule::NFCMifare, 73); -constexpr Result MifareNfcDisabled(ErrorModule::NFCMifare, 80); -constexpr Result MifareTagRemoved(ErrorModule::NFCMifare, 97); -constexpr Result MifareReadError(ErrorModule::NFCMifare, 288); +constexpr Result ResultDeviceNotFound(ErrorModule::NFC, 64); +constexpr Result ResultInvalidArgument(ErrorModule::NFC, 65); +constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFP, 68); +constexpr Result ResultWrongDeviceState(ErrorModule::NFC, 73); +constexpr Result ResultUnknown74(ErrorModule::NFC, 74); +constexpr Result ResultUnknown76(ErrorModule::NFC, 76); +constexpr Result ResultNfcNotInitialized(ErrorModule::NFC, 77); +constexpr Result ResultNfcDisabled(ErrorModule::NFC, 80); +constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88); +constexpr Result ResultTagRemoved(ErrorModule::NFC, 97); +constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120); +constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); +constexpr Result ResultCorruptedData(ErrorModule::NFP, 144); +constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152); +constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168); +constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178); +constexpr Result ResultUnknown216(ErrorModule::NFC, 216); } // namespace Service::NFC diff --git a/src/core/hle/service/nfc/nfc_types.h b/src/core/hle/service/nfc/nfc_types.h new file mode 100644 index 000000000..c7ebd1fdb --- /dev/null +++ b/src/core/hle/service/nfc/nfc_types.h @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <array> + +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Service::NFC { +enum class BackendType : u32 { + None, + Nfc, + Nfp, + Mifare, +}; + +// This is nn::nfc::DeviceState +enum class DeviceState : u32 { + Initialized, + SearchingForTag, + TagFound, + TagRemoved, + TagMounted, + Unavailable, + Finalized, +}; + +// This is nn::nfc::State +enum class State : u32 { + NonInitialized, + Initialized, +}; + +// This is nn::nfc::TagType +enum class TagType : u32 { + None, + Type1, // ISO14443A RW 96-2k bytes 106kbit/s + Type2, // ISO14443A RW/RO 540 bytes 106kbit/s + Type3, // Sony FeliCa RW/RO 2k bytes 212kbit/s + Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s + Type5, // ISO15693 RW/RO 540 bytes 106kbit/s +}; + +enum class PackedTagType : u8 { + None, + Type1, // ISO14443A RW 96-2k bytes 106kbit/s + Type2, // ISO14443A RW/RO 540 bytes 106kbit/s + Type3, // Sony FeliCa RW/RO 2k bytes 212kbit/s + Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s + Type5, // ISO15693 RW/RO 540 bytes 106kbit/s +}; + +// This is nn::nfc::NfcProtocol +// Verify this enum. It might be completely wrong default protocol is 0x48 +enum class NfcProtocol : u32 { + None, + TypeA = 1U << 0, // ISO14443A + TypeB = 1U << 1, // ISO14443B + TypeF = 1U << 2, // Sony FeliCa + Unknown1 = 1U << 3, + Unknown2 = 1U << 5, + All = 0xFFFFFFFFU, +}; + +// this is nn::nfc::TestWaveType +enum class TestWaveType : u32 { + Unknown, +}; + +using UniqueSerialNumber = std::array<u8, 7>; +using UniqueSerialNumberExtension = std::array<u8, 3>; + +// This is nn::nfc::DeviceHandle +using DeviceHandle = u64; + +// This is nn::nfc::TagInfo +struct TagInfo { + UniqueSerialNumber uuid; + UniqueSerialNumberExtension uuid_extension; + u8 uuid_length; + INSERT_PADDING_BYTES(0x15); + NfcProtocol protocol; + TagType tag_type; + INSERT_PADDING_BYTES(0x30); +}; +static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size"); + +} // namespace Service::NFC diff --git a/src/core/hle/service/nfc/nfc_user.cpp b/src/core/hle/service/nfc/nfc_user.cpp deleted file mode 100644 index 7c162a4f3..000000000 --- a/src/core/hle/service/nfc/nfc_user.cpp +++ /dev/null @@ -1,365 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/logging/log.h" -#include "core/core.h" -#include "core/hid/hid_types.h" -#include "core/hle/kernel/k_event.h" -#include "core/hle/service/ipc_helpers.h" -#include "core/hle/service/nfc/nfc_device.h" -#include "core/hle/service/nfc/nfc_result.h" -#include "core/hle/service/nfc/nfc_user.h" -#include "core/hle/service/time/clock_types.h" - -namespace Service::NFC { - -IUser::IUser(Core::System& system_) - : ServiceFramework{system_, "NFC::IUser"}, service_context{system_, service_name} { - static const FunctionInfo functions[] = { - {0, &IUser::Initialize, "InitializeOld"}, - {1, &IUser::Finalize, "FinalizeOld"}, - {2, &IUser::GetState, "GetStateOld"}, - {3, &IUser::IsNfcEnabled, "IsNfcEnabledOld"}, - {400, &IUser::Initialize, "Initialize"}, - {401, &IUser::Finalize, "Finalize"}, - {402, &IUser::GetState, "GetState"}, - {403, &IUser::IsNfcEnabled, "IsNfcEnabled"}, - {404, &IUser::ListDevices, "ListDevices"}, - {405, &IUser::GetDeviceState, "GetDeviceState"}, - {406, &IUser::GetNpadId, "GetNpadId"}, - {407, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, - {408, &IUser::StartDetection, "StartDetection"}, - {409, &IUser::StopDetection, "StopDetection"}, - {410, &IUser::GetTagInfo, "GetTagInfo"}, - {411, &IUser::AttachActivateEvent, "AttachActivateEvent"}, - {412, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"}, - {1000, nullptr, "ReadMifare"}, - {1001, nullptr, "WriteMifare"}, - {1300, &IUser::SendCommandByPassThrough, "SendCommandByPassThrough"}, - {1301, nullptr, "KeepPassThroughSession"}, - {1302, nullptr, "ReleasePassThroughSession"}, - }; - RegisterHandlers(functions); - - availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent"); - - for (u32 device_index = 0; device_index < 10; device_index++) { - devices[device_index] = - std::make_shared<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system, - service_context, availability_change_event); - } -} - -IUser ::~IUser() { - availability_change_event->Close(); -} - -void IUser::Initialize(HLERequestContext& ctx) { - LOG_INFO(Service_NFC, "called"); - - state = State::Initialized; - - for (auto& device : devices) { - device->Initialize(); - } - - IPC::ResponseBuilder rb{ctx, 2, 0}; - rb.Push(ResultSuccess); -} - -void IUser::Finalize(HLERequestContext& ctx) { - LOG_INFO(Service_NFC, "called"); - - state = State::NonInitialized; - - for (auto& device : devices) { - device->Finalize(); - } - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} - -void IUser::GetState(HLERequestContext& ctx) { - LOG_DEBUG(Service_NFC, "called"); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(state); -} - -void IUser::IsNfcEnabled(HLERequestContext& ctx) { - LOG_DEBUG(Service_NFC, "called"); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(state != State::NonInitialized); -} - -void IUser::ListDevices(HLERequestContext& ctx) { - LOG_DEBUG(Service_NFC, "called"); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - if (!ctx.CanWriteBuffer()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(InvalidArgument); - return; - } - - if (ctx.GetWriteBufferSize() == 0) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(InvalidArgument); - return; - } - - std::vector<u64> nfp_devices; - const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>(); - - for (auto& device : devices) { - if (nfp_devices.size() >= max_allowed_devices) { - continue; - } - if (device->GetCurrentState() != NFP::DeviceState::Unavailable) { - nfp_devices.push_back(device->GetHandle()); - } - } - - if (nfp_devices.empty()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - ctx.WriteBuffer(nfp_devices); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(static_cast<s32>(nfp_devices.size())); -} - -void IUser::GetDeviceState(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(device.value()->GetCurrentState()); -} - -void IUser::GetNpadId(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(device.value()->GetNpadId()); -} - -void IUser::AttachAvailabilityChangeEvent(HLERequestContext& ctx) { - LOG_INFO(Service_NFC, "called"); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(availability_change_event->GetReadableEvent()); -} - -void IUser::StartDetection(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - const auto nfp_protocol{rp.PopEnum<NFP::TagProtocol>()}; - LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - const auto result = device.value()->StartDetection(nfp_protocol); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); -} - -void IUser::StopDetection(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - const auto result = device.value()->StopDetection(); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); -} - -void IUser::GetTagInfo(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - NFP::TagInfo tag_info{}; - const auto result = device.value()->GetTagInfo(tag_info, false); - ctx.WriteBuffer(tag_info); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); -} - -void IUser::AttachActivateEvent(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(device.value()->GetActivateEvent()); -} - -void IUser::AttachDeactivateEvent(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(device.value()->GetDeactivateEvent()); -} - -void IUser::SendCommandByPassThrough(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - const auto timeout{rp.PopRaw<Time::Clock::TimeSpanType>()}; - const auto command_data{ctx.ReadBuffer()}; - - LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, timeout={}, data_size={}", - device_handle, timeout.ToSeconds(), command_data.size()); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - auto device = GetNfcDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - std::vector<u8> out_data(1); - // TODO: Request data from nfc device - ctx.WriteBuffer(out_data); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(static_cast<u32>(out_data.size())); -} - -std::optional<std::shared_ptr<NfcDevice>> IUser::GetNfcDevice(u64 handle) { - for (auto& device : devices) { - if (device->GetHandle() == handle) { - return device; - } - } - return std::nullopt; -} - -} // namespace Service::NFC diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index 2714f4bea..2eeabc138 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -13,7 +13,7 @@ class IUser final : public Interface { public: explicit IUser(Core::System& system_) : Interface(system_, "NFP:IUser") { // clang-format off - static const FunctionInfo functions[] = { + static const FunctionInfoTyped<IUser> functions[] = { {0, &IUser::Initialize, "Initialize"}, {1, &IUser::Finalize, "Finalize"}, {2, &IUser::ListDevices, "ListDevices"}, @@ -50,7 +50,7 @@ class ISystem final : public Interface { public: explicit ISystem(Core::System& system_) : Interface(system_, "NFP:ISystem") { // clang-format off - static const FunctionInfo functions[] = { + static const FunctionInfoTyped<ISystem> functions[] = { {0, &ISystem::InitializeSystem, "InitializeSystem"}, {1, &ISystem::FinalizeSystem, "FinalizeSystem"}, {2, &ISystem::ListDevices, "ListDevices"}, @@ -89,7 +89,7 @@ class IDebug final : public Interface { public: explicit IDebug(Core::System& system_) : Interface(system_, "NFP:IDebug") { // clang-format off - static const FunctionInfo functions[] = { + static const FunctionInfoTyped<IDebug> functions[] = { {0, &IDebug::InitializeDebug, "InitializeDebug"}, {1, &IDebug::FinalizeDebug, "FinalizeDebug"}, {2, &IDebug::ListDevices, "ListDevices"}, @@ -126,9 +126,9 @@ public: {201, &IDebug::SetAll, "SetAll"}, {202, &IDebug::FlushDebug, "FlushDebug"}, {203, &IDebug::BreakTag, "BreakTag"}, - {204, nullptr, "ReadBackupData"}, - {205, nullptr, "WriteBackupData"}, - {206, nullptr, "WriteNtf"}, + {204, &IDebug::ReadBackupData, "ReadBackupData"}, + {205, &IDebug::WriteBackupData, "WriteBackupData"}, + {206, &IDebug::WriteNtf, "WriteNtf"}, }; // clang-format on @@ -152,16 +152,10 @@ private: void CreateUserInterface(HLERequestContext& ctx) { LOG_DEBUG(Service_NFP, "called"); - if (user_interface == nullptr) { - user_interface = std::make_shared<IUser>(system); - } - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface<IUser>(user_interface); + rb.PushIpcInterface<IUser>(system); } - - std::shared_ptr<IUser> user_interface; }; class ISystemManager final : public ServiceFramework<ISystemManager> { @@ -180,16 +174,10 @@ 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); + rb.PushIpcInterface<ISystem>(system); } - - std::shared_ptr<ISystem> system_interface; }; class IDebugManager final : public ServiceFramework<IDebugManager> { @@ -208,16 +196,10 @@ 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); + rb.PushIpcInterface<IDebug>(system); } - - std::shared_ptr<IDebug> system_interface; }; void LoopProcess(Core::System& system) { diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h deleted file mode 100644 index bab05538a..000000000 --- a/src/core/hle/service/nfp/nfp_device.h +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <span> -#include <vector> - -#include "common/common_types.h" -#include "core/hle/service/kernel_helpers.h" -#include "core/hle/service/nfp/nfp_types.h" -#include "core/hle/service/service.h" - -namespace Kernel { -class KEvent; -class KReadableEvent; -} // namespace Kernel - -namespace Core { -class System; -} // namespace Core - -namespace Core::HID { -class EmulatedController; -enum class ControllerTriggerType; -enum class NpadIdType : u32; -} // namespace Core::HID - -namespace Service::NFP { -class NfpDevice { -public: - NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, - KernelHelpers::ServiceContext& service_context_, - Kernel::KEvent* availability_change_event_); - ~NfpDevice(); - - void Initialize(); - void Finalize(); - - Result StartDetection(TagProtocol allowed_protocol); - 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(); - Result SetRegisterInfoPrivate(const AmiiboName& amiibo_name); - Result RestoreAmiibo(); - Result Format(); - - Result OpenApplicationArea(u32 access_id); - Result GetApplicationAreaId(u32& application_area_id) const; - Result GetApplicationArea(std::vector<u8>& data) const; - Result SetApplicationArea(std::span<const u8> data); - 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; - DeviceState GetCurrentState() const; - Core::HID::NpadIdType GetNpadId() const; - - Kernel::KReadableEvent& GetActivateEvent() const; - Kernel::KReadableEvent& GetDeactivateEvent() const; - -private: - void NpadUpdate(Core::HID::ControllerTriggerType type); - bool LoadAmiibo(std::span<const u8> data); - void CloseAmiibo(); - - AmiiboName GetAmiiboName(const AmiiboSettings& settings) const; - void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name); - AmiiboDate GetAmiiboDate(s64 posix_time) const; - u64 RemoveVersionByte(u64 application_id) const; - void UpdateSettingsCrc(); - void UpdateRegisterInfoCrc(); - - bool is_controller_set{}; - int callback_key; - const Core::HID::NpadIdType npad_id; - Core::System& system; - Core::HID::EmulatedController* npad_device = nullptr; - KernelHelpers::ServiceContext& service_context; - Kernel::KEvent* activate_event = nullptr; - Kernel::KEvent* deactivate_event = nullptr; - Kernel::KEvent* availability_change_event = nullptr; - - bool is_initalized{}; - bool is_data_moddified{}; - bool is_app_area_open{}; - bool is_plain_amiibo{}; - TagProtocol allowed_protocols{}; - s64 current_posix_time{}; - MountTarget mount_target{MountTarget::None}; - DeviceState device_state{DeviceState::Unavailable}; - - NTAG215File tag_data{}; - EncryptedNTAG215File encrypted_tag_data{}; -}; - -} // namespace Service::NFP diff --git a/src/core/hle/service/nfp/nfp_interface.cpp b/src/core/hle/service/nfp/nfp_interface.cpp index 2ed8bb1ba..21d159154 100644 --- a/src/core/hle/service/nfp/nfp_interface.cpp +++ b/src/core/hle/service/nfp/nfp_interface.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" @@ -6,198 +6,34 @@ #include "core/hid/hid_types.h" #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/nfc/common/device.h" +#include "core/hle/service/nfc/common/device_manager.h" +#include "core/hle/service/nfc/nfc_types.h" #include "core/hle/service/nfp/nfp_interface.h" #include "core/hle/service/nfp/nfp_result.h" +#include "core/hle/service/nfp/nfp_types.h" namespace Service::NFP { Interface::Interface(Core::System& system_, const char* name) - : ServiceFramework{system_, name}, service_context{system_, service_name} { - availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent"); + : NfcInterface{system_, name, NFC::BackendType::Nfp} {} - for (u32 device_index = 0; device_index < 10; device_index++) { - devices[device_index] = - std::make_shared<NfpDevice>(Core::HID::IndexToNpadIdType(device_index), system, - service_context, availability_change_event); - } -} - -Interface::~Interface() { - availability_change_event->Close(); -} - -void Interface::Initialize(HLERequestContext& ctx) { - LOG_INFO(Service_NFP, "called"); - - state = State::Initialized; - - for (auto& device : devices) { - device->Initialize(); - } - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} +Interface::~Interface() = default; 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); + Initialize(ctx); } 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; - - for (auto& device : devices) { - device->Finalize(); - } - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + Initialize(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); + Finalize(ctx); } 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) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - if (!ctx.CanWriteBuffer()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(InvalidArgument); - return; - } - - if (ctx.GetWriteBufferSize() == 0) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(InvalidArgument); - return; - } - - std::vector<u64> nfp_devices; - const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>(); - - for (const auto& device : devices) { - if (nfp_devices.size() >= max_allowed_devices) { - continue; - } - if (device->GetCurrentState() != DeviceState::Unavailable) { - nfp_devices.push_back(device->GetHandle()); - } - } - - if (nfp_devices.empty()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - ctx.WriteBuffer(nfp_devices); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(static_cast<s32>(nfp_devices.size())); -} - -void Interface::StartDetection(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto device_handle{rp.Pop<u64>()}; - const auto nfp_protocol{rp.PopEnum<TagProtocol>()}; - LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol); - - 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()->StartDetection(nfp_protocol); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); -} - -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); - - 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()->StopDetection(); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); + Finalize(ctx); } void Interface::Mount(HLERequestContext& ctx) { @@ -208,21 +44,9 @@ void Interface::Mount(HLERequestContext& ctx) { LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle, model_type, mount_target); - 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; - } + auto result = GetManager()->Mount(device_handle, model_type, mount_target); + result = TranslateResultToServiceError(result); - const auto result = device.value()->Mount(mount_target); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -232,21 +56,9 @@ void Interface::Unmount(HLERequestContext& 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; - } + auto result = GetManager()->Unmount(device_handle); + result = TranslateResultToServiceError(result); - const auto result = device.value()->Unmount(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -257,21 +69,9 @@ void Interface::OpenApplicationArea(HLERequestContext& ctx) { const auto access_id{rp.Pop<u32>()}; LOG_INFO(Service_NFP, "called, device_handle={}, access_id={}", device_handle, access_id); - 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; - } + auto result = GetManager()->OpenApplicationArea(device_handle, access_id); + result = TranslateResultToServiceError(result); - const auto result = device.value()->OpenApplicationArea(access_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -282,28 +82,16 @@ void Interface::GetApplicationArea(HLERequestContext& ctx) { const auto data_size = ctx.GetWriteBufferSize(); LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - if (!ctx.CanWriteBuffer()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(InvalidArgument); - return; - } - - auto device = GetNfpDevice(device_handle); + std::vector<u8> data(data_size); + auto result = GetManager()->GetApplicationArea(device_handle, data); + result = TranslateResultToServiceError(result); - if (!device.has_value()) { + if (result.IsError()) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); + rb.Push(result); return; } - std::vector<u8> data(data_size); - const auto result = device.value()->GetApplicationArea(data); ctx.WriteBuffer(data); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(result); @@ -316,27 +104,9 @@ void Interface::SetApplicationArea(HLERequestContext& ctx) { const auto data{ctx.ReadBuffer()}; LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}", device_handle, data.size()); - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - if (!ctx.CanReadBuffer()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(InvalidArgument); - return; - } - - auto device = GetNfpDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } + auto result = GetManager()->SetApplicationArea(device_handle, data); + result = TranslateResultToServiceError(result); - const auto result = device.value()->SetApplicationArea(data); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -346,21 +116,9 @@ void Interface::Flush(HLERequestContext& 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); + auto result = GetManager()->Flush(device_handle); + result = TranslateResultToServiceError(result); - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - const auto result = device.value()->Flush(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -370,21 +128,9 @@ void Interface::Restore(HLERequestContext& ctx) { const auto device_handle{rp.Pop<u64>()}; LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } + auto result = GetManager()->Restore(device_handle); + result = TranslateResultToServiceError(result); - auto device = GetNfpDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - const auto result = device.value()->RestoreAmiibo(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -397,53 +143,9 @@ void Interface::CreateApplicationArea(HLERequestContext& ctx) { LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle, access_id, data.size()); - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - if (!ctx.CanReadBuffer()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(InvalidArgument); - return; - } - - auto device = GetNfpDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - const auto result = device.value()->CreateApplicationArea(access_id, data); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); -} - -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); - - 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; - } + auto result = GetManager()->CreateApplicationArea(device_handle, access_id, data); + result = TranslateResultToServiceError(result); - TagInfo tag_info{}; - const auto result = device.value()->GetTagInfo(tag_info); - ctx.WriteBuffer(tag_info); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -453,23 +155,14 @@ void Interface::GetRegisterInfo(HLERequestContext& 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); + RegisterInfo register_info{}; + auto result = GetManager()->GetRegisterInfo(device_handle, register_info); + result = TranslateResultToServiceError(result); - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; + if (result.IsSuccess()) { + ctx.WriteBuffer(register_info); } - RegisterInfo register_info{}; - const auto result = device.value()->GetRegisterInfo(register_info); - ctx.WriteBuffer(register_info); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -479,23 +172,14 @@ void Interface::GetCommonInfo(HLERequestContext& 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); + CommonInfo common_info{}; + auto result = GetManager()->GetCommonInfo(device_handle, common_info); + result = TranslateResultToServiceError(result); - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; + if (result.IsSuccess()) { + ctx.WriteBuffer(common_info); } - CommonInfo common_info{}; - const auto result = device.value()->GetCommonInfo(common_info); - ctx.WriteBuffer(common_info); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -505,155 +189,26 @@ void Interface::GetModelInfo(HLERequestContext& 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); + ModelInfo model_info{}; + auto result = GetManager()->GetModelInfo(device_handle, model_info); + result = TranslateResultToServiceError(result); - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; + if (result.IsSuccess()) { + ctx.WriteBuffer(model_info); } - ModelInfo model_info{}; - const auto result = device.value()->GetModelInfo(model_info); - ctx.WriteBuffer(model_info); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } -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); - - 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; - } - - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(device.value()->GetActivateEvent()); -} - -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); - - 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; - } - - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(device.value()->GetDeactivateEvent()); -} - -void Interface::GetState(HLERequestContext& ctx) { - LOG_DEBUG(Service_NFP, "called"); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(state); -} - -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); - - auto device = GetNfpDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(device.value()->GetCurrentState()); -} - -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); - - 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; - } - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.PushEnum(device.value()->GetNpadId()); -} - 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); - auto device = GetNfpDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(device.value()->GetApplicationAreaSize()); -} - -void Interface::AttachAvailabilityChangeEvent(HLERequestContext& ctx) { - LOG_INFO(Service_NFP, "called"); - - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } - - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(availability_change_event->GetReadableEvent()); + rb.Push(GetManager()->GetApplicationAreaSize()); } void Interface::RecreateApplicationArea(HLERequestContext& ctx) { @@ -664,21 +219,9 @@ void Interface::RecreateApplicationArea(HLERequestContext& ctx) { LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle, access_id, data.size()); - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } + auto result = GetManager()->RecreateApplicationArea(device_handle, access_id, data); + result = TranslateResultToServiceError(result); - auto device = GetNfpDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } - - const auto result = device.value()->RecreateApplicationArea(access_id, data); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -686,23 +229,11 @@ void Interface::RecreateApplicationArea(HLERequestContext& ctx) { 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; - } + LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); - auto device = GetNfpDevice(device_handle); + auto result = GetManager()->Format(device_handle); + result = TranslateResultToServiceError(result); - 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); } @@ -712,23 +243,14 @@ void Interface::GetAdminInfo(HLERequestContext& 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); + AdminInfo admin_info{}; + auto result = GetManager()->GetAdminInfo(device_handle, admin_info); + result = TranslateResultToServiceError(result); - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; + if (result.IsSuccess()) { + ctx.WriteBuffer(admin_info); } - AdminInfo admin_info{}; - const auto result = device.value()->GetAdminInfo(admin_info); - ctx.WriteBuffer(admin_info); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -738,23 +260,14 @@ void Interface::GetRegisterInfoPrivate(HLERequestContext& 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); + RegisterInfoPrivate register_info{}; + auto result = GetManager()->GetRegisterInfoPrivate(device_handle, register_info); + result = TranslateResultToServiceError(result); - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; + if (result.IsSuccess()) { + ctx.WriteBuffer(register_info); } - RegisterInfoPrivate register_info{}; - const auto result = device.value()->GetRegisterInfoPrivate(register_info); - ctx.WriteBuffer(register_info); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -762,25 +275,15 @@ void Interface::GetRegisterInfoPrivate(HLERequestContext& ctx) { 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()); + const auto register_info_buffer{ctx.ReadBuffer()}; + LOG_INFO(Service_NFP, "called, device_handle={}, buffer_size={}", device_handle, + register_info_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; - } + RegisterInfoPrivate register_info{}; + memcpy(®ister_info, register_info_buffer.data(), sizeof(RegisterInfoPrivate)); + auto result = GetManager()->SetRegisterInfoPrivate(device_handle, register_info); + result = TranslateResultToServiceError(result); - const auto result = device.value()->SetRegisterInfoPrivate({}); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -788,23 +291,11 @@ void Interface::SetRegisterInfoPrivate(HLERequestContext& ctx) { 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; - } + LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); - auto device = GetNfpDevice(device_handle); + auto result = GetManager()->DeleteRegisterInfo(device_handle); + result = TranslateResultToServiceError(result); - 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); } @@ -812,23 +303,11 @@ void Interface::DeleteRegisterInfo(HLERequestContext& ctx) { 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; - } + LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); - auto device = GetNfpDevice(device_handle); + auto result = GetManager()->DeleteApplicationArea(device_handle); + result = TranslateResultToServiceError(result); - 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); } @@ -836,24 +315,18 @@ void Interface::DeleteApplicationArea(HLERequestContext& ctx) { 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; - } + LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); - auto device = GetNfpDevice(device_handle); + bool has_application_area = false; + auto result = GetManager()->ExistsApplicationArea(device_handle, has_application_area); + result = TranslateResultToServiceError(result); - if (!device.has_value()) { + if (result.IsError()) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); + rb.Push(result); 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); @@ -862,27 +335,16 @@ void Interface::ExistsApplicationArea(HLERequestContext& ctx) { 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; - } + LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); - auto device = GetNfpDevice(device_handle); + NfpData nfp_data{}; + auto result = GetManager()->GetAll(device_handle, nfp_data); + result = TranslateResultToServiceError(result); - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; + if (result.IsSuccess()) { + ctx.WriteBuffer(nfp_data); } - NfpData data{}; - const auto result = device.value()->GetAll(data); - - ctx.WriteBuffer(data); - IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -890,28 +352,15 @@ void Interface::GetAll(HLERequestContext& ctx) { 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; - } + const auto nfp_data_buffer{ctx.ReadBuffer()}; - auto device = GetNfpDevice(device_handle); - - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } + LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); - NfpData data{}; - memcpy(&data, nfp_data.data(), sizeof(NfpData)); + NfpData nfp_data{}; + memcpy(&nfp_data, nfp_data_buffer.data(), sizeof(NfpData)); + auto result = GetManager()->SetAll(device_handle, nfp_data); + result = TranslateResultToServiceError(result); - const auto result = device.value()->SetAll(data); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -919,23 +368,11 @@ void Interface::SetAll(HLERequestContext& ctx) { 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; - } + LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); - auto device = GetNfpDevice(device_handle); + auto result = GetManager()->FlushDebug(device_handle); + result = TranslateResultToServiceError(result); - 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); } @@ -944,23 +381,12 @@ 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); + LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, break_type={}", device_handle, + break_type); - if (state == State::NonInitialized) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(NfcDisabled); - return; - } + auto result = GetManager()->BreakTag(device_handle, break_type); + result = TranslateResultToServiceError(result); - 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); } @@ -968,23 +394,16 @@ void Interface::BreakTag(HLERequestContext& ctx) { 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; - } + LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); - auto device = GetNfpDevice(device_handle); + std::vector<u8> backup_data{}; + auto result = GetManager()->ReadBackupData(device_handle, backup_data); + result = TranslateResultToServiceError(result); - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; + if (result.IsSuccess()) { + ctx.WriteBuffer(backup_data); } - const auto result = device.value()->ReadBackupData(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -992,23 +411,12 @@ void Interface::ReadBackupData(HLERequestContext& ctx) { 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); + const auto backup_data_buffer{ctx.ReadBuffer()}; + LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); - if (!device.has_value()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(DeviceNotFound); - return; - } + auto result = GetManager()->WriteBackupData(device_handle, backup_data_buffer); + result = TranslateResultToServiceError(result); - const auto result = device.value()->WriteBackupData(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); } @@ -1016,34 +424,15 @@ void Interface::WriteBackupData(HLERequestContext& ctx) { 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; - } + const auto write_type{rp.PopEnum<WriteType>()}; + const auto ntf_data_buffer{ctx.ReadBuffer()}; + LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); - auto device = GetNfpDevice(device_handle); + auto result = GetManager()->WriteNtf(device_handle, write_type, ntf_data_buffer); + result = TranslateResultToServiceError(result); - 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; - } - } - return std::nullopt; -} - } // namespace Service::NFP diff --git a/src/core/hle/service/nfp/nfp_interface.h b/src/core/hle/service/nfp/nfp_interface.h index 616c94b06..fa985b068 100644 --- a/src/core/hle/service/nfp/nfp_interface.h +++ b/src/core/hle/service/nfp/nfp_interface.h @@ -1,32 +1,23 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include <array> -#include <memory> -#include <optional> - #include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/nfc/nfc_interface.h" #include "core/hle/service/service.h" namespace Service::NFP { -class NfpDevice; -class Interface : public ServiceFramework<Interface> { +class Interface : public NFC::NfcInterface { public: 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); void Mount(HLERequestContext& ctx); void Unmount(HLERequestContext& ctx); void OpenApplicationArea(HLERequestContext& ctx); @@ -35,17 +26,10 @@ public: void Flush(HLERequestContext& ctx); void Restore(HLERequestContext& ctx); void CreateApplicationArea(HLERequestContext& ctx); - void GetTagInfo(HLERequestContext& ctx); void GetRegisterInfo(HLERequestContext& ctx); void GetCommonInfo(HLERequestContext& ctx); void GetModelInfo(HLERequestContext& ctx); - void AttachActivateEvent(HLERequestContext& ctx); - void AttachDeactivateEvent(HLERequestContext& ctx); - void GetState(HLERequestContext& ctx); - void GetDeviceState(HLERequestContext& ctx); - void GetNpadId(HLERequestContext& ctx); void GetApplicationAreaSize(HLERequestContext& ctx); - void AttachAvailabilityChangeEvent(HLERequestContext& ctx); void RecreateApplicationArea(HLERequestContext& ctx); void Format(HLERequestContext& ctx); void GetAdminInfo(HLERequestContext& ctx); @@ -61,21 +45,6 @@ public: 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); - - KernelHelpers::ServiceContext service_context; - - std::array<std::shared_ptr<NfpDevice>, 10> devices{}; - - State state{State::NonInitialized}; - Kernel::KEvent* availability_change_event; }; } // namespace Service::NFP diff --git a/src/core/hle/service/nfp/nfp_result.h b/src/core/hle/service/nfp/nfp_result.h index d8e4cf094..4c126cd81 100644 --- a/src/core/hle/service/nfp/nfp_result.h +++ b/src/core/hle/service/nfp/nfp_result.h @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -7,18 +7,19 @@ namespace Service::NFP { -constexpr Result DeviceNotFound(ErrorModule::NFP, 64); -constexpr Result InvalidArgument(ErrorModule::NFP, 65); -constexpr Result WrongApplicationAreaSize(ErrorModule::NFP, 68); -constexpr Result WrongDeviceState(ErrorModule::NFP, 73); -constexpr Result NfcDisabled(ErrorModule::NFP, 80); -constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88); -constexpr Result TagRemoved(ErrorModule::NFP, 97); -constexpr Result RegistrationIsNotInitialized(ErrorModule::NFP, 120); -constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); -constexpr Result CorruptedData(ErrorModule::NFP, 144); -constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152); -constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168); -constexpr Result NotAnAmiibo(ErrorModule::NFP, 178); +constexpr Result ResultDeviceNotFound(ErrorModule::NFP, 64); +constexpr Result ResultInvalidArgument(ErrorModule::NFP, 65); +constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFP, 68); +constexpr Result ResultWrongDeviceState(ErrorModule::NFP, 73); +constexpr Result ResultUnknown74(ErrorModule::NFC, 74); +constexpr Result ResultNfcDisabled(ErrorModule::NFP, 80); +constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88); +constexpr Result ResultTagRemoved(ErrorModule::NFP, 97); +constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120); +constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); +constexpr Result ResultCorruptedData(ErrorModule::NFP, 144); +constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152); +constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168); +constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178); } // namespace Service::NFP diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h index 1ef047cee..7d36d5ee6 100644 --- a/src/core/hle/service/nfp/nfp_types.h +++ b/src/core/hle/service/nfp/nfp_types.h @@ -7,32 +7,19 @@ #include "common/swap.h" #include "core/hle/service/mii/types.h" +#include "core/hle/service/nfc/nfc_types.h" namespace Service::NFP { static constexpr std::size_t amiibo_name_length = 0xA; static constexpr std::size_t application_id_version_offset = 0x1c; static constexpr std::size_t counter_limit = 0xffff; -enum class ServiceType : u32 { - User, - Debug, - System, -}; - -enum class DeviceState : u32 { - Initialized, - SearchingForTag, - TagFound, - TagRemoved, - TagMounted, - Unavailable, - Finalized, -}; - +// This is nn::nfp::ModelType enum class ModelType : u32 { Amiibo, }; +// This is nn::nfp::MountTarget enum class MountTarget : u32 { None, Rom, @@ -72,35 +59,6 @@ enum class AmiiboSeries : u8 { Diablo, }; -enum class TagType : u32 { - None, - Type1, // ISO14443A RW 96-2k bytes 106kbit/s - Type2, // ISO14443A RW/RO 540 bytes 106kbit/s - Type3, // Sony Felica RW/RO 2k bytes 212kbit/s - Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s - Type5, // ISO15693 RW/RO 540 bytes 106kbit/s -}; - -enum class PackedTagType : u8 { - None, - Type1, // ISO14443A RW 96-2k bytes 106kbit/s - Type2, // ISO14443A RW/RO 540 bytes 106kbit/s - Type3, // Sony Felica RW/RO 2k bytes 212kbit/s - Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s - Type5, // ISO15693 RW/RO 540 bytes 106kbit/s -}; - -// Verify this enum. It might be completely wrong default protocol is 0x48 -enum class TagProtocol : u32 { - None, - TypeA = 1U << 0, // ISO14443A - TypeB = 1U << 1, // ISO14443B - TypeF = 1U << 2, // Sony Felica - Unknown1 = 1U << 3, - Unknown2 = 1U << 5, - All = 0xFFFFFFFFU, -}; - enum class AppAreaVersion : u8 { Nintendo3DS = 0, NintendoWiiU = 1, @@ -115,6 +73,11 @@ enum class BreakType : u32 { Unknown2, }; +enum class WriteType : u32 { + Unknown0, + Unknown1, +}; + enum class CabinetMode : u8 { StartNicknameAndOwnerSettings, StartGameDataEraser, @@ -122,27 +85,16 @@ enum class CabinetMode : u8 { StartFormatter, }; -enum class MifareCmd : u8 { - AuthA = 0x60, - AuthB = 0x61, - Read = 0x30, - Write = 0xA0, - Transfer = 0xB0, - Decrement = 0xC0, - Increment = 0xC1, - Store = 0xC2 -}; - -using UniqueSerialNumber = std::array<u8, 7>; using LockBytes = std::array<u8, 2>; using HashData = std::array<u8, 0x20>; using ApplicationArea = std::array<u8, 0xD8>; using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; -using DataBlock = std::array<u8, 0x10>; -using KeyData = std::array<u8, 0x6>; + +// This is nn::nfp::TagInfo +using TagInfo = NFC::TagInfo; struct TagUuid { - UniqueSerialNumber uid; + NFC::UniqueSerialNumber uid; u8 nintendo_id; LockBytes lock_bytes; }; @@ -243,7 +195,7 @@ struct AmiiboModelInfo { AmiiboType amiibo_type; u16_be model_number; AmiiboSeries series; - PackedTagType tag_type; + NFC::PackedTagType tag_type; INSERT_PADDING_BYTES(0x4); // Unknown }; static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size"); @@ -298,7 +250,7 @@ struct NTAG215File { u32_be register_info_crc; ApplicationArea application_area; // Encrypted Game data HashData hmac_tag; // Hash - UniqueSerialNumber uid; // Unique serial number + NFC::UniqueSerialNumber uid; // Unique serial number u8 nintendo_id; // Tag UUID AmiiboModelInfo model_info; HashData keygen_salt; // Salt @@ -326,17 +278,7 @@ static_assert(sizeof(EncryptedNTAG215File) == sizeof(NTAG215File), static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>, "EncryptedNTAG215File must be trivially copyable."); -struct TagInfo { - UniqueSerialNumber uuid; - INSERT_PADDING_BYTES(0x3); - u8 uuid_length; - INSERT_PADDING_BYTES(0x15); - TagProtocol protocol; - TagType tag_type; - INSERT_PADDING_BYTES(0x30); -}; -static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size"); - +// This is nn::nfp::CommonInfo struct CommonInfo { WriteDate last_write_date; u16 write_counter; @@ -347,6 +289,7 @@ struct CommonInfo { }; static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size"); +// This is nn::nfp::ModelInfo struct ModelInfo { u16 character_id; u8 character_variant; @@ -357,6 +300,7 @@ struct ModelInfo { }; static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size"); +// This is nn::nfp::RegisterInfo struct RegisterInfo { Service::Mii::CharInfo mii_char_info; WriteDate creation_date; @@ -366,6 +310,7 @@ struct RegisterInfo { }; static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size"); +// This is nn::nfp::RegisterInfoPrivate struct RegisterInfoPrivate { Service::Mii::MiiStoreData mii_store_data; WriteDate creation_date; @@ -375,12 +320,13 @@ struct RegisterInfoPrivate { }; static_assert(sizeof(RegisterInfoPrivate) == 0x100, "RegisterInfoPrivate is an invalid size"); +// This is nn::nfp::AdminInfo struct AdminInfo { u64 application_id; u32 application_area_id; u16 crc_change_counter; u8 flags; - PackedTagType tag_type; + NFC::PackedTagType tag_type; AppAreaVersion app_area_version; INSERT_PADDING_BYTES(0x7); INSERT_PADDING_BYTES(0x28); @@ -411,7 +357,7 @@ struct NfpData { u32 access_id; u16 settings_crc_counter; u8 font_region; - PackedTagType tag_type; + NFC::PackedTagType tag_type; AppAreaVersion console_type; u8 application_id_byte; INSERT_PADDING_BYTES(0x2E); @@ -420,37 +366,4 @@ struct NfpData { static_assert(sizeof(NfpData) == 0x298, "NfpData is an invalid size"); #pragma pack() -struct SectorKey { - MifareCmd command; - u8 unknown; // Usually 1 - INSERT_PADDING_BYTES(0x6); - KeyData sector_key; - INSERT_PADDING_BYTES(0x2); -}; -static_assert(sizeof(SectorKey) == 0x10, "SectorKey is an invalid size"); - -struct MifareReadBlockParameter { - u8 sector_number; - INSERT_PADDING_BYTES(0x7); - SectorKey sector_key; -}; -static_assert(sizeof(MifareReadBlockParameter) == 0x18, - "MifareReadBlockParameter is an invalid size"); - -struct MifareReadBlockData { - DataBlock data; - u8 sector_number; - INSERT_PADDING_BYTES(0x7); -}; -static_assert(sizeof(MifareReadBlockData) == 0x18, "MifareReadBlockData is an invalid size"); - -struct MifareWriteBlockParameter { - DataBlock data; - u8 sector_number; - INSERT_PADDING_BYTES(0x7); - SectorKey sector_key; -}; -static_assert(sizeof(MifareWriteBlockParameter) == 0x28, - "MifareWriteBlockParameter is an invalid size"); - } // namespace Service::NFP diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 0c042f412..91d42853e 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -218,7 +218,7 @@ public: private: void Submit(HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + LOG_DEBUG(Service_NIFM, "(STUBBED) called"); if (state == RequestState::NotSubmitted) { UpdateState(RequestState::OnHold); @@ -229,7 +229,7 @@ private: } void GetRequestState(HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + LOG_DEBUG(Service_NIFM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); @@ -237,7 +237,7 @@ private: } void GetResult(HLERequestContext& ctx) { - LOG_WARNING(Service_NIFM, "(STUBBED) called"); + LOG_DEBUG(Service_NIFM, "(STUBBED) called"); const auto result = [this] { const auto has_connection = Network::GetHostIPv4Address().has_value(); diff --git a/src/core/hle/service/server_manager.cpp b/src/core/hle/service/server_manager.cpp index 6b4a1291e..156bc27d8 100644 --- a/src/core/hle/service/server_manager.cpp +++ b/src/core/hle/service/server_manager.cpp @@ -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; diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 0f79a1b7e..45b2c43b7 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -142,7 +142,8 @@ template <typename Self> class ServiceFramework : public ServiceFrameworkBase { protected: /// Contains information about a request type which is handled by the service. - struct FunctionInfo : FunctionInfoBase { + template <typename T> + struct FunctionInfoTyped : FunctionInfoBase { // TODO(yuriks): This function could be constexpr, but clang is the only compiler that // doesn't emit an ICE or a wrong diagnostic because of the static_cast. @@ -155,12 +156,13 @@ protected: * the request * @param name_ human-friendly name for the request. Used mostly for logging purposes. */ - FunctionInfo(u32 expected_header_, HandlerFnP<Self> handler_callback_, const char* name_) + FunctionInfoTyped(u32 expected_header_, HandlerFnP<T> handler_callback_, const char* name_) : FunctionInfoBase{ expected_header_, // Type-erase member function pointer by casting it down to the base class. static_cast<HandlerFnP<ServiceFrameworkBase>>(handler_callback_), name_} {} }; + using FunctionInfo = FunctionInfoTyped<Self>; /** * Initializes the handler with no functions installed. @@ -175,8 +177,8 @@ protected: : ServiceFrameworkBase(system_, service_name_, max_sessions_, Invoker) {} /// Registers handlers in the service. - template <std::size_t N> - void RegisterHandlers(const FunctionInfo (&functions)[N]) { + template <typename T = Self, std::size_t N> + void RegisterHandlers(const FunctionInfoTyped<T> (&functions)[N]) { RegisterHandlers(functions, N); } @@ -184,13 +186,14 @@ protected: * Registers handlers in the service. Usually prefer using the other RegisterHandlers * overload in order to avoid needing to specify the array size. */ - void RegisterHandlers(const FunctionInfo* functions, std::size_t n) { + template <typename T = Self> + void RegisterHandlers(const FunctionInfoTyped<T>* functions, std::size_t n) { RegisterHandlersBase(functions, n); } /// Registers handlers in the service. - template <std::size_t N> - void RegisterHandlersTipc(const FunctionInfo (&functions)[N]) { + template <typename T = Self, std::size_t N> + void RegisterHandlersTipc(const FunctionInfoTyped<T> (&functions)[N]) { RegisterHandlersTipc(functions, N); } @@ -198,7 +201,8 @@ protected: * Registers handlers in the service. Usually prefer using the other RegisterHandlers * overload in order to avoid needing to specify the array size. */ - void RegisterHandlersTipc(const FunctionInfo* functions, std::size_t n) { + template <typename T = Self> + void RegisterHandlersTipc(const FunctionInfoTyped<T>* functions, std::size_t n) { RegisterHandlersBaseTipc(functions, n); } diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index c45be5726..1608fa24c 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -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) { diff --git a/src/core/hle/service/sm/sm_controller.cpp b/src/core/hle/service/sm/sm_controller.cpp index 419c1df2b..7dce28fe0 100644 --- a/src/core/hle/service/sm/sm_controller.cpp +++ b/src/core/hle/service/sm/sm_controller.cpp @@ -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); diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index bf97b0ebc..75ac10a9c 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp @@ -356,7 +356,7 @@ NetworkInstance::~NetworkInstance() { std::optional<IPv4Address> GetHostIPv4Address() { const auto network_interface = Network::GetSelectedNetworkInterface(); if (!network_interface.has_value()) { - LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface"); + LOG_DEBUG(Network, "GetSelectedNetworkInterface returned no interface"); return {}; } diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp index 7b8e510a2..4c909a6d3 100644 --- a/src/core/internal_network/network_interface.cpp +++ b/src/core/internal_network/network_interface.cpp @@ -200,7 +200,7 @@ std::optional<NetworkInterface> GetSelectedNetworkInterface() { }); if (res == network_interfaces.end()) { - LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); + LOG_DEBUG(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); return std::nullopt; } diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 432310632..514ba0d66 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -13,10 +13,12 @@ #include "common/swap.h" #include "core/core.h" #include "core/device_memory.h" +#include "core/hardware_properties.h" #include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/k_process.h" #include "core/memory.h" #include "video_core/gpu.h" +#include "video_core/rasterizer_download_area.h" namespace Core::Memory { @@ -243,7 +245,7 @@ struct Memory::Impl { [&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount, const u8* const host_ptr) { if constexpr (!UNSAFE) { - system.GPU().FlushRegion(GetInteger(current_vaddr), copy_amount); + HandleRasterizerDownload(GetInteger(current_vaddr), copy_amount); } std::memcpy(dest_buffer, host_ptr, copy_amount); }, @@ -334,7 +336,7 @@ struct Memory::Impl { }, [&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount, u8* const host_ptr) { - system.GPU().FlushRegion(GetInteger(current_vaddr), copy_amount); + HandleRasterizerDownload(GetInteger(current_vaddr), copy_amount); WriteBlockImpl<false>(process, dest_addr, host_ptr, copy_amount); }, [&](const std::size_t copy_amount) { @@ -373,7 +375,7 @@ struct Memory::Impl { const std::size_t block_size) { // dc ivac: Invalidate to point of coherency // GPU flush -> CPU invalidate - system.GPU().FlushRegion(GetInteger(current_vaddr), block_size); + HandleRasterizerDownload(GetInteger(current_vaddr), block_size); }; return PerformCacheOperation(process, dest_addr, size, on_rasterizer); } @@ -462,7 +464,8 @@ struct Memory::Impl { } if (Settings::IsFastmemEnabled()) { - const bool is_read_enable = Settings::IsGPULevelHigh() || !cached; + const bool is_read_enable = + !Settings::values.use_reactive_flushing.GetValue() || !cached; system.DeviceMemory().buffer.Protect(vaddr, size, is_read_enable, !cached); } @@ -651,7 +654,7 @@ struct Memory::Impl { LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, GetInteger(vaddr)); }, - [&]() { system.GPU().FlushRegion(GetInteger(vaddr), sizeof(T)); }); + [&]() { HandleRasterizerDownload(GetInteger(vaddr), sizeof(T)); }); if (ptr) { std::memcpy(&result, ptr, sizeof(T)); } @@ -712,7 +715,19 @@ struct Memory::Impl { return true; } + void HandleRasterizerDownload(VAddr address, size_t size) { + const size_t core = system.GetCurrentHostThreadID(); + auto& current_area = rasterizer_areas[core]; + const VAddr end_address = address + size; + if (current_area.start_address <= address && end_address <= current_area.end_address) + [[likely]] { + return; + } + current_area = system.GPU().OnCPURead(address, size); + } + Common::PageTable* current_page_table = nullptr; + std::array<VideoCore::RasterizerDownloadArea, Core::Hardware::NUM_CPU_CORES> rasterizer_areas{}; Core::System& system; }; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 9178b00ca..7a2f3c90a 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -85,6 +85,20 @@ static const char* TranslateNvdecEmulation(Settings::NvdecEmulation backend) { return "Unknown"; } +static constexpr const char* TranslateVSyncMode(Settings::VSyncMode mode) { + switch (mode) { + case Settings::VSyncMode::Immediate: + return "Immediate"; + case Settings::VSyncMode::Mailbox: + return "Mailbox"; + case Settings::VSyncMode::FIFO: + return "FIFO"; + case Settings::VSyncMode::FIFORelaxed: + return "FIFO Relaxed"; + } + return "Unknown"; +} + u64 GetTelemetryId() { u64 telemetry_id{}; const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id"; @@ -241,7 +255,8 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader, AddField(field_type, "Renderer_NvdecEmulation", TranslateNvdecEmulation(Settings::values.nvdec_emulation.GetValue())); AddField(field_type, "Renderer_AccelerateASTC", Settings::values.accelerate_astc.GetValue()); - AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue()); + AddField(field_type, "Renderer_UseVsync", + TranslateVSyncMode(Settings::values.vsync_mode.GetValue())); AddField(field_type, "Renderer_ShaderBackend", static_cast<u32>(Settings::values.shader_backend.GetValue())); AddField(field_type, "Renderer_UseAsynchronousShaders", diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp index 2b42a4555..077d72cd0 100644 --- a/src/input_common/helpers/joycon_protocol/common_protocol.cpp +++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp @@ -236,13 +236,13 @@ DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode, return DriverResult::Success; } -DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc, +DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, MCUSubCommand sc, std::span<const u8> buffer, MCUCommandResponse& output) { SubCommandPacket packet{ .output_report = OutputReport::MCU_DATA, .packet_counter = GetCounter(), - .sub_command = sc, + .mcu_sub_command = sc, .command_data = {}, }; @@ -269,8 +269,7 @@ DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMod std::size_t tries{}; do { - const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)}; - const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output); + const auto result = SendMCUData(report_mode, MCUSubCommand::SetDeviceMode, {}, output); if (result != DriverResult::Success) { return result; diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h index 62cae739a..411ec018a 100644 --- a/src/input_common/helpers/joycon_protocol/common_protocol.h +++ b/src/input_common/helpers/joycon_protocol/common_protocol.h @@ -156,7 +156,7 @@ public: * @param buffer data to be send * @returns output buffer containing the response */ - DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer, + DriverResult SendMCUData(ReportMode report_mode, MCUSubCommand sc, std::span<const u8> buffer, MCUCommandResponse& output); /** diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h index dcac0e422..b03143e04 100644 --- a/src/input_common/helpers/joycon_protocol/joycon_types.h +++ b/src/input_common/helpers/joycon_protocol/joycon_types.h @@ -575,7 +575,6 @@ struct NFCPollingCommandData { static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size"); struct NFCRequestState { - MCUSubCommand sub_command; NFCReadCommand command_argument; u8 packet_id; INSERT_PADDING_BYTES(0x1); @@ -587,6 +586,7 @@ struct NFCRequestState { NFCPollingCommandData nfc_polling; }; u8 crc; + INSERT_PADDING_BYTES(0x1); }; static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size"); @@ -659,7 +659,10 @@ struct SubCommandPacket { OutputReport output_report; u8 packet_counter; INSERT_PADDING_BYTES(0x8); // This contains vibration data - SubCommand sub_command; + union { + SubCommand sub_command; + MCUSubCommand mcu_sub_command; + }; std::array<u8, 0x26> command_data; }; static_assert(sizeof(SubCommandPacket) == 0x31, "SubCommandPacket is an invalid size"); diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp index eeba82986..77ea6d5cf 100644 --- a/src/input_common/helpers/joycon_protocol/nfc.cpp +++ b/src/input_common/helpers/joycon_protocol/nfc.cpp @@ -278,7 +278,6 @@ DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) { DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output) { NFCRequestState request{ - .sub_command = MCUSubCommand::ReadDeviceMode, .command_argument = NFCReadCommand::StartPolling, .packet_id = 0x0, .packet_flag = MCUPacketFlag::LastCommandPacket, @@ -296,13 +295,13 @@ DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output) { std::array<u8, sizeof(NFCRequestState)> request_data{}; memcpy(request_data.data(), &request, sizeof(NFCRequestState)); - request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); - return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); + request_data[36] = CalculateMCU_CRC8(request_data.data(), 36); + return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data, + output); } DriverResult NfcProtocol::SendStopPollingRequest(MCUCommandResponse& output) { NFCRequestState request{ - .sub_command = MCUSubCommand::ReadDeviceMode, .command_argument = NFCReadCommand::StopPolling, .packet_id = 0x0, .packet_flag = MCUPacketFlag::LastCommandPacket, @@ -313,13 +312,13 @@ DriverResult NfcProtocol::SendStopPollingRequest(MCUCommandResponse& output) { std::array<u8, sizeof(NFCRequestState)> request_data{}; memcpy(request_data.data(), &request, sizeof(NFCRequestState)); - request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); - return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); + request_data[36] = CalculateMCU_CRC8(request_data.data(), 36); + return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data, + output); } DriverResult NfcProtocol::SendStartWaitingRecieveRequest(MCUCommandResponse& output) { NFCRequestState request{ - .sub_command = MCUSubCommand::ReadDeviceMode, .command_argument = NFCReadCommand::StartWaitingRecieve, .packet_id = 0x0, .packet_flag = MCUPacketFlag::LastCommandPacket, @@ -330,13 +329,13 @@ DriverResult NfcProtocol::SendStartWaitingRecieveRequest(MCUCommandResponse& out std::vector<u8> request_data(sizeof(NFCRequestState)); memcpy(request_data.data(), &request, sizeof(NFCRequestState)); - request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); - return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); + request_data[36] = CalculateMCU_CRC8(request_data.data(), 36); + return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data, + output); } DriverResult NfcProtocol::SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages) { NFCRequestState request{ - .sub_command = MCUSubCommand::ReadDeviceMode, .command_argument = NFCReadCommand::Ntag, .packet_id = 0x0, .packet_flag = MCUPacketFlag::LastCommandPacket, @@ -355,8 +354,9 @@ DriverResult NfcProtocol::SendReadAmiiboRequest(MCUCommandResponse& output, NFCP std::array<u8, sizeof(NFCRequestState)> request_data{}; memcpy(request_data.data(), &request, sizeof(NFCRequestState)); - request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36); - return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output); + request_data[36] = CalculateMCU_CRC8(request_data.data(), 36); + return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data, + output); } NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const { diff --git a/src/input_common/input_mapping.cpp b/src/input_common/input_mapping.cpp index 9361b00c5..8c2ee4eb3 100644 --- a/src/input_common/input_mapping.cpp +++ b/src/input_common/input_mapping.cpp @@ -82,6 +82,9 @@ void MappingFactory::RegisterButton(const MappingData& data) { new_input.Set("axis", data.index); new_input.Set("threshold", 0.5f); break; + case EngineInputType::Motion: + new_input.Set("motion", data.index); + break; default: return; } diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index 8c6a6521a..380a01542 100644 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp @@ -667,7 +667,7 @@ public: .raw_value = input_engine->GetAxis(identifier, axis_z), .properties = properties_z, }; - status.delta_timestamp = 5000; + status.delta_timestamp = 1000; status.force_update = true; return status; } @@ -939,6 +939,7 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice( .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), .inverted = params.Get("invert", "+") == "-", + .inverted_button = params.Get("inverted", false) != 0, .toggle = params.Get("toggle", false) != 0, }; input_engine->PreSetController(identifier); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index 0cd87a48f..fee510f7b 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -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) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index d48d4860e..47739794f 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -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; diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 39b774c98..1e158f375 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -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 ) diff --git a/src/tests/video_core/buffer_base.cpp b/src/tests/video_core/buffer_base.cpp deleted file mode 100644 index 734dbf4b6..000000000 --- a/src/tests/video_core/buffer_base.cpp +++ /dev/null @@ -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); -} diff --git a/src/tests/video_core/memory_tracker.cpp b/src/tests/video_core/memory_tracker.cpp new file mode 100644 index 000000000..618793668 --- /dev/null +++ b/src/tests/video_core/memory_tracker.cpp @@ -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 == 0); + 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); +}
\ No newline at end of file diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index e904573d7..a0009a36f 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -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 diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h index 1b4d63616..0bb3bf8ae 100644 --- a/src/video_core/buffer_cache/buffer_base.h +++ b/src/video_core/buffer_cache/buffer_base.h @@ -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,15 +11,14 @@ #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 { enum class BufferFlagBits { Picked = 1 << 0, CachedWrites = 1 << 1, + PreemtiveDownload = 1 << 2, }; DECLARE_ENUM_FLAG_OPERATORS(BufferFlagBits) @@ -36,116 +35,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,105 +50,15 @@ 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; } + void MarkPreemtiveDownload() noexcept { + flags |= BufferFlagBits::PreemtiveDownload; + } + /// Unmark buffer as picked void Unpick() noexcept { flags &= ~BufferFlagBits::Picked; @@ -284,6 +89,10 @@ public: return True(flags & BufferFlagBits::CachedWrites); } + bool IsPreemtiveDownload() const noexcept { + return True(flags & BufferFlagBits::PreemtiveDownload); + } + /// Returns the base CPU address of the buffer [[nodiscard]] VAddr CpuAddr() const noexcept { return cpu_addr; @@ -295,11 +104,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 +112,16 @@ public: lru_id = lru_id_; } -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(); + size_t SizeBytes() const { + return size_bytes; } - /// Returns true when the buffer fits in the small vector optimization - [[nodiscard]] bool IsShort() const noexcept { - return words.IsShort(); - } - - RasterizerInterface* rasterizer = nullptr; +private: 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 diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index a16308b60..40db243d2 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -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" diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index abdc593df..6624919a4 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -1,485 +1,27 @@ -// SPDX-FileCopyrightText: Copyright 2019 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 #include <algorithm> -#include <array> #include <memory> -#include <mutex> #include <numeric> -#include <span> -#include <vector> - -#include <boost/container/small_vector.hpp> -#include <boost/icl/interval_set.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/polyfill_ranges.h" -#include "common/scratch_buffer.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 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; - -enum class ObtainBufferSynchronize : u32 { - NoSynchronize = 0, - FullSynchronize = 1, - SynchronizeNoDirty = 2, -}; - -enum class ObtainBufferOperation : u32 { - DoNothing = 0, - MarkAsWritten = 1, - DiscardWrite = 2, - MarkQuery = 3, -}; - -using UniformBufferSizes = std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES>; -using ComputeUniformBufferSizes = std::array<u32, NUM_COMPUTE_UNIFORM_BUFFERS>; - -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 YUZU_PAGEBITS = 16; - static constexpr u64 YUZU_PAGESIZE = u64{1} << YUZU_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 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; - - using Maxwell = Tegra::Engines::Maxwell3D::Regs; - - using Runtime = typename P::Runtime; - using Buffer = typename P::Buffer; - - using IntervalSet = boost::icl::interval_set<VAddr>; - using IntervalType = typename IntervalSet::interval_type; - - 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); - - 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(); - - bool DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount); - - bool DMAClear(GPUVAddr src_address, u64 amount, u32 value); - - [[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(GPUVAddr gpu_addr, u32 size, - ObtainBufferSynchronize sync_info, - ObtainBufferOperation post_op); - - /// 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, YUZU_PAGESIZE); - for (u64 page = cpu_addr >> YUZU_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, YUZU_PAGESIZE); - } - } - - template <typename Func> - void ForEachWrittenRange(VAddr cpu_addr, u64 size, Func&& func) { - const VAddr start_address = cpu_addr; - const VAddr end_address = start_address + size; - const VAddr search_base = - static_cast<VAddr>(std::min<s64>(0LL, static_cast<s64>(start_address - size))); - const IntervalType search_interval{search_base, search_base + 1}; - auto it = common_ranges.lower_bound(search_interval); - if (it == common_ranges.end()) { - it = common_ranges.begin(); - } - for (; it != common_ranges.end(); it++) { - VAddr inter_addr_end = it->upper(); - VAddr inter_addr = it->lower(); - if (inter_addr >= end_address) { - break; - } - if (inter_addr_end <= start_address) { - continue; - } - 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); - } - } - - 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 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); - - 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); - - void NotifyBufferDeletion(); - - [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index, - bool is_written = false) 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{}; +#include "video_core/buffer_cache/buffer_cache_base.h" - std::vector<BufferId> cached_write_buffer_ids; - - IntervalSet uncommitted_ranges; - IntervalSet common_ranges; - std::deque<IntervalSet> committed_ranges; - - 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; +namespace VideoCommon { - std::array<BufferId, ((1ULL << 39) >> YUZU_PAGEBITS)> page_table; -}; +using Core::Memory::YUZU_PAGESIZE; template <class P> BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_, Core::Memory::Memory& cpu_memory_, Runtime& runtime_) - : runtime{runtime_}, rasterizer{rasterizer_}, cpu_memory{cpu_memory_} { + : runtime{runtime_}, rasterizer{rasterizer_}, cpu_memory{cpu_memory_}, memory_tracker{ + rasterizer} { // Ensure the first slot is used for the null buffer void(slot_buffers.insert(runtime, NullBufferParams{})); common_ranges.clear(); + inline_buffer_id = NULL_BUFFER_ID; if (!runtime.CanReportMemoryUsage()) { minimum_memory = DEFAULT_EXPECTED_MEMORY; @@ -543,35 +85,78 @@ void BufferCache<P>::TickFrame() { } ++frame_tick; delayed_destruction_ring.Tick(); + + if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) { + for (auto& buffer : async_buffers_death_ring) { + runtime.FreeDeferredStagingBuffer(buffer); + } + async_buffers_death_ring.clear(); + } } template <class P> void BufferCache<P>::WriteMemory(VAddr cpu_addr, u64 size) { - ForEachBufferInRange(cpu_addr, size, [&](BufferId, Buffer& buffer) { - buffer.MarkRegionAsCpuModified(cpu_addr, size); - }); + memory_tracker.MarkRegionAsCpuModified(cpu_addr, size); + if (memory_tracker.IsRegionGpuModified(cpu_addr, size)) { + const IntervalType subtract_interval{cpu_addr, cpu_addr + size}; + ClearDownload(subtract_interval); + common_ranges.subtract(subtract_interval); + } } template <class P> void BufferCache<P>::CachedWriteMemory(VAddr cpu_addr, u64 size) { - ForEachBufferInRange(cpu_addr, size, [&](BufferId buffer_id, Buffer& buffer) { - if (!buffer.HasCachedWrites()) { - cached_write_buffer_ids.push_back(buffer_id); - } - buffer.CachedCpuWrite(cpu_addr, size); - }); + memory_tracker.CachedCpuWrite(cpu_addr, size); +} + +template <class P> +std::optional<VideoCore::RasterizerDownloadArea> BufferCache<P>::GetFlushArea(VAddr cpu_addr, + u64 size) { + std::optional<VideoCore::RasterizerDownloadArea> area{}; + area.emplace(); + VAddr cpu_addr_start_aligned = Common::AlignDown(cpu_addr, Core::Memory::YUZU_PAGESIZE); + VAddr cpu_addr_end_aligned = Common::AlignUp(cpu_addr + size, Core::Memory::YUZU_PAGESIZE); + area->start_address = cpu_addr_start_aligned; + area->end_address = cpu_addr_end_aligned; + if (memory_tracker.IsRegionPreflushable(cpu_addr, size)) { + area->preemtive = true; + return area; + }; + memory_tracker.MarkRegionAsPreflushable(cpu_addr_start_aligned, + cpu_addr_end_aligned - cpu_addr_start_aligned); + area->preemtive = !IsRegionGpuModified(cpu_addr, size); + return area; } template <class P> void BufferCache<P>::DownloadMemory(VAddr cpu_addr, u64 size) { + WaitOnAsyncFlushes(cpu_addr, size); ForEachBufferInRange(cpu_addr, size, [&](BufferId, Buffer& buffer) { DownloadBufferMemory(buffer, cpu_addr, size); }); } template <class P> +void BufferCache<P>::WaitOnAsyncFlushes(VAddr cpu_addr, u64 size) { + bool must_wait = false; + ForEachInOverlapCounter(async_downloads, cpu_addr, size, + [&](VAddr, VAddr, int) { must_wait = true; }); + bool must_release = false; + ForEachInRangeSet(pending_ranges, cpu_addr, size, [&](VAddr, VAddr) { must_release = true; }); + if (must_release) { + std::function<void()> tmp([]() {}); + rasterizer.SignalFence(std::move(tmp)); + } + if (must_wait || must_release) { + rasterizer.ReleaseFences(); + } +} + +template <class P> void BufferCache<P>::ClearDownload(IntervalType subtract_interval) { + RemoveEachInOverlapCounter(async_downloads, subtract_interval, -1024); uncommitted_ranges.subtract(subtract_interval); + pending_ranges.subtract(subtract_interval); for (auto& interval_set : committed_ranges) { interval_set.subtract(subtract_interval); } @@ -591,6 +176,7 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am } const IntervalType subtract_interval{*cpu_dest_address, *cpu_dest_address + amount}; + WaitOnAsyncFlushes(*cpu_src_address, static_cast<u32>(amount)); ClearDownload(subtract_interval); BufferId buffer_a; @@ -616,10 +202,11 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am const VAddr diff = base_address - *cpu_src_address; const VAddr new_base_address = *cpu_dest_address + diff; const IntervalType add_interval{new_base_address, new_base_address + size}; - uncommitted_ranges.add(add_interval); tmp_intervals.push_back(add_interval); + uncommitted_ranges.add(add_interval); + pending_ranges.add(add_interval); }; - ForEachWrittenRange(*cpu_src_address, amount, mirror); + ForEachInRangeSet(common_ranges, *cpu_src_address, amount, mirror); // This subtraction in this order is important for overlapping copies. common_ranges.subtract(subtract_interval); const bool has_new_downloads = tmp_intervals.size() != 0; @@ -628,9 +215,9 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am } runtime.CopyBuffer(dest_buffer, src_buffer, copies); if (has_new_downloads) { - dest_buffer.MarkRegionAsGpuModified(*cpu_dest_address, amount); + memory_tracker.MarkRegionAsGpuModified(*cpu_dest_address, amount); } - std::vector<u8> tmp_buffer(amount); + tmp_buffer.resize(amount); cpu_memory.ReadBlockUnsafe(*cpu_src_address, tmp_buffer.data(), amount); cpu_memory.WriteBlockUnsafe(*cpu_dest_address, tmp_buffer.data(), amount); return true; @@ -866,10 +453,7 @@ void BufferCache<P>::BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_add template <class P> void BufferCache<P>::FlushCachedWrites() { - for (const BufferId buffer_id : cached_write_buffer_ids) { - slot_buffers[buffer_id].FlushCachedWrites(); - } - cached_write_buffer_ids.clear(); + memory_tracker.FlushCachedWrites(); } template <class P> @@ -879,10 +463,6 @@ bool BufferCache<P>::HasUncommittedFlushes() const noexcept { template <class P> void BufferCache<P>::AccumulateFlushes() { - if (Settings::values.gpu_accuracy.GetValue() != Settings::GPUAccuracy::High) { - uncommitted_ranges.clear(); - return; - } if (uncommitted_ranges.empty()) { return; } @@ -891,7 +471,11 @@ void BufferCache<P>::AccumulateFlushes() { template <class P> bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept { - return false; + if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) { + return (!async_buffers.empty() && async_buffers.front().has_value()); + } else { + return false; + } } template <class P> @@ -899,12 +483,15 @@ void BufferCache<P>::CommitAsyncFlushesHigh() { AccumulateFlushes(); if (committed_ranges.empty()) { + if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) { + + async_buffers.emplace_back(std::optional<Async_Buffer>{}); + } return; } MICROPROFILE_SCOPE(GPU_DownloadMemory); - const bool is_accuracy_normal = - Settings::values.gpu_accuracy.GetValue() == Settings::GPUAccuracy::Normal; + pending_ranges.clear(); auto it = committed_ranges.begin(); while (it != committed_ranges.end()) { auto& current_intervals = *it; @@ -926,11 +513,12 @@ void BufferCache<P>::CommitAsyncFlushesHigh() { const std::size_t size = interval.upper() - interval.lower(); const VAddr cpu_addr = interval.lower(); ForEachBufferInRange(cpu_addr, size, [&](BufferId buffer_id, Buffer& buffer) { - buffer.ForEachDownloadRangeAndClear( - cpu_addr, size, [&](u64 range_offset, u64 range_size) { - if (is_accuracy_normal) { - return; - } + const VAddr buffer_start = buffer.CpuAddr(); + const VAddr buffer_end = buffer_start + buffer.SizeBytes(); + const VAddr new_start = std::max(buffer_start, cpu_addr); + const VAddr new_end = std::min(buffer_end, cpu_addr + size); + memory_tracker.ForEachDownloadRange( + new_start, new_end - new_start, false, [&](u64 cpu_addr_out, u64 range_size) { const VAddr buffer_addr = buffer.CpuAddr(); const auto add_download = [&](VAddr start, VAddr end) { const u64 new_offset = start - buffer_addr; @@ -944,92 +532,143 @@ void BufferCache<P>::CommitAsyncFlushesHigh() { buffer_id, }); // Align up to avoid cache conflicts - constexpr u64 align = 8ULL; + constexpr u64 align = 64ULL; constexpr u64 mask = ~(align - 1ULL); total_size_bytes += (new_size + align - 1) & mask; largest_copy = std::max(largest_copy, new_size); }; - const VAddr start_address = buffer_addr + range_offset; - const VAddr end_address = start_address + range_size; - ForEachWrittenRange(start_address, range_size, add_download); - const IntervalType subtract_interval{start_address, end_address}; - common_ranges.subtract(subtract_interval); + ForEachInRangeSet(common_ranges, cpu_addr_out, range_size, add_download); }); }); } } committed_ranges.clear(); if (downloads.empty()) { + if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) { + + async_buffers.emplace_back(std::optional<Async_Buffer>{}); + } return; } - if constexpr (USE_MEMORY_MAPS) { - auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes); + if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) { + auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes, true); + boost::container::small_vector<BufferCopy, 4> normalized_copies; + IntervalSet new_async_range{}; runtime.PreCopyBarrier(); for (auto& [copy, buffer_id] : downloads) { - // Have in mind the staging buffer offset for the copy copy.dst_offset += download_staging.offset; const std::array copies{copy}; - runtime.CopyBuffer(download_staging.buffer, slot_buffers[buffer_id], copies, false); + BufferCopy second_copy{copy}; + Buffer& buffer = slot_buffers[buffer_id]; + second_copy.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset; + VAddr orig_cpu_addr = static_cast<VAddr>(second_copy.src_offset); + const IntervalType base_interval{orig_cpu_addr, orig_cpu_addr + copy.size}; + async_downloads += std::make_pair(base_interval, 1); + runtime.CopyBuffer(download_staging.buffer, buffer, copies, false); + normalized_copies.push_back(second_copy); } runtime.PostCopyBarrier(); - runtime.Finish(); - for (const auto& [copy, buffer_id] : downloads) { - const Buffer& buffer = slot_buffers[buffer_id]; - const VAddr cpu_addr = buffer.CpuAddr() + copy.src_offset; - // Undo the modified offset - const u64 dst_offset = copy.dst_offset - download_staging.offset; - const u8* read_mapped_memory = download_staging.mapped_span.data() + dst_offset; - cpu_memory.WriteBlockUnsafe(cpu_addr, read_mapped_memory, copy.size); - } + pending_downloads.emplace_back(std::move(normalized_copies)); + async_buffers.emplace_back(download_staging); } else { - const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy); - for (const auto& [copy, buffer_id] : downloads) { - Buffer& buffer = slot_buffers[buffer_id]; - buffer.ImmediateDownload(copy.src_offset, immediate_buffer.subspan(0, copy.size)); - const VAddr cpu_addr = buffer.CpuAddr() + copy.src_offset; - cpu_memory.WriteBlockUnsafe(cpu_addr, immediate_buffer.data(), copy.size); + if (!Settings::IsGPULevelHigh()) { + committed_ranges.clear(); + uncommitted_ranges.clear(); + } else { + if constexpr (USE_MEMORY_MAPS) { + auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes); + runtime.PreCopyBarrier(); + for (auto& [copy, buffer_id] : downloads) { + // Have in mind the staging buffer offset for the copy + copy.dst_offset += download_staging.offset; + const std::array copies{copy}; + runtime.CopyBuffer(download_staging.buffer, slot_buffers[buffer_id], copies, + false); + } + runtime.PostCopyBarrier(); + runtime.Finish(); + for (const auto& [copy, buffer_id] : downloads) { + const Buffer& buffer = slot_buffers[buffer_id]; + const VAddr cpu_addr = buffer.CpuAddr() + copy.src_offset; + // Undo the modified offset + const u64 dst_offset = copy.dst_offset - download_staging.offset; + const u8* read_mapped_memory = download_staging.mapped_span.data() + dst_offset; + cpu_memory.WriteBlockUnsafe(cpu_addr, read_mapped_memory, copy.size); + } + } else { + const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy); + for (const auto& [copy, buffer_id] : downloads) { + Buffer& buffer = slot_buffers[buffer_id]; + buffer.ImmediateDownload(copy.src_offset, + immediate_buffer.subspan(0, copy.size)); + const VAddr cpu_addr = buffer.CpuAddr() + copy.src_offset; + cpu_memory.WriteBlockUnsafe(cpu_addr, immediate_buffer.data(), copy.size); + } + } } } } template <class P> void BufferCache<P>::CommitAsyncFlushes() { - if (Settings::values.gpu_accuracy.GetValue() == Settings::GPUAccuracy::High) { - CommitAsyncFlushesHigh(); - } else { - uncommitted_ranges.clear(); - committed_ranges.clear(); - } + CommitAsyncFlushesHigh(); } template <class P> -void BufferCache<P>::PopAsyncFlushes() {} +void BufferCache<P>::PopAsyncFlushes() { + MICROPROFILE_SCOPE(GPU_DownloadMemory); + PopAsyncBuffers(); +} template <class P> -bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) { - const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE); - for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) { - const BufferId image_id = page_table[page]; - if (!image_id) { - ++page; - continue; - } - Buffer& buffer = slot_buffers[image_id]; - if (buffer.IsRegionGpuModified(addr, size)) { - return true; +void BufferCache<P>::PopAsyncBuffers() { + if (async_buffers.empty()) { + return; + } + if (!async_buffers.front().has_value()) { + async_buffers.pop_front(); + return; + } + if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) { + auto& downloads = pending_downloads.front(); + auto& async_buffer = async_buffers.front(); + u8* base = async_buffer->mapped_span.data(); + const size_t base_offset = async_buffer->offset; + for (const auto& copy : downloads) { + const VAddr cpu_addr = static_cast<VAddr>(copy.src_offset); + const u64 dst_offset = copy.dst_offset - base_offset; + const u8* read_mapped_memory = base + dst_offset; + ForEachInOverlapCounter( + async_downloads, cpu_addr, copy.size, [&](VAddr start, VAddr end, int count) { + cpu_memory.WriteBlockUnsafe(start, &read_mapped_memory[start - cpu_addr], + end - start); + if (count == 1) { + const IntervalType base_interval{start, end}; + common_ranges.subtract(base_interval); + } + }); + const IntervalType subtract_interval{cpu_addr, cpu_addr + copy.size}; + RemoveEachInOverlapCounter(async_downloads, subtract_interval, -1); } - const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); - page = Common::DivCeil(end_addr, YUZU_PAGESIZE); + async_buffers_death_ring.emplace_back(*async_buffer); + async_buffers.pop_front(); + pending_downloads.pop_front(); } - return false; +} + +template <class P> +bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) { + bool is_dirty = false; + ForEachInRangeSet(common_ranges, addr, size, [&](VAddr, VAddr) { is_dirty = true; }); + return is_dirty; } template <class P> bool BufferCache<P>::IsRegionRegistered(VAddr addr, size_t size) { const VAddr end_addr = addr + size; - const u64 page_end = Common::DivCeil(end_addr, YUZU_PAGESIZE); - for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) { + const u64 page_end = Common::DivCeil(end_addr, CACHING_PAGESIZE); + for (u64 page = addr >> CACHING_PAGEBITS; page < page_end;) { const BufferId buffer_id = page_table[page]; if (!buffer_id) { ++page; @@ -1041,28 +680,14 @@ bool BufferCache<P>::IsRegionRegistered(VAddr addr, size_t size) { if (buf_start_addr < end_addr && addr < buf_end_addr) { return true; } - page = Common::DivCeil(end_addr, YUZU_PAGESIZE); + page = Common::DivCeil(end_addr, CACHING_PAGESIZE); } return false; } template <class P> bool BufferCache<P>::IsRegionCpuModified(VAddr addr, size_t size) { - const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE); - for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) { - const BufferId image_id = page_table[page]; - if (!image_id) { - ++page; - continue; - } - Buffer& buffer = slot_buffers[image_id]; - if (buffer.IsRegionCpuModified(addr, size)) { - return true; - } - const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); - page = Common::DivCeil(end_addr, YUZU_PAGESIZE); - } - return false; + return memory_tracker.IsRegionCpuModified(addr, size); } template <class P> @@ -1072,7 +697,7 @@ void BufferCache<P>::BindHostIndexBuffer() { const u32 offset = buffer.Offset(index_buffer.cpu_addr); const u32 size = index_buffer.size; const auto& draw_state = maxwell3d->draw_manager->GetDrawState(); - if (!draw_state.inline_index_draw_indexes.empty()) { + if (!draw_state.inline_index_draw_indexes.empty()) [[unlikely]] { if constexpr (USE_MEMORY_MAPS) { auto upload_staging = runtime.UploadStagingBuffer(size); std::array<BufferCopy, 1> copies{ @@ -1155,7 +780,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 TouchBuffer(buffer, binding.buffer_id); const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID && size <= uniform_buffer_skip_cache_size && - !buffer.IsRegionGpuModified(cpu_addr, size); + !memory_tracker.IsRegionGpuModified(cpu_addr, size); if (use_fast_buffer) { if constexpr (IS_OPENGL) { if (runtime.HasFastBufferSubData()) { @@ -1378,27 +1003,36 @@ void BufferCache<P>::UpdateIndexBuffer() { // We have to check for the dirty flags and index count // The index count is currently changed without updating the dirty flags const auto& draw_state = maxwell3d->draw_manager->GetDrawState(); - const auto& index_array = draw_state.index_buffer; + const auto& index_buffer_ref = draw_state.index_buffer; auto& flags = maxwell3d->dirty.flags; if (!flags[Dirty::IndexBuffer]) { return; } flags[Dirty::IndexBuffer] = false; - last_index_count = index_array.count; - if (!draw_state.inline_index_draw_indexes.empty()) { + if (!draw_state.inline_index_draw_indexes.empty()) [[unlikely]] { auto inline_index_size = static_cast<u32>(draw_state.inline_index_draw_indexes.size()); + u32 buffer_size = Common::AlignUp(inline_index_size, CACHING_PAGESIZE); + if (inline_buffer_id == NULL_BUFFER_ID) [[unlikely]] { + inline_buffer_id = CreateBuffer(0, buffer_size); + } + if (slot_buffers[inline_buffer_id].SizeBytes() < buffer_size) [[unlikely]] { + slot_buffers.erase(inline_buffer_id); + inline_buffer_id = CreateBuffer(0, buffer_size); + } index_buffer = Binding{ .cpu_addr = 0, .size = inline_index_size, - .buffer_id = CreateBuffer(0, inline_index_size), + .buffer_id = inline_buffer_id, }; return; } - const GPUVAddr gpu_addr_begin = index_array.StartAddress(); - const GPUVAddr gpu_addr_end = index_array.EndAddress(); + + const GPUVAddr gpu_addr_begin = index_buffer_ref.StartAddress(); + const GPUVAddr gpu_addr_end = index_buffer_ref.EndAddress(); const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin); const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); - const u32 draw_size = (index_array.count + index_array.first) * index_array.FormatSizeInBytes(); + const u32 draw_size = + (index_buffer_ref.count + index_buffer_ref.first) * index_buffer_ref.FormatSizeInBytes(); const u32 size = std::min(address_size, draw_size); if (size == 0 || !cpu_addr) { index_buffer = NULL_BINDING; @@ -1434,17 +1068,15 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) { const GPUVAddr gpu_addr_begin = array.Address(); const GPUVAddr gpu_addr_end = limit.Address() + 1; const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin); - u32 address_size = static_cast<u32>( - std::min(gpu_addr_end - gpu_addr_begin, static_cast<u64>(std::numeric_limits<u32>::max()))); - if (array.enable == 0 || address_size == 0 || !cpu_addr) { + const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); + u32 size = address_size; // TODO: Analyze stride and number of vertices + if (array.enable == 0 || size == 0 || !cpu_addr) { vertex_buffers[index] = NULL_BINDING; return; } if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end)) { - address_size = - static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, address_size)); + size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size)); } - const u32 size = address_size; // TODO: Analyze stride and number of vertices vertex_buffers[index] = Binding{ .cpu_addr = *cpu_addr, .size = size, @@ -1591,17 +1223,16 @@ void BufferCache<P>::UpdateComputeTextureBuffers() { template <class P> void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) { - Buffer& buffer = slot_buffers[buffer_id]; - buffer.MarkRegionAsGpuModified(cpu_addr, size); + memory_tracker.MarkRegionAsGpuModified(cpu_addr, size); + + if (memory_tracker.IsRegionCpuModified(cpu_addr, size)) { + SynchronizeBuffer(slot_buffers[buffer_id], cpu_addr, size); + } const IntervalType base_interval{cpu_addr, cpu_addr + size}; common_ranges.add(base_interval); - - const bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue(); - if (!is_async) { - return; - } uncommitted_ranges.add(base_interval); + pending_ranges.add(base_interval); } template <class P> @@ -1609,7 +1240,7 @@ BufferId BufferCache<P>::FindBuffer(VAddr cpu_addr, u32 size) { if (cpu_addr == 0) { return NULL_BUFFER_ID; } - const u64 page = cpu_addr >> YUZU_PAGEBITS; + const u64 page = cpu_addr >> CACHING_PAGEBITS; const BufferId buffer_id = page_table[page]; if (!buffer_id) { return CreateBuffer(cpu_addr, size); @@ -1638,9 +1269,9 @@ typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu .has_stream_leap = has_stream_leap, }; } - for (; cpu_addr >> YUZU_PAGEBITS < Common::DivCeil(end, YUZU_PAGESIZE); - cpu_addr += YUZU_PAGESIZE) { - const BufferId overlap_id = page_table[cpu_addr >> YUZU_PAGEBITS]; + for (; cpu_addr >> CACHING_PAGEBITS < Common::DivCeil(end, CACHING_PAGESIZE); + cpu_addr += CACHING_PAGESIZE) { + const BufferId overlap_id = page_table[cpu_addr >> CACHING_PAGEBITS]; if (!overlap_id) { continue; } @@ -1666,11 +1297,11 @@ typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu // as a stream buffer. Increase the size to skip constantly recreating buffers. has_stream_leap = true; if (expands_right) { - begin -= YUZU_PAGESIZE * 256; + begin -= CACHING_PAGESIZE * 256; cpu_addr = begin; } if (expands_left) { - end += YUZU_PAGESIZE * 256; + end += CACHING_PAGESIZE * 256; } } } @@ -1690,25 +1321,22 @@ void BufferCache<P>::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, if (accumulate_stream_score) { new_buffer.IncreaseStreamScore(overlap.StreamScore() + 1); } - std::vector<BufferCopy> copies; + boost::container::small_vector<BufferCopy, 1> copies; const size_t dst_base_offset = overlap.CpuAddr() - new_buffer.CpuAddr(); - overlap.ForEachDownloadRange([&](u64 begin, u64 range_size) { - copies.push_back(BufferCopy{ - .src_offset = begin, - .dst_offset = dst_base_offset + begin, - .size = range_size, - }); - new_buffer.UnmarkRegionAsCpuModified(begin, range_size); - new_buffer.MarkRegionAsGpuModified(begin, range_size); + copies.push_back(BufferCopy{ + .src_offset = 0, + .dst_offset = dst_base_offset, + .size = overlap.SizeBytes(), }); - if (!copies.empty()) { - runtime.CopyBuffer(slot_buffers[new_buffer_id], overlap, copies); - } - DeleteBuffer(overlap_id); + runtime.CopyBuffer(new_buffer, overlap, copies); + DeleteBuffer(overlap_id, true); } template <class P> BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) { + VAddr cpu_addr_end = Common::AlignUp(cpu_addr + wanted_size, CACHING_PAGESIZE); + cpu_addr = Common::AlignDown(cpu_addr, CACHING_PAGESIZE); + wanted_size = static_cast<u32>(cpu_addr_end - cpu_addr); const OverlapResult overlap = ResolveOverlaps(cpu_addr, wanted_size); const u32 size = static_cast<u32>(overlap.end - overlap.begin); const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size); @@ -1718,7 +1346,7 @@ BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) { JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap); } Register(new_buffer_id); - TouchBuffer(slot_buffers[new_buffer_id], new_buffer_id); + TouchBuffer(new_buffer, new_buffer_id); return new_buffer_id; } @@ -1746,8 +1374,8 @@ void BufferCache<P>::ChangeRegister(BufferId buffer_id) { } const VAddr cpu_addr_begin = buffer.CpuAddr(); const VAddr cpu_addr_end = cpu_addr_begin + size; - const u64 page_begin = cpu_addr_begin / YUZU_PAGESIZE; - const u64 page_end = Common::DivCeil(cpu_addr_end, YUZU_PAGESIZE); + const u64 page_begin = cpu_addr_begin / CACHING_PAGESIZE; + const u64 page_end = Common::DivCeil(cpu_addr_end, CACHING_PAGESIZE); for (u64 page = page_begin; page != page_end; ++page) { if constexpr (insert) { page_table[page] = buffer_id; @@ -1766,9 +1394,6 @@ void BufferCache<P>::TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept { template <class P> bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) { - if (buffer.CpuAddr() == 0) { - return true; - } return SynchronizeBufferImpl(buffer, cpu_addr, size); } @@ -1777,10 +1402,11 @@ bool BufferCache<P>::SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 s boost::container::small_vector<BufferCopy, 4> copies; u64 total_size_bytes = 0; u64 largest_copy = 0; - buffer.ForEachUploadRange(cpu_addr, size, [&](u64 range_offset, u64 range_size) { + VAddr buffer_start = buffer.CpuAddr(); + memory_tracker.ForEachUploadRange(cpu_addr, size, [&](u64 cpu_addr_out, u64 range_size) { copies.push_back(BufferCopy{ .src_offset = total_size_bytes, - .dst_offset = range_offset, + .dst_offset = cpu_addr_out - buffer_start, .size = range_size, }); total_size_bytes += range_size; @@ -1795,6 +1421,51 @@ bool BufferCache<P>::SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 s } template <class P> +bool BufferCache<P>::SynchronizeBufferNoModified(Buffer& buffer, VAddr cpu_addr, u32 size) { + boost::container::small_vector<BufferCopy, 4> copies; + u64 total_size_bytes = 0; + u64 largest_copy = 0; + IntervalSet found_sets{}; + auto make_copies = [&] { + for (auto& interval : found_sets) { + const std::size_t sub_size = interval.upper() - interval.lower(); + const VAddr cpu_addr_ = interval.lower(); + copies.push_back(BufferCopy{ + .src_offset = total_size_bytes, + .dst_offset = cpu_addr_ - buffer.CpuAddr(), + .size = sub_size, + }); + total_size_bytes += sub_size; + largest_copy = std::max<u64>(largest_copy, sub_size); + } + const std::span<BufferCopy> copies_span(copies.data(), copies.size()); + UploadMemory(buffer, total_size_bytes, largest_copy, copies_span); + }; + memory_tracker.ForEachUploadRange(cpu_addr, size, [&](u64 cpu_addr_out, u64 range_size) { + const VAddr base_adr = cpu_addr_out; + const VAddr end_adr = base_adr + range_size; + const IntervalType add_interval{base_adr, end_adr}; + found_sets.add(add_interval); + }); + if (found_sets.empty()) { + return true; + } + const IntervalType search_interval{cpu_addr, cpu_addr + size}; + auto it = common_ranges.lower_bound(search_interval); + auto it_end = common_ranges.upper_bound(search_interval); + if (it == common_ranges.end()) { + make_copies(); + return false; + } + while (it != it_end) { + found_sets.subtract(*it); + it++; + } + make_copies(); + return false; +} + +template <class P> void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy, std::span<BufferCopy> copies) { if constexpr (USE_MEMORY_MAPS) { @@ -1805,39 +1476,45 @@ void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 larg } template <class P> -void BufferCache<P>::ImmediateUploadMemory(Buffer& buffer, u64 largest_copy, - std::span<const BufferCopy> copies) { - std::span<u8> immediate_buffer; - for (const BufferCopy& copy : copies) { - std::span<const u8> upload_span; - const VAddr cpu_addr = buffer.CpuAddr() + copy.dst_offset; - if (IsRangeGranular(cpu_addr, copy.size)) { - upload_span = std::span(cpu_memory.GetPointer(cpu_addr), copy.size); - } else { - if (immediate_buffer.empty()) { - immediate_buffer = ImmediateBuffer(largest_copy); +void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer, + [[maybe_unused]] u64 largest_copy, + [[maybe_unused]] std::span<const BufferCopy> copies) { + if constexpr (!USE_MEMORY_MAPS) { + std::span<u8> immediate_buffer; + for (const BufferCopy& copy : copies) { + std::span<const u8> upload_span; + const VAddr cpu_addr = buffer.CpuAddr() + copy.dst_offset; + if (IsRangeGranular(cpu_addr, copy.size)) { + upload_span = std::span(cpu_memory.GetPointer(cpu_addr), copy.size); + } else { + if (immediate_buffer.empty()) { + immediate_buffer = ImmediateBuffer(largest_copy); + } + cpu_memory.ReadBlockUnsafe(cpu_addr, immediate_buffer.data(), copy.size); + upload_span = immediate_buffer.subspan(0, copy.size); } - cpu_memory.ReadBlockUnsafe(cpu_addr, immediate_buffer.data(), copy.size); - upload_span = immediate_buffer.subspan(0, copy.size); + buffer.ImmediateUpload(copy.dst_offset, upload_span); } - buffer.ImmediateUpload(copy.dst_offset, upload_span); } } template <class P> -void BufferCache<P>::MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, - std::span<BufferCopy> copies) { - auto upload_staging = runtime.UploadStagingBuffer(total_size_bytes); - const std::span<u8> staging_pointer = upload_staging.mapped_span; - for (BufferCopy& copy : copies) { - u8* const src_pointer = staging_pointer.data() + copy.src_offset; - const VAddr cpu_addr = buffer.CpuAddr() + copy.dst_offset; - cpu_memory.ReadBlockUnsafe(cpu_addr, src_pointer, copy.size); +void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer, + [[maybe_unused]] u64 total_size_bytes, + [[maybe_unused]] std::span<BufferCopy> copies) { + if constexpr (USE_MEMORY_MAPS) { + auto upload_staging = runtime.UploadStagingBuffer(total_size_bytes); + const std::span<u8> staging_pointer = upload_staging.mapped_span; + for (BufferCopy& copy : copies) { + u8* const src_pointer = staging_pointer.data() + copy.src_offset; + const VAddr cpu_addr = buffer.CpuAddr() + copy.dst_offset; + cpu_memory.ReadBlockUnsafe(cpu_addr, src_pointer, copy.size); - // Apply the staging offset - copy.src_offset += upload_staging.offset; + // Apply the staging offset + copy.src_offset += upload_staging.offset; + } + runtime.CopyBuffer(buffer, upload_staging.buffer, copies); } - runtime.CopyBuffer(buffer, upload_staging.buffer, copies); } template <class P> @@ -1847,7 +1524,9 @@ bool BufferCache<P>::InlineMemory(VAddr dest_address, size_t copy_size, if (!is_dirty) { return false; } - if (!IsRegionGpuModified(dest_address, copy_size)) { + VAddr aligned_start = Common::AlignDown(dest_address, YUZU_PAGESIZE); + VAddr aligned_end = Common::AlignUp(dest_address + copy_size, YUZU_PAGESIZE); + if (!IsRegionGpuModified(aligned_start, aligned_end - aligned_start)) { return false; } @@ -1886,30 +1565,31 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 si boost::container::small_vector<BufferCopy, 1> copies; u64 total_size_bytes = 0; u64 largest_copy = 0; - buffer.ForEachDownloadRangeAndClear(cpu_addr, size, [&](u64 range_offset, u64 range_size) { - const VAddr buffer_addr = buffer.CpuAddr(); - const auto add_download = [&](VAddr start, VAddr end) { - const u64 new_offset = start - buffer_addr; - const u64 new_size = end - start; - copies.push_back(BufferCopy{ - .src_offset = new_offset, - .dst_offset = total_size_bytes, - .size = new_size, - }); - // Align up to avoid cache conflicts - constexpr u64 align = 256ULL; - constexpr u64 mask = ~(align - 1ULL); - total_size_bytes += (new_size + align - 1) & mask; - largest_copy = std::max(largest_copy, new_size); - }; - - const VAddr start_address = buffer_addr + range_offset; - const VAddr end_address = start_address + range_size; - ForEachWrittenRange(start_address, range_size, add_download); - const IntervalType subtract_interval{start_address, end_address}; - ClearDownload(subtract_interval); - common_ranges.subtract(subtract_interval); - }); + memory_tracker.ForEachDownloadRangeAndClear( + cpu_addr, size, [&](u64 cpu_addr_out, u64 range_size) { + const VAddr buffer_addr = buffer.CpuAddr(); + const auto add_download = [&](VAddr start, VAddr end) { + const u64 new_offset = start - buffer_addr; + const u64 new_size = end - start; + copies.push_back(BufferCopy{ + .src_offset = new_offset, + .dst_offset = total_size_bytes, + .size = new_size, + }); + // Align up to avoid cache conflicts + constexpr u64 align = 64ULL; + constexpr u64 mask = ~(align - 1ULL); + total_size_bytes += (new_size + align - 1) & mask; + largest_copy = std::max(largest_copy, new_size); + }; + + const VAddr start_address = cpu_addr_out; + const VAddr end_address = start_address + range_size; + ForEachInRangeSet(common_ranges, start_address, range_size, add_download); + const IntervalType subtract_interval{start_address, end_address}; + ClearDownload(subtract_interval); + common_ranges.subtract(subtract_interval); + }); if (total_size_bytes == 0) { return; } @@ -1943,7 +1623,7 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 si } template <class P> -void BufferCache<P>::DeleteBuffer(BufferId buffer_id) { +void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) { const auto scalar_replace = [buffer_id](Binding& binding) { if (binding.buffer_id == buffer_id) { binding.buffer_id = BufferId{}; @@ -1959,11 +1639,12 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id) { replace(transform_feedback_buffers); replace(compute_uniform_buffers); replace(compute_storage_buffers); - std::erase(cached_write_buffer_ids, buffer_id); // Mark the whole buffer as CPU written to stop tracking CPU writes - Buffer& buffer = slot_buffers[buffer_id]; - buffer.MarkRegionAsCpuModified(buffer.CpuAddr(), buffer.SizeBytes()); + if (!do_not_mark) { + Buffer& buffer = slot_buffers[buffer_id]; + memory_tracker.MarkRegionAsCpuModified(buffer.CpuAddr(), buffer.SizeBytes()); + } Unregister(buffer_id); delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id])); @@ -2011,7 +1692,7 @@ typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr s LOG_WARNING(HW_GPU, "Failed to find storage buffer for cbuf index {}", cbuf_index); return NULL_BINDING; } - const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, Core::Memory::YUZU_PAGESIZE); + const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, YUZU_PAGESIZE); const Binding binding{ .cpu_addr = *cpu_addr, .size = is_written ? size : static_cast<u32>(cpu_end - *cpu_addr), diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h new file mode 100644 index 000000000..0445ec47f --- /dev/null +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -0,0 +1,579 @@ +// 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); + + std::optional<VideoCore::RasterizerDownloadArea> GetFlushArea(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{}; + + 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; + + std::array<BufferId, ((1ULL << 39) >> CACHING_PAGEBITS)> page_table; + std::vector<u8> tmp_buffer; +}; + +} // namespace VideoCommon diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker_base.h new file mode 100644 index 000000000..6036b21c9 --- /dev/null +++ b/src/video_core/buffer_cache/memory_tracker_base.h @@ -0,0 +1,299 @@ +// 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); + }); + } + + /// Returns true if a region has been marked as Preflushable + [[nodiscard]] bool IsRegionPreflushable(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::Preflushable>(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); + }); + } + + /// Mark region as modified from the host GPU + void MarkRegionAsPreflushable(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::Preflushable, 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); + }); + } + + /// Unmark region as modified from the host GPU + void UnmarkRegionAsPreflushable(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::Preflushable, 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 diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h new file mode 100644 index 000000000..a336bde41 --- /dev/null +++ b/src/video_core/buffer_cache/word_manager.h @@ -0,0 +1,485 @@ +// 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, + Preflushable, +}; + +/// 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}); + preflushable.stack.fill(0); + } else { + // Share allocation between CPU and GPU pages and set their default values + u64* const alloc = new u64[num_words * 5]; + cpu.heap = alloc; + gpu.heap = alloc + num_words; + cached_cpu.heap = alloc + num_words * 2; + untracked.heap = alloc + num_words * 3; + preflushable.heap = alloc + num_words * 4; + 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}); + std::fill_n(preflushable.heap, num_words, 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; + preflushable = rhs.preflushable; + 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}, preflushable{rhs.preflushable} { + 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); + } else if constexpr (type == Type::Preflushable) { + return std::span<u64>(preflushable.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); + } else if constexpr (type == Type::Preflushable) { + return std::span<const u64>(preflushable.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; + WordsArray<stack_words> preflushable; +}; + +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) { + if constexpr (type == Type::GPU) { + mask &= ~untracked_words[index]; + } + 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>(); + [[maybe_unused]] const std::span<const u64> untracked_words = + words.template Span<Type::Untracked>(); + bool result = false; + IterateWords(offset, size, [&](size_t index, u64 mask) { + if constexpr (type == Type::GPU) { + mask &= ~untracked_words[index]; + } + 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>(); + [[maybe_unused]] const std::span<const u64> untracked_words = + words.template Span<Type::Untracked>(); + u64 begin = std::numeric_limits<u64>::max(); + u64 end = 0; + IterateWords(offset, size, [&](size_t index, u64 mask) { + if constexpr (type == Type::GPU) { + mask &= ~untracked_words[index]; + } + 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 diff --git a/src/video_core/compatible_formats.cpp b/src/video_core/compatible_formats.cpp index 4e75f33ca..ab4f4d407 100644 --- a/src/video_core/compatible_formats.cpp +++ b/src/video_core/compatible_formats.cpp @@ -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; } diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index e68850dc5..ebe5536de 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -223,7 +223,7 @@ void MaxwellDMA::CopyBlockLinearToPitch() { write_buffer.resize_destructive(dst_size); memory_manager.ReadBlock(src_operand.address, read_buffer.data(), src_size); - memory_manager.ReadBlockUnsafe(dst_operand.address, write_buffer.data(), dst_size); + memory_manager.ReadBlock(dst_operand.address, write_buffer.data(), dst_size); UnswizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, width, height, depth, x_offset, src_params.origin.y, x_elements, regs.line_count, block_height, block_depth, @@ -288,11 +288,7 @@ void MaxwellDMA::CopyPitchToBlockLinear() { write_buffer.resize_destructive(dst_size); memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size); - if (Settings::IsGPULevelExtreme()) { - memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size); - } else { - memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size); - } + memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size); // If the input is linear and the output is tiled, swizzle the input and copy it over. SwizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, width, height, depth, x_offset, diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h index c390ac91b..35d699bbf 100644 --- a/src/video_core/fence_manager.h +++ b/src/video_core/fence_manager.h @@ -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(); } @@ -41,22 +59,43 @@ public: buffer_cache.AccumulateFlushes(); } + void SignalReference() { + std::function<void()> do_nothing([] {}); + SignalFence(std::move(do_nothing)); + } + void SyncOperation(std::function<void()>&& func) { uncommitted_operations.emplace_back(std::move(func)); } 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 +105,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 +144,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 +165,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 +241,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; }; diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 2e7f9c5ed..295a416a8 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -283,6 +283,21 @@ struct GPU::Impl { gpu_thread.FlushRegion(addr, size); } + VideoCore::RasterizerDownloadArea OnCPURead(VAddr addr, u64 size) { + auto raster_area = rasterizer->GetFlushArea(addr, size); + if (raster_area.preemtive) { + return raster_area; + } + raster_area.preemtive = true; + const u64 fence = RequestSyncOperation([this, &raster_area]() { + rasterizer->FlushRegion(raster_area.start_address, + raster_area.end_address - raster_area.start_address); + }); + gpu_thread.TickGPU(); + WaitForSyncOperation(fence); + return raster_area; + } + /// Notify rasterizer that any caches of the specified region should be invalidated void InvalidateRegion(VAddr addr, u64 size) { gpu_thread.InvalidateRegion(addr, size); @@ -538,6 +553,10 @@ void GPU::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { impl->SwapBuffers(framebuffer); } +VideoCore::RasterizerDownloadArea GPU::OnCPURead(VAddr addr, u64 size) { + return impl->OnCPURead(addr, size); +} + void GPU::FlushRegion(VAddr addr, u64 size) { impl->FlushRegion(addr, size); } diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 8a871593a..e49c40cf2 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -10,6 +10,7 @@ #include "core/hle/service/nvdrv/nvdata.h" #include "video_core/cdma_pusher.h" #include "video_core/framebuffer_config.h" +#include "video_core/rasterizer_download_area.h" namespace Core { class System; @@ -241,6 +242,9 @@ public: void SwapBuffers(const Tegra::FramebufferConfig* framebuffer); /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory + [[nodiscard]] VideoCore::RasterizerDownloadArea OnCPURead(VAddr addr, u64 size); + + /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory void FlushRegion(VAddr addr, u64 size); /// Notify rasterizer that any caches of the specified region should be invalidated diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index 01fb5b546..7b2cde7a7 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -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(); } diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index fbbe856c4..794535122 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h @@ -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; diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h index 8906ba6d8..1528cc1dd 100644 --- a/src/video_core/query_cache.h +++ b/src/video_core/query_cache.h @@ -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) { @@ -225,8 +255,16 @@ private: if (!in_range(query)) { 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); } @@ -234,7 +272,6 @@ private: /// Registers the passed parameters as cached and returns a pointer to the stored cached query. CachedQuery* Register(VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr, bool timestamp) { - rasterizer.UpdatePagesCachedCount(cpu_addr, CachedQuery::SizeInBytes(timestamp), 1); const u64 page = static_cast<u64>(cpu_addr) >> YUZU_PAGEBITS; return &cached_queries[page].emplace_back(static_cast<QueryCache&>(*this), type, cpu_addr, host_ptr); @@ -253,26 +290,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; + SlotVector<AsyncJob> slot_async_jobs; + VideoCore::RasterizerInterface& rasterizer; + Core::Memory::Memory& cpu_memory; - std::recursive_mutex mutex; + 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 +362,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 +388,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 +411,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 +452,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 +475,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 diff --git a/src/video_core/rasterizer_download_area.h b/src/video_core/rasterizer_download_area.h new file mode 100644 index 000000000..2d7425c79 --- /dev/null +++ b/src/video_core/rasterizer_download_area.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace VideoCore { + +struct RasterizerDownloadArea { + VAddr start_address; + VAddr end_address; + bool preemtive; +}; + +} // namespace VideoCore
\ No newline at end of file diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 33e2610bc..7566a8c4e 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -12,6 +12,7 @@ #include "video_core/cache_types.h" #include "video_core/engines/fermi_2d.h" #include "video_core/gpu.h" +#include "video_core/rasterizer_download_area.h" namespace Tegra { class MemoryManager; @@ -95,6 +96,8 @@ public: virtual bool MustFlushRegion(VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) = 0; + virtual RasterizerDownloadArea GetFlushArea(VAddr addr, u64 size) = 0; + /// Notify rasterizer that any caches of the specified region should be invalidated virtual void InvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) = 0; diff --git a/src/video_core/renderer_null/null_rasterizer.cpp b/src/video_core/renderer_null/null_rasterizer.cpp index 2b5c7defa..bf2ce4c49 100644 --- a/src/video_core/renderer_null/null_rasterizer.cpp +++ b/src/video_core/renderer_null/null_rasterizer.cpp @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/alignment.h" +#include "core/memory.h" #include "video_core/host1x/host1x.h" #include "video_core/memory_manager.h" #include "video_core/renderer_null/null_rasterizer.h" @@ -46,6 +48,14 @@ bool RasterizerNull::MustFlushRegion(VAddr addr, u64 size, VideoCommon::CacheTyp } void RasterizerNull::InvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType) {} void RasterizerNull::OnCPUWrite(VAddr addr, u64 size) {} +VideoCore::RasterizerDownloadArea RasterizerNull::GetFlushArea(VAddr addr, u64 size) { + VideoCore::RasterizerDownloadArea new_area{ + .start_address = Common::AlignDown(addr, Core::Memory::YUZU_PAGESIZE), + .end_address = Common::AlignUp(addr + size, Core::Memory::YUZU_PAGESIZE), + .preemtive = true, + }; + return new_area; +} void RasterizerNull::InvalidateGPUCache() {} void RasterizerNull::UnmapMemory(VAddr addr, u64 size) {} void RasterizerNull::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {} diff --git a/src/video_core/renderer_null/null_rasterizer.h b/src/video_core/renderer_null/null_rasterizer.h index 0c59e6a1f..a8d35d2c1 100644 --- a/src/video_core/renderer_null/null_rasterizer.h +++ b/src/video_core/renderer_null/null_rasterizer.h @@ -54,6 +54,7 @@ public: void InvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; void OnCPUWrite(VAddr addr, u64 size) override; + VideoCore::RasterizerDownloadArea GetFlushArea(VAddr addr, u64 size) override; void InvalidateGPUCache() override; void UnmapMemory(VAddr addr, u64 size) override; void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override; diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index a8c3f8b67..18d3c3ac0 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -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>; diff --git a/src/video_core/renderer_opengl/gl_buffer_cache_base.cpp b/src/video_core/renderer_opengl/gl_buffer_cache_base.cpp new file mode 100644 index 000000000..f15ae8e25 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_buffer_cache_base.cpp @@ -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>; +} diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index 22ed16ebf..400c21981 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -108,7 +108,8 @@ bool IsASTCSupported() { [[nodiscard]] bool IsDebugToolAttached(std::span<const std::string_view> extensions) { const bool nsight = std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED"); - return nsight || HasExtension(extensions, "GL_EXT_debug_tool"); + return nsight || HasExtension(extensions, "GL_EXT_debug_tool") || + Settings::values.renderer_debug.GetValue(); } } // Anonymous namespace diff --git a/src/video_core/renderer_opengl/gl_fence_manager.h b/src/video_core/renderer_opengl/gl_fence_manager.h index f1446e732..e21b19dcc 100644 --- a/src/video_core/renderer_opengl/gl_fence_manager.h +++ b/src/video_core/renderer_opengl/gl_fence_manager.h @@ -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: diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp index 5070db441..99d7347f5 100644 --- a/src/video_core/renderer_opengl/gl_query_cache.cpp +++ b/src/video_core/renderer_opengl/gl_query_cache.cpp @@ -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 diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h index 14ce59990..872513f22 100644 --- a/src/video_core/renderer_opengl/gl_query_cache.h +++ b/src/video_core/renderer_opengl/gl_query_cache.h @@ -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; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 4993d4709..f5baa0f3c 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -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_) {} @@ -433,6 +433,29 @@ bool RasterizerOpenGL::MustFlushRegion(VAddr addr, u64 size, VideoCommon::CacheT return false; } +VideoCore::RasterizerDownloadArea RasterizerOpenGL::GetFlushArea(VAddr addr, u64 size) { + { + std::scoped_lock lock{texture_cache.mutex}; + auto area = texture_cache.GetFlushArea(addr, size); + if (area) { + return *area; + } + } + { + std::scoped_lock lock{buffer_cache.mutex}; + auto area = buffer_cache.GetFlushArea(addr, size); + if (area) { + return *area; + } + } + VideoCore::RasterizerDownloadArea new_area{ + .start_address = Common::AlignDown(addr, Core::Memory::YUZU_PAGESIZE), + .end_address = Common::AlignUp(addr + size, Core::Memory::YUZU_PAGESIZE), + .preemtive = true, + }; + return new_area; +} + void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType which) { MICROPROFILE_SCOPE(OpenGL_CacheManagement); if (addr == 0 || size == 0) { @@ -1281,7 +1304,7 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info, const Tegra::DMA::BufferOperand& buffer_operand, const Tegra::DMA::ImageOperand& image_operand) { std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; - const auto image_id = texture_cache.DmaImageId(image_operand); + const auto image_id = texture_cache.DmaImageId(image_operand, IS_IMAGE_UPLOAD); if (image_id == VideoCommon::NULL_IMAGE_ID) { return false; } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index ad6978bd0..410d8ffc5 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -95,6 +95,7 @@ public: VideoCommon::CacheType which = VideoCommon::CacheType::All) override; bool MustFlushRegion(VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; + VideoCore::RasterizerDownloadArea GetFlushArea(VAddr addr, u64 size) override; void InvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; void OnCPUWrite(VAddr addr, u64 size) override; diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 032a8ebc5..31118886f 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -231,7 +231,7 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4 [[nodiscard]] bool CanBeAccelerated(const TextureCacheRuntime& runtime, const VideoCommon::ImageInfo& info) { - if (IsPixelFormatASTC(info.format) && !runtime.HasNativeASTC()) { + if (IsPixelFormatASTC(info.format) && info.size.depth == 1 && !runtime.HasNativeASTC()) { return Settings::values.accelerate_astc.GetValue() && !Settings::values.async_astc.GetValue(); } @@ -861,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: @@ -1123,7 +1126,8 @@ bool Image::ScaleDown(bool ignore) { ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info, ImageId image_id_, Image& image, const SlotVector<Image>&) - : VideoCommon::ImageViewBase{info, image.info, image_id_}, views{runtime.null_image_views} { + : VideoCommon::ImageViewBase{info, image.info, image_id_, image.gpu_addr}, + views{runtime.null_image_views} { const Device& device = runtime.device; if (True(image.flags & ImageFlagBits::Converted)) { internal_format = IsPixelFormatSRGB(info.format) ? GL_SRGB8_ALPHA8 : GL_RGBA8; @@ -1214,12 +1218,12 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, const VideoCommon::ImageViewInfo& view_info, GPUVAddr gpu_addr_) - : VideoCommon::ImageViewBase{info, view_info}, gpu_addr{gpu_addr_}, + : VideoCommon::ImageViewBase{info, view_info, gpu_addr_}, buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {} ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, const VideoCommon::ImageViewInfo& view_info) - : VideoCommon::ImageViewBase{info, view_info} {} + : VideoCommon::ImageViewBase{info, view_info, 0} {} ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageViewParams& params) : VideoCommon::ImageViewBase{params}, views{runtime.null_image_views} {} @@ -1279,7 +1283,7 @@ GLuint ImageView::MakeView(Shader::TextureType view_type, GLenum view_format) { ApplySwizzle(view.handle, format, casted_swizzle); } if (set_object_label) { - const std::string name = VideoCommon::Name(*this); + const std::string name = VideoCommon::Name(*this, gpu_addr); glObjectLabel(GL_TEXTURE, view.handle, static_cast<GLsizei>(name.size()), name.data()); } return view.handle; diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index 0dd039ed2..1190999a8 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -314,7 +314,6 @@ private: std::unique_ptr<StorageViews> storage_views; GLenum internal_format = GL_NONE; GLuint default_handle = 0; - GPUVAddr gpu_addr = 0; u32 buffer_size = 0; GLuint original_texture = 0; int num_samples = 0; diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index ef1190e1f..c7dc7e0a1 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -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 diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index 5dce51be8..8853cf0f7 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -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 diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 2a8d9e377..8e31eba34 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -88,13 +88,14 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_, instance(CreateInstance(library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, Settings::values.renderer_debug.GetValue())), debug_callback(Settings::values.renderer_debug ? CreateDebugCallback(instance) : nullptr), - surface(CreateSurface(instance, render_window)), + surface(CreateSurface(instance, render_window.GetWindowInfo())), device(CreateDevice(instance, dld, *surface)), memory_allocator(device, false), 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, diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 009e75e0d..f44367cb2 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -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; diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 2f0cc27e8..1e0fdd3d9 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -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); - - 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]; + 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[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; } diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h index ebe10b08b..68ec20253 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.h +++ b/src/video_core/renderer_vulkan/vk_blit_screen.h @@ -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; diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 9cbcb3c8f..510602e8e 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -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 { diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 183b33632..879f1ed94 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -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>; diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache_base.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache_base.cpp new file mode 100644 index 000000000..f9e271507 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_buffer_cache_base.cpp @@ -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>; +} diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.cpp b/src/video_core/renderer_vulkan/vk_fence_manager.cpp index 0214b103a..fad9e3832 100644 --- a/src/video_core/renderer_vulkan/vk_fence_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_fence_manager.cpp @@ -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" diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.h b/src/video_core/renderer_vulkan/vk_fence_manager.h index 7fe2afcd9..145359d4e 100644 --- a/src/video_core/renderer_vulkan/vk_fence_manager.h +++ b/src/video_core/renderer_vulkan/vk_fence_manager.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: diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp new file mode 100644 index 000000000..c49583013 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -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 diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h new file mode 100644 index 000000000..420a775e2 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_present_manager.h @@ -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 diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 929c8ece6..d67490449 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -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), diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h index 26762ee09..c1b9552eb 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.h +++ b/src/video_core/renderer_vulkan/vk_query_cache.h @@ -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; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 2559a3aa7..628e1376f 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -172,7 +172,8 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra buffer_cache(*this, cpu_memory_, buffer_cache_runtime), pipeline_cache(*this, device, scheduler, descriptor_pool, update_descriptor_queue, render_pass_cache, buffer_cache, texture_cache, gpu.ShaderNotify()), - query_cache{*this, device, scheduler}, accelerate_dma(buffer_cache, texture_cache, scheduler), + query_cache{*this, cpu_memory_, device, scheduler}, + accelerate_dma(buffer_cache, texture_cache, scheduler), fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler), wfi_event(device.GetLogical().CreateEvent()) { scheduler.SetQueryCache(query_cache); @@ -501,6 +502,22 @@ bool RasterizerVulkan::MustFlushRegion(VAddr addr, u64 size, VideoCommon::CacheT return false; } +VideoCore::RasterizerDownloadArea RasterizerVulkan::GetFlushArea(VAddr addr, u64 size) { + { + std::scoped_lock lock{texture_cache.mutex}; + auto area = texture_cache.GetFlushArea(addr, size); + if (area) { + return *area; + } + } + VideoCore::RasterizerDownloadArea new_area{ + .start_address = Common::AlignDown(addr, Core::Memory::YUZU_PAGESIZE), + .end_address = Common::AlignUp(addr + size, Core::Memory::YUZU_PAGESIZE), + .preemtive = true, + }; + return new_area; +} + void RasterizerVulkan::InvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType which) { if (addr == 0 || size == 0) { return; @@ -597,7 +614,7 @@ void RasterizerVulkan::SignalSyncPoint(u32 value) { } void RasterizerVulkan::SignalReference() { - fence_manager.SignalOrdering(); + fence_manager.SignalReference(); } void RasterizerVulkan::ReleaseFences() { @@ -630,7 +647,7 @@ void RasterizerVulkan::WaitForIdle() { cmdbuf.SetEvent(event, flags); cmdbuf.WaitEvents(event, flags, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, {}, {}, {}); }); - SignalReference(); + fence_manager.SignalOrdering(); } void RasterizerVulkan::FragmentBarrier() { @@ -675,7 +692,8 @@ bool RasterizerVulkan::AccelerateConditionalRendering() { const GPUVAddr condition_address{maxwell3d->regs.render_enable.Address()}; Maxwell::ReportSemaphore::Compare cmp; if (gpu_memory->IsMemoryDirty(condition_address, sizeof(cmp), - VideoCommon::CacheType::BufferCache)) { + VideoCommon::CacheType::BufferCache | + VideoCommon::CacheType::QueryCache)) { return true; } return false; @@ -775,7 +793,7 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info, const Tegra::DMA::BufferOperand& buffer_operand, const Tegra::DMA::ImageOperand& image_operand) { std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; - const auto image_id = texture_cache.DmaImageId(image_operand); + const auto image_id = texture_cache.DmaImageId(image_operand, IS_IMAGE_UPLOAD); if (image_id == VideoCommon::NULL_IMAGE_ID) { return false; } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 1659fbc13..9bd422850 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -92,6 +92,7 @@ public: VideoCommon::CacheType which = VideoCommon::CacheType::All) override; bool MustFlushRegion(VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; + VideoCore::RasterizerDownloadArea GetFlushArea(VAddr addr, u64 size) override; void InvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType which = VideoCommon::CacheType::All) override; void InnerInvalidation(std::span<const std::pair<VAddr, std::size_t>> sequences) override; diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 057e16967..80455ec08 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -46,10 +46,11 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_) Scheduler::~Scheduler() = default; -void Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { +u64 Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { // When flushing, we only send data to the worker thread; no waiting is necessary. - SubmitExecution(signal_semaphore, wait_semaphore); + const u64 signal_value = SubmitExecution(signal_semaphore, wait_semaphore); AllocateNewContext(); + return signal_value; } void Scheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { @@ -205,7 +206,7 @@ void Scheduler::AllocateWorkerCommandBuffer() { }); } -void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { +u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { EndPendingOperations(); InvalidateState(); @@ -217,6 +218,7 @@ void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_s on_submit(); } + std::scoped_lock lock{submit_mutex}; switch (const VkResult result = master_semaphore->SubmitQueue( cmdbuf, signal_semaphore, wait_semaphore, signal_value)) { case VK_SUCCESS: @@ -231,6 +233,7 @@ void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_s }); chunk->MarkSubmit(); DispatchWork(); + return signal_value; } void Scheduler::AllocateNewContext() { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 8d75ce987..475c682eb 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -34,7 +34,7 @@ public: ~Scheduler(); /// Sends the current execution context to the GPU. - void Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); + u64 Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); /// Sends the current execution context to the GPU and waits for it to complete. void Finish(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); @@ -106,6 +106,8 @@ public: return *master_semaphore; } + std::mutex submit_mutex; + private: class Command { public: @@ -201,7 +203,7 @@ private: void AllocateWorkerCommandBuffer(); - void SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore); + u64 SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore); void AllocateNewContext(); diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index b1465e35c..1e80ce463 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -14,6 +14,7 @@ #include "video_core/renderer_vulkan/vk_swapchain.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" +#include "vulkan/vulkan_core.h" namespace Vulkan { @@ -33,23 +34,47 @@ VkSurfaceFormatKHR ChooseSwapSurfaceFormat(vk::Span<VkSurfaceFormatKHR> formats) return found != formats.end() ? *found : formats[0]; } -VkPresentModeKHR ChooseSwapPresentMode(vk::Span<VkPresentModeKHR> modes) { - // Mailbox (triple buffering) doesn't lock the application like fifo (vsync), - // prefer it if vsync option is not selected - const auto found_mailbox = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_MAILBOX_KHR); - if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Borderless && - found_mailbox != modes.end() && !Settings::values.use_vsync.GetValue()) { - return VK_PRESENT_MODE_MAILBOX_KHR; - } - if (!Settings::values.use_speed_limit.GetValue()) { - // FIFO present mode locks the framerate to the monitor's refresh rate, - // Find an alternative to surpass this limitation if FPS is unlocked. - const auto found_imm = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_IMMEDIATE_KHR); - if (found_imm != modes.end()) { - return VK_PRESENT_MODE_IMMEDIATE_KHR; +static constexpr VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox, + bool has_fifo_relaxed) { + // Mailbox doesn't lock the application like FIFO (vsync) + // FIFO present mode locks the framerate to the monitor's refresh rate + Settings::VSyncMode setting = [has_imm, has_mailbox]() { + // Choose Mailbox or Immediate if unlocked and those modes are supported + const auto mode = Settings::values.vsync_mode.GetValue(); + if (Settings::values.use_speed_limit.GetValue()) { + return mode; + } + switch (mode) { + case Settings::VSyncMode::FIFO: + case Settings::VSyncMode::FIFORelaxed: + if (has_mailbox) { + return Settings::VSyncMode::Mailbox; + } else if (has_imm) { + return Settings::VSyncMode::Immediate; + } + [[fallthrough]]; + default: + return mode; } + }(); + if ((setting == Settings::VSyncMode::Mailbox && !has_mailbox) || + (setting == Settings::VSyncMode::Immediate && !has_imm) || + (setting == Settings::VSyncMode::FIFORelaxed && !has_fifo_relaxed)) { + setting = Settings::VSyncMode::FIFO; + } + + switch (setting) { + case Settings::VSyncMode::Immediate: + return VK_PRESENT_MODE_IMMEDIATE_KHR; + case Settings::VSyncMode::Mailbox: + return VK_PRESENT_MODE_MAILBOX_KHR; + case Settings::VSyncMode::FIFO: + return VK_PRESENT_MODE_FIFO_KHR; + case Settings::VSyncMode::FIFORelaxed: + return VK_PRESENT_MODE_FIFO_RELAXED_KHR; + default: + return VK_PRESENT_MODE_FIFO_KHR; } - return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, u32 width, u32 height) { @@ -99,18 +124,16 @@ void Swapchain::Create(u32 width_, u32 height_, bool srgb) { return; } - device.GetLogical().WaitIdle(); Destroy(); CreateSwapchain(capabilities, srgb); CreateSemaphores(); - CreateImageViews(); resource_ticks.clear(); resource_ticks.resize(image_count); } -void Swapchain::AcquireNextImage() { +bool Swapchain::AcquireNextImage() { const VkResult result = device.GetLogical().AcquireNextImageKHR( *swapchain, std::numeric_limits<u64>::max(), *present_semaphores[frame_index], VK_NULL_HANDLE, &image_index); @@ -127,8 +150,11 @@ void Swapchain::AcquireNextImage() { LOG_ERROR(Render_Vulkan, "vkAcquireNextImageKHR returned {}", vk::ToString(result)); break; } + scheduler.Wait(resource_ticks[image_index]); resource_ticks[image_index] = scheduler.CurrentTick(); + + return is_suboptimal || is_outdated; } void Swapchain::Present(VkSemaphore render_semaphore) { @@ -143,6 +169,7 @@ void Swapchain::Present(VkSemaphore render_semaphore) { .pImageIndices = &image_index, .pResults = nullptr, }; + std::scoped_lock lock{scheduler.submit_mutex}; switch (const VkResult result = present_queue.Present(present_info)) { case VK_SUCCESS: break; @@ -165,11 +192,17 @@ void Swapchain::Present(VkSemaphore render_semaphore) { void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bool srgb) { const auto physical_device{device.GetPhysical()}; const auto formats{physical_device.GetSurfaceFormatsKHR(surface)}; - const auto present_modes{physical_device.GetSurfacePresentModesKHR(surface)}; + const auto present_modes = physical_device.GetSurfacePresentModesKHR(surface); + has_mailbox = std::find(present_modes.begin(), present_modes.end(), + VK_PRESENT_MODE_MAILBOX_KHR) != present_modes.end(); + has_imm = std::find(present_modes.begin(), present_modes.end(), + VK_PRESENT_MODE_IMMEDIATE_KHR) != present_modes.end(); + has_fifo_relaxed = std::find(present_modes.begin(), present_modes.end(), + VK_PRESENT_MODE_FIFO_RELAXED_KHR) != present_modes.end(); const VkCompositeAlphaFlagBitsKHR alpha_flags{ChooseAlphaFlags(capabilities)}; - const VkSurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats)}; - present_mode = ChooseSwapPresentMode(present_modes); + surface_format = ChooseSwapSurfaceFormat(formats); + present_mode = ChooseSwapPresentMode(has_imm, has_mailbox, has_fifo_relaxed); u32 requested_image_count{capabilities.minImageCount + 1}; // Ensure Triple buffering if possible. @@ -193,7 +226,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo .imageColorSpace = surface_format.colorSpace, .imageExtent = {}, .imageArrayLayers = 1, - .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, @@ -230,7 +263,6 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo extent = swapchain_ci.imageExtent; current_srgb = srgb; - current_fps_unlocked = !Settings::values.use_speed_limit.GetValue(); images = swapchain.GetImages(); image_count = static_cast<u32>(images.size()); @@ -241,56 +273,20 @@ void Swapchain::CreateSemaphores() { present_semaphores.resize(image_count); std::ranges::generate(present_semaphores, [this] { return device.GetLogical().CreateSemaphore(); }); -} - -void Swapchain::CreateImageViews() { - VkImageViewCreateInfo ci{ - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .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, - }, - }; - - image_views.resize(image_count); - for (std::size_t i = 0; i < image_count; i++) { - ci.image = images[i]; - image_views[i] = device.GetLogical().CreateImageView(ci); - } + render_semaphores.resize(image_count); + std::ranges::generate(render_semaphores, + [this] { return device.GetLogical().CreateSemaphore(); }); } void Swapchain::Destroy() { frame_index = 0; present_semaphores.clear(); - framebuffers.clear(); - image_views.clear(); swapchain.reset(); } -bool Swapchain::HasFpsUnlockChanged() const { - return current_fps_unlocked != !Settings::values.use_speed_limit.GetValue(); -} - bool Swapchain::NeedsPresentModeUpdate() const { - // Mailbox present mode is the ideal for all scenarios. If it is not available, - // A different present mode is needed to support unlocked FPS above the monitor's refresh rate. - return present_mode != VK_PRESENT_MODE_MAILBOX_KHR && HasFpsUnlockChanged(); + const auto requested_mode = ChooseSwapPresentMode(has_imm, has_mailbox, has_fifo_relaxed); + return present_mode != requested_mode; } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index caf1ff32b..bf1ea7254 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -27,7 +27,7 @@ public: void Create(u32 width, u32 height, bool srgb); /// Acquires the next image in the swapchain, waits as needed. - void AcquireNextImage(); + bool AcquireNextImage(); /// Presents the rendered image to the swapchain. void Present(VkSemaphore render_semaphore); @@ -52,6 +52,11 @@ public: return is_suboptimal; } + /// Returns true when the swapchain format is in the srgb color space + bool IsSrgb() const { + return current_srgb; + } + VkExtent2D GetSize() const { return extent; } @@ -64,22 +69,34 @@ public: return image_index; } + std::size_t GetFrameIndex() const { + return frame_index; + } + VkImage GetImageIndex(std::size_t index) const { return images[index]; } - VkImageView GetImageViewIndex(std::size_t index) const { - return *image_views[index]; + VkImage CurrentImage() const { + return images[image_index]; } VkFormat GetImageViewFormat() const { return image_view_format; } + VkFormat GetImageFormat() const { + return surface_format.format; + } + VkSemaphore CurrentPresentSemaphore() const { return *present_semaphores[frame_index]; } + VkSemaphore CurrentRenderSemaphore() const { + return *render_semaphores[frame_index]; + } + u32 GetWidth() const { return width; } @@ -88,6 +105,10 @@ public: return height; } + VkExtent2D GetExtent() const { + return extent; + } + private: void CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bool srgb); void CreateSemaphores(); @@ -95,8 +116,6 @@ private: void Destroy(); - bool HasFpsUnlockChanged() const; - bool NeedsPresentModeUpdate() const; const VkSurfaceKHR surface; @@ -107,10 +126,9 @@ private: std::size_t image_count{}; std::vector<VkImage> images; - std::vector<vk::ImageView> image_views; - std::vector<vk::Framebuffer> framebuffers; std::vector<u64> resource_ticks; std::vector<vk::Semaphore> present_semaphores; + std::vector<vk::Semaphore> render_semaphores; u32 width; u32 height; @@ -121,9 +139,12 @@ private: VkFormat image_view_format{}; VkExtent2D extent{}; VkPresentModeKHR present_mode{}; + VkSurfaceFormatKHR surface_format{}; + bool has_imm{false}; + bool has_mailbox{false}; + bool has_fifo_relaxed{false}; bool current_srgb{}; - bool current_fps_unlocked{}; bool is_outdated{}; bool is_suboptimal{}; }; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index d0a7d8f35..9ca7751c5 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -1268,7 +1268,7 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) { if (Settings::values.async_astc.GetValue()) { flags |= VideoCommon::ImageFlagBits::AsynchronousDecode; - } else if (Settings::values.accelerate_astc.GetValue()) { + } else if (Settings::values.accelerate_astc.GetValue() && info.size.depth == 1) { flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; } flags |= VideoCommon::ImageFlagBits::Converted; @@ -1584,8 +1584,9 @@ bool Image::NeedsScaleHelper() const { ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info, ImageId image_id_, Image& image) - : VideoCommon::ImageViewBase{info, image.info, image_id_}, device{&runtime.device}, - image_handle{image.Handle()}, samples(ConvertSampleCount(image.info.num_samples)) { + : VideoCommon::ImageViewBase{info, image.info, image_id_, image.gpu_addr}, + device{&runtime.device}, image_handle{image.Handle()}, + samples(ConvertSampleCount(image.info.num_samples)) { using Shader::TextureType; const VkImageAspectFlags aspect_mask = ImageViewAspectMask(info); @@ -1631,7 +1632,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI } vk::ImageView handle = device->GetLogical().CreateImageView(ci); if (device->HasDebuggingToolAttached()) { - handle.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); + handle.SetObjectNameEXT(VideoCommon::Name(*this, gpu_addr).c_str()); } image_views[static_cast<size_t>(tex_type)] = std::move(handle); }; @@ -1672,7 +1673,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, const VideoCommon::ImageViewInfo& view_info, GPUVAddr gpu_addr_) - : VideoCommon::ImageViewBase{info, view_info}, gpu_addr{gpu_addr_}, + : VideoCommon::ImageViewBase{info, view_info, gpu_addr_}, buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {} ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams& params) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index c656c5386..6f360177a 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -265,7 +265,6 @@ private: VkImage image_handle = VK_NULL_HANDLE; VkImageView render_target = VK_NULL_HANDLE; VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; - GPUVAddr gpu_addr = 0; u32 buffer_size = 0; }; diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp index 009dab0b6..0630ebda5 100644 --- a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp @@ -14,13 +14,18 @@ namespace Vulkan { UpdateDescriptorQueue::UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_) : device{device_}, scheduler{scheduler_} { + payload_start = payload.data(); payload_cursor = payload.data(); } UpdateDescriptorQueue::~UpdateDescriptorQueue() = default; void UpdateDescriptorQueue::TickFrame() { - payload_cursor = payload.data(); + if (++frame_index >= FRAMES_IN_FLIGHT) { + frame_index = 0; + } + payload_start = payload.data() + frame_index * FRAME_PAYLOAD_SIZE; + payload_cursor = payload_start; } void UpdateDescriptorQueue::Acquire() { @@ -28,10 +33,10 @@ void UpdateDescriptorQueue::Acquire() { // This is the maximum number of entries a single draw call might use. static constexpr size_t MIN_ENTRIES = 0x400; - if (std::distance(payload.data(), payload_cursor) + MIN_ENTRIES >= payload.max_size()) { + if (std::distance(payload_start, payload_cursor) + MIN_ENTRIES >= FRAME_PAYLOAD_SIZE) { LOG_WARNING(Render_Vulkan, "Payload overflow, waiting for worker thread"); scheduler.WaitWorker(); - payload_cursor = payload.data(); + payload_cursor = payload_start; } upload_start = payload_cursor; } diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.h b/src/video_core/renderer_vulkan/vk_update_descriptor.h index 625bcc809..1c1a7020b 100644 --- a/src/video_core/renderer_vulkan/vk_update_descriptor.h +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.h @@ -29,6 +29,12 @@ struct DescriptorUpdateEntry { }; class UpdateDescriptorQueue final { + // This should be plenty for the vast majority of cases. Most desktop platforms only + // provide up to 3 swapchain images. + static constexpr size_t FRAMES_IN_FLIGHT = 5; + static constexpr size_t FRAME_PAYLOAD_SIZE = 0x10000; + static constexpr size_t PAYLOAD_SIZE = FRAME_PAYLOAD_SIZE * FRAMES_IN_FLIGHT; + public: explicit UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_); ~UpdateDescriptorQueue(); @@ -73,9 +79,11 @@ private: const Device& device; Scheduler& scheduler; + size_t frame_index{0}; DescriptorUpdateEntry* payload_cursor = nullptr; + DescriptorUpdateEntry* payload_start = nullptr; const DescriptorUpdateEntry* upload_start = nullptr; - std::array<DescriptorUpdateEntry, 0x10000> payload; + std::array<DescriptorUpdateEntry, PAYLOAD_SIZE> payload; }; } // namespace Vulkan diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp index d9482371b..c5213875b 100644 --- a/src/video_core/shader_cache.cpp +++ b/src/video_core/shader_cache.cpp @@ -228,14 +228,14 @@ const ShaderInfo* ShaderCache::MakeShaderInfo(GenericEnvironment& env, VAddr cpu auto info = std::make_unique<ShaderInfo>(); if (const std::optional<u64> cached_hash{env.Analyze()}) { info->unique_hash = *cached_hash; - info->size_bytes = env.CachedSize(); + info->size_bytes = env.CachedSizeBytes(); } else { // Slow path, not really hit on commercial games // Build a control flow graph to get the real shader size Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block; Shader::Maxwell::Flow::CFG cfg{env, flow_block, env.StartAddress()}; info->unique_hash = env.CalculateHash(); - info->size_bytes = env.ReadSize(); + info->size_bytes = env.ReadSizeBytes(); } const size_t size_bytes{info->size_bytes}; const ShaderInfo* const result{info.get()}; diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index 574760f80..c7cb56243 100644 --- a/src/video_core/shader_environment.cpp +++ b/src/video_core/shader_environment.cpp @@ -170,15 +170,19 @@ std::optional<u64> GenericEnvironment::Analyze() { void GenericEnvironment::SetCachedSize(size_t size_bytes) { cached_lowest = start_address; cached_highest = start_address + static_cast<u32>(size_bytes); - code.resize(CachedSize()); + code.resize(CachedSizeWords()); gpu_memory->ReadBlock(program_base + cached_lowest, code.data(), code.size() * sizeof(u64)); } -size_t GenericEnvironment::CachedSize() const noexcept { - return cached_highest - cached_lowest + INST_SIZE; +size_t GenericEnvironment::CachedSizeWords() const noexcept { + return CachedSizeBytes() / INST_SIZE; } -size_t GenericEnvironment::ReadSize() const noexcept { +size_t GenericEnvironment::CachedSizeBytes() const noexcept { + return static_cast<size_t>(cached_highest) - cached_lowest + INST_SIZE; +} + +size_t GenericEnvironment::ReadSizeBytes() const noexcept { return read_highest - read_lowest + INST_SIZE; } @@ -187,7 +191,7 @@ bool GenericEnvironment::CanBeSerialized() const noexcept { } u64 GenericEnvironment::CalculateHash() const { - const size_t size{ReadSize()}; + const size_t size{ReadSizeBytes()}; const auto data{std::make_unique<char[]>(size)}; gpu_memory->ReadBlock(program_base + read_lowest, data.get(), size); return Common::CityHash64(data.get(), size); @@ -198,7 +202,7 @@ void GenericEnvironment::Dump(u64 hash) { } void GenericEnvironment::Serialize(std::ofstream& file) const { - const u64 code_size{static_cast<u64>(CachedSize())}; + const u64 code_size{static_cast<u64>(CachedSizeBytes())}; const u64 num_texture_types{static_cast<u64>(texture_types.size())}; const u64 num_texture_pixel_formats{static_cast<u64>(texture_pixel_formats.size())}; const u64 num_cbuf_values{static_cast<u64>(cbuf_values.size())}; diff --git a/src/video_core/shader_environment.h b/src/video_core/shader_environment.h index d75987a52..a0f61cbda 100644 --- a/src/video_core/shader_environment.h +++ b/src/video_core/shader_environment.h @@ -48,9 +48,11 @@ public: void SetCachedSize(size_t size_bytes); - [[nodiscard]] size_t CachedSize() const noexcept; + [[nodiscard]] size_t CachedSizeWords() const noexcept; - [[nodiscard]] size_t ReadSize() const noexcept; + [[nodiscard]] size_t CachedSizeBytes() const noexcept; + + [[nodiscard]] size_t ReadSizeBytes() const noexcept; [[nodiscard]] bool CanBeSerialized() const noexcept; diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 1a76d4178..cb51529e4 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -250,10 +250,13 @@ bool IsPixelFormatASTC(PixelFormat format) { case PixelFormat::ASTC_2D_6X6_UNORM: case PixelFormat::ASTC_2D_6X6_SRGB: case PixelFormat::ASTC_2D_10X6_UNORM: + case PixelFormat::ASTC_2D_10X6_SRGB: case PixelFormat::ASTC_2D_10X5_UNORM: case PixelFormat::ASTC_2D_10X5_SRGB: case PixelFormat::ASTC_2D_10X10_UNORM: case PixelFormat::ASTC_2D_10X10_SRGB: + case PixelFormat::ASTC_2D_12X10_UNORM: + case PixelFormat::ASTC_2D_12X10_SRGB: case PixelFormat::ASTC_2D_12X12_UNORM: case PixelFormat::ASTC_2D_12X12_SRGB: case PixelFormat::ASTC_2D_8X6_UNORM: @@ -279,11 +282,13 @@ bool IsPixelFormatSRGB(PixelFormat format) { case PixelFormat::ASTC_2D_8X5_SRGB: case PixelFormat::ASTC_2D_5X4_SRGB: case PixelFormat::ASTC_2D_5X5_SRGB: + case PixelFormat::ASTC_2D_10X6_SRGB: case PixelFormat::ASTC_2D_10X8_SRGB: case PixelFormat::ASTC_2D_6X6_SRGB: case PixelFormat::ASTC_2D_10X5_SRGB: case PixelFormat::ASTC_2D_10X10_SRGB: case PixelFormat::ASTC_2D_12X12_SRGB: + case PixelFormat::ASTC_2D_12X10_SRGB: case PixelFormat::ASTC_2D_8X6_SRGB: case PixelFormat::ASTC_2D_6X5_SRGB: return true; diff --git a/src/video_core/surface.h b/src/video_core/surface.h index 44b79af20..0225d3287 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -95,10 +95,13 @@ enum class PixelFormat { ASTC_2D_6X6_UNORM, ASTC_2D_6X6_SRGB, ASTC_2D_10X6_UNORM, + ASTC_2D_10X6_SRGB, ASTC_2D_10X5_UNORM, ASTC_2D_10X5_SRGB, ASTC_2D_10X10_UNORM, ASTC_2D_10X10_SRGB, + ASTC_2D_12X10_UNORM, + ASTC_2D_12X10_SRGB, ASTC_2D_12X12_UNORM, ASTC_2D_12X12_SRGB, ASTC_2D_8X6_UNORM, @@ -232,10 +235,13 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{ 6, // ASTC_2D_6X6_UNORM 6, // ASTC_2D_6X6_SRGB 10, // ASTC_2D_10X6_UNORM + 10, // ASTC_2D_10X6_SRGB 10, // ASTC_2D_10X5_UNORM 10, // ASTC_2D_10X5_SRGB 10, // ASTC_2D_10X10_UNORM 10, // ASTC_2D_10X10_SRGB + 12, // ASTC_2D_12X10_UNORM + 12, // ASTC_2D_12X10_SRGB 12, // ASTC_2D_12X12_UNORM 12, // ASTC_2D_12X12_SRGB 8, // ASTC_2D_8X6_UNORM @@ -338,10 +344,13 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{ 6, // ASTC_2D_6X6_UNORM 6, // ASTC_2D_6X6_SRGB 6, // ASTC_2D_10X6_UNORM + 6, // ASTC_2D_10X6_SRGB 5, // ASTC_2D_10X5_UNORM 5, // ASTC_2D_10X5_SRGB 10, // ASTC_2D_10X10_UNORM 10, // ASTC_2D_10X10_SRGB + 10, // ASTC_2D_12X10_UNORM + 10, // ASTC_2D_12X10_SRGB 12, // ASTC_2D_12X12_UNORM 12, // ASTC_2D_12X12_SRGB 6, // ASTC_2D_8X6_UNORM @@ -444,10 +453,13 @@ constexpr std::array<u8, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{ 128, // ASTC_2D_6X6_UNORM 128, // ASTC_2D_6X6_SRGB 128, // ASTC_2D_10X6_UNORM + 128, // ASTC_2D_10X6_SRGB 128, // ASTC_2D_10X5_UNORM 128, // ASTC_2D_10X5_SRGB 128, // ASTC_2D_10X10_UNORM 128, // ASTC_2D_10X10_SRGB + 128, // ASTC_2D_12X10_UNORM + 128, // ASTC_2D_12X10_SRGB 128, // ASTC_2D_12X12_UNORM 128, // ASTC_2D_12X12_SRGB 128, // ASTC_2D_8X6_UNORM diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index 5fc2b2fec..11ced6c38 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp @@ -210,6 +210,8 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, return PixelFormat::ASTC_2D_6X6_SRGB; case Hash(TextureFormat::ASTC_2D_10X6, UNORM, LINEAR): return PixelFormat::ASTC_2D_10X6_UNORM; + case Hash(TextureFormat::ASTC_2D_10X6, UNORM, SRGB): + return PixelFormat::ASTC_2D_10X6_SRGB; case Hash(TextureFormat::ASTC_2D_10X5, UNORM, LINEAR): return PixelFormat::ASTC_2D_10X5_UNORM; case Hash(TextureFormat::ASTC_2D_10X5, UNORM, SRGB): @@ -218,6 +220,10 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, return PixelFormat::ASTC_2D_10X10_UNORM; case Hash(TextureFormat::ASTC_2D_10X10, UNORM, SRGB): return PixelFormat::ASTC_2D_10X10_SRGB; + case Hash(TextureFormat::ASTC_2D_12X10, UNORM, LINEAR): + return PixelFormat::ASTC_2D_12X10_UNORM; + case Hash(TextureFormat::ASTC_2D_12X10, UNORM, SRGB): + return PixelFormat::ASTC_2D_12X10_SRGB; case Hash(TextureFormat::ASTC_2D_12X12, UNORM, LINEAR): return PixelFormat::ASTC_2D_12X12_UNORM; case Hash(TextureFormat::ASTC_2D_12X12, UNORM, SRGB): diff --git a/src/video_core/texture_cache/formatter.cpp b/src/video_core/texture_cache/formatter.cpp index 30f72361d..6279d8e9e 100644 --- a/src/video_core/texture_cache/formatter.cpp +++ b/src/video_core/texture_cache/formatter.cpp @@ -46,7 +46,7 @@ std::string Name(const ImageBase& image) { return "Invalid"; } -std::string Name(const ImageViewBase& image_view) { +std::string Name(const ImageViewBase& image_view, GPUVAddr addr) { const u32 width = image_view.size.width; const u32 height = image_view.size.height; const u32 depth = image_view.size.depth; @@ -56,23 +56,25 @@ std::string Name(const ImageViewBase& image_view) { const std::string level = num_levels > 1 ? fmt::format(":{}", num_levels) : ""; switch (image_view.type) { case ImageViewType::e1D: - return fmt::format("ImageView 1D {}{}", width, level); + return fmt::format("ImageView 1D 0x{:X} {}{}", addr, width, level); case ImageViewType::e2D: - return fmt::format("ImageView 2D {}x{}{}", width, height, level); + return fmt::format("ImageView 2D 0x{:X} {}x{}{}", addr, width, height, level); case ImageViewType::Cube: - return fmt::format("ImageView Cube {}x{}{}", width, height, level); + return fmt::format("ImageView Cube 0x{:X} {}x{}{}", addr, width, height, level); case ImageViewType::e3D: - return fmt::format("ImageView 3D {}x{}x{}{}", width, height, depth, level); + return fmt::format("ImageView 3D 0x{:X} {}x{}x{}{}", addr, width, height, depth, level); case ImageViewType::e1DArray: - return fmt::format("ImageView 1DArray {}{}|{}", width, level, num_layers); + return fmt::format("ImageView 1DArray 0x{:X} {}{}|{}", addr, width, level, num_layers); case ImageViewType::e2DArray: - return fmt::format("ImageView 2DArray {}x{}{}|{}", width, height, level, num_layers); + return fmt::format("ImageView 2DArray 0x{:X} {}x{}{}|{}", addr, width, height, level, + num_layers); case ImageViewType::CubeArray: - return fmt::format("ImageView CubeArray {}x{}{}|{}", width, height, level, num_layers); + return fmt::format("ImageView CubeArray 0x{:X} {}x{}{}|{}", addr, width, height, level, + num_layers); case ImageViewType::Rect: - return fmt::format("ImageView Rect {}x{}{}", width, height, level); + return fmt::format("ImageView Rect 0x{:X} {}x{}{}", addr, width, height, level); case ImageViewType::Buffer: - return fmt::format("BufferView {}", width); + return fmt::format("BufferView 0x{:X} {}", addr, width); } return "Invalid"; } diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h index f1f0a057b..9ee57a076 100644 --- a/src/video_core/texture_cache/formatter.h +++ b/src/video_core/texture_cache/formatter.h @@ -179,6 +179,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str return "ASTC_2D_6X6_SRGB"; case PixelFormat::ASTC_2D_10X6_UNORM: return "ASTC_2D_10X6_UNORM"; + case PixelFormat::ASTC_2D_10X6_SRGB: + return "ASTC_2D_10X6_SRGB"; case PixelFormat::ASTC_2D_10X5_UNORM: return "ASTC_2D_10X5_UNORM"; case PixelFormat::ASTC_2D_10X5_SRGB: @@ -187,6 +189,10 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str return "ASTC_2D_10X10_UNORM"; case PixelFormat::ASTC_2D_10X10_SRGB: return "ASTC_2D_10X10_SRGB"; + case PixelFormat::ASTC_2D_12X10_UNORM: + return "ASTC_2D_12X10_UNORM"; + case PixelFormat::ASTC_2D_12X10_SRGB: + return "ASTC_2D_12X10_SRGB"; case PixelFormat::ASTC_2D_12X12_UNORM: return "ASTC_2D_12X12_UNORM"; case PixelFormat::ASTC_2D_12X12_SRGB: @@ -268,7 +274,7 @@ struct RenderTargets; [[nodiscard]] std::string Name(const ImageBase& image); -[[nodiscard]] std::string Name(const ImageViewBase& image_view); +[[nodiscard]] std::string Name(const ImageViewBase& image_view, GPUVAddr addr); [[nodiscard]] std::string Name(const RenderTargets& render_targets); diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 11f3f78a1..e8ddde691 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -4,6 +4,7 @@ #include <fmt/format.h> #include "common/assert.h" +#include "common/settings.h" #include "video_core/surface.h" #include "video_core/texture_cache/format_lookup_table.h" #include "video_core/texture_cache/image_info.h" @@ -22,6 +23,8 @@ using VideoCore::Surface::PixelFormat; using VideoCore::Surface::SurfaceType; ImageInfo::ImageInfo(const TICEntry& config) noexcept { + forced_flushed = config.IsPitchLinear() && !Settings::values.use_reactive_flushing.GetValue(); + dma_downloaded = forced_flushed; format = PixelFormatFromTextureInfo(config.format, config.r_type, config.g_type, config.b_type, config.a_type, config.srgb_conversion); num_samples = NumSamples(config.msaa_mode); @@ -117,6 +120,9 @@ ImageInfo::ImageInfo(const TICEntry& config) noexcept { ImageInfo::ImageInfo(const Maxwell3D::Regs::RenderTargetConfig& ct, Tegra::Texture::MsaaMode msaa_mode) noexcept { + forced_flushed = + ct.tile_mode.is_pitch_linear && !Settings::values.use_reactive_flushing.GetValue(); + dma_downloaded = forced_flushed; format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(ct.format); rescaleable = false; if (ct.tile_mode.is_pitch_linear) { @@ -155,6 +161,9 @@ ImageInfo::ImageInfo(const Maxwell3D::Regs::RenderTargetConfig& ct, ImageInfo::ImageInfo(const Maxwell3D::Regs::Zeta& zt, const Maxwell3D::Regs::ZetaSize& zt_size, Tegra::Texture::MsaaMode msaa_mode) noexcept { + forced_flushed = + zt.tile_mode.is_pitch_linear && !Settings::values.use_reactive_flushing.GetValue(); + dma_downloaded = forced_flushed; format = VideoCore::Surface::PixelFormatFromDepthFormat(zt.format); size.width = zt_size.width; size.height = zt_size.height; @@ -195,6 +204,9 @@ ImageInfo::ImageInfo(const Maxwell3D::Regs::Zeta& zt, const Maxwell3D::Regs::Zet ImageInfo::ImageInfo(const Fermi2D::Surface& config) noexcept { UNIMPLEMENTED_IF_MSG(config.layer != 0, "Surface layer is not zero"); + forced_flushed = config.linear == Fermi2D::MemoryLayout::Pitch && + !Settings::values.use_reactive_flushing.GetValue(); + dma_downloaded = forced_flushed; format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(config.format); rescaleable = false; if (config.linear == Fermi2D::MemoryLayout::Pitch) { diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h index 4b7dfa315..8a4cb0cbd 100644 --- a/src/video_core/texture_cache/image_info.h +++ b/src/video_core/texture_cache/image_info.h @@ -39,6 +39,8 @@ struct ImageInfo { u32 tile_width_spacing = 0; bool rescaleable = false; bool downscaleable = false; + bool forced_flushed = false; + bool dma_downloaded = false; }; } // namespace VideoCommon diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp index 04fb84bfa..d134b6738 100644 --- a/src/video_core/texture_cache/image_view_base.cpp +++ b/src/video_core/texture_cache/image_view_base.cpp @@ -4,7 +4,6 @@ #include <algorithm> #include "common/assert.h" -#include "common/settings.h" #include "video_core/compatible_formats.h" #include "video_core/surface.h" #include "video_core/texture_cache/formatter.h" @@ -16,8 +15,8 @@ namespace VideoCommon { ImageViewBase::ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_info, - ImageId image_id_) - : image_id{image_id_}, format{info.format}, type{info.type}, range{info.range}, + ImageId image_id_, GPUVAddr addr) + : image_id{image_id_}, gpu_addr{addr}, format{info.format}, type{info.type}, range{info.range}, size{ .width = std::max(image_info.size.width >> range.base.level, 1u), .height = std::max(image_info.size.height >> range.base.level, 1u), @@ -26,8 +25,7 @@ ImageViewBase::ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_i ASSERT_MSG(VideoCore::Surface::IsViewCompatible(image_info.format, info.format, false, true), "Image view format {} is incompatible with image format {}", info.format, image_info.format); - const bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue(); - if (image_info.type == ImageType::Linear && is_async) { + if (image_info.forced_flushed) { flags |= ImageViewFlagBits::PreemtiveDownload; } if (image_info.type == ImageType::e3D && info.type != ImageViewType::e3D) { @@ -35,8 +33,8 @@ ImageViewBase::ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_i } } -ImageViewBase::ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_info) - : image_id{NULL_IMAGE_ID}, format{info.format}, type{ImageViewType::Buffer}, +ImageViewBase::ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_info, GPUVAddr addr) + : image_id{NULL_IMAGE_ID}, gpu_addr{addr}, format{info.format}, type{ImageViewType::Buffer}, size{ .width = info.size.width, .height = 1, diff --git a/src/video_core/texture_cache/image_view_base.h b/src/video_core/texture_cache/image_view_base.h index 69c9776e7..a25ae1d4a 100644 --- a/src/video_core/texture_cache/image_view_base.h +++ b/src/video_core/texture_cache/image_view_base.h @@ -24,9 +24,9 @@ enum class ImageViewFlagBits : u16 { DECLARE_ENUM_FLAG_OPERATORS(ImageViewFlagBits) struct ImageViewBase { - explicit ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_info, - ImageId image_id); - explicit ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_info); + explicit ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_info, ImageId image_id, + GPUVAddr addr); + explicit ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_info, GPUVAddr addr); explicit ImageViewBase(const NullImageViewParams&); [[nodiscard]] bool IsBuffer() const noexcept { @@ -34,6 +34,7 @@ struct ImageViewBase { } ImageId image_id{}; + GPUVAddr gpu_addr = 0; PixelFormat format{}; ImageViewType type{}; SubresourceRange range; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index e601f8446..e1198dcf8 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -491,6 +491,32 @@ void TextureCache<P>::DownloadMemory(VAddr cpu_addr, size_t size) { } template <class P> +std::optional<VideoCore::RasterizerDownloadArea> TextureCache<P>::GetFlushArea(VAddr cpu_addr, + u64 size) { + std::optional<VideoCore::RasterizerDownloadArea> area{}; + ForEachImageInRegion(cpu_addr, size, [&](ImageId, ImageBase& image) { + if (False(image.flags & ImageFlagBits::GpuModified)) { + return; + } + if (!area) { + area.emplace(); + area->start_address = cpu_addr; + area->end_address = cpu_addr + size; + area->preemtive = true; + } + area->start_address = std::min(area->start_address, image.cpu_addr); + area->end_address = std::max(area->end_address, image.cpu_addr_end); + for (auto image_view_id : image.image_view_ids) { + auto& image_view = slot_image_views[image_view_id]; + image_view.flags |= ImageViewFlagBits::PreemtiveDownload; + } + area->preemtive &= image.info.forced_flushed; + image.info.forced_flushed = true; + }); + return area; +} + +template <class P> void TextureCache<P>::UnmapMemory(VAddr cpu_addr, size_t size) { std::vector<ImageId> deleted_images; ForEachImageInRegion(cpu_addr, size, [&](ImageId id, Image&) { deleted_images.push_back(id); }); @@ -683,6 +709,7 @@ void TextureCache<P>::CommitAsyncFlushes() { download_info.async_buffer_id = last_async_buffer_id; } } + if (any_none_dma) { auto download_map = runtime.DownloadStagingBuffer(total_size_bytes, true); for (const PendingDownload& download_info : download_ids) { @@ -695,6 +722,7 @@ void TextureCache<P>::CommitAsyncFlushes() { } uncommitted_async_buffers.emplace_back(download_map); } + async_buffers.emplace_back(std::move(uncommitted_async_buffers)); uncommitted_async_buffers.clear(); } @@ -783,17 +811,22 @@ void TextureCache<P>::PopAsyncFlushes() { } template <class P> -ImageId TextureCache<P>::DmaImageId(const Tegra::DMA::ImageOperand& operand) { +ImageId TextureCache<P>::DmaImageId(const Tegra::DMA::ImageOperand& operand, bool is_upload) { const ImageInfo dst_info(operand); const ImageId dst_id = FindDMAImage(dst_info, operand.address); if (!dst_id) { return NULL_IMAGE_ID; } - const auto& image = slot_images[dst_id]; + auto& image = slot_images[dst_id]; if (False(image.flags & ImageFlagBits::GpuModified)) { // No need to waste time on an image that's synced with guest return NULL_IMAGE_ID; } + if (!is_upload && !image.info.dma_downloaded) { + // Force a full sync. + image.info.dma_downloaded = true; + return NULL_IMAGE_ID; + } const auto base = image.TryFindBase(operand.address); if (!base) { return NULL_IMAGE_ID; @@ -888,7 +921,7 @@ void TextureCache<P>::DownloadImageIntoBuffer(typename TextureCache<P>::Image* i buffer, download_map.buffer, }; - std::array buffer_offsets{ + std::array<u64, 2> buffer_offsets{ buffer_offset, download_map.offset, }; @@ -1290,7 +1323,6 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA all_siblings.push_back(overlap_id); } else { bad_overlap_ids.push_back(overlap_id); - overlap.flags |= ImageFlagBits::BadOverlap; } }; ForEachImageInRegion(cpu_addr, size_bytes, region_check); @@ -1359,6 +1391,12 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA ScaleDown(new_image); } + std::ranges::sort(overlap_ids, [this](const ImageId lhs, const ImageId rhs) { + const ImageBase& lhs_image = slot_images[lhs]; + const ImageBase& rhs_image = slot_images[rhs]; + return lhs_image.modification_tick < rhs_image.modification_tick; + }); + for (const ImageId overlap_id : overlap_ids) { Image& overlap = slot_images[overlap_id]; if (True(overlap.flags & ImageFlagBits::GpuModified)) { @@ -1395,7 +1433,12 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA ImageBase& aliased = slot_images[aliased_id]; aliased.overlapping_images.push_back(new_image_id); new_image.overlapping_images.push_back(aliased_id); - new_image.flags |= ImageFlagBits::BadOverlap; + if (aliased.info.resources.levels == 1 && aliased.overlapping_images.size() > 1) { + aliased.flags |= ImageFlagBits::BadOverlap; + } + if (new_image.info.resources.levels == 1 && new_image.overlapping_images.size() > 1) { + new_image.flags |= ImageFlagBits::BadOverlap; + } } RegisterImage(new_image_id); return new_image_id; diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 758b7e212..0720494e5 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -179,6 +179,8 @@ public: /// Download contents of host images to guest memory in a region void DownloadMemory(VAddr cpu_addr, size_t size); + std::optional<VideoCore::RasterizerDownloadArea> GetFlushArea(VAddr cpu_addr, u64 size); + /// Remove images in a region void UnmapMemory(VAddr cpu_addr, size_t size); @@ -205,7 +207,7 @@ public: /// Pop asynchronous downloads void PopAsyncFlushes(); - [[nodiscard]] ImageId DmaImageId(const Tegra::DMA::ImageOperand& operand); + [[nodiscard]] ImageId DmaImageId(const Tegra::DMA::ImageOperand& operand, bool is_upload); [[nodiscard]] std::pair<Image*, BufferImageCopy> DmaBufferImageCopy( const Tegra::DMA::ImageCopy& copy_info, const Tegra::DMA::BufferOperand& buffer_operand, diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp index de37db684..f1071aa23 100644 --- a/src/video_core/texture_cache/util.cpp +++ b/src/video_core/texture_cache/util.cpp @@ -896,11 +896,11 @@ void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8 ASSERT(copy.buffer_row_length == Common::AlignUp(mip_size.width, tile_size.width)); ASSERT(copy.buffer_image_height == Common::AlignUp(mip_size.height, tile_size.height)); if (IsPixelFormatASTC(info.format)) { - ASSERT(copy.image_extent.depth == 1); - Tegra::Texture::ASTC::Decompress(input.subspan(copy.buffer_offset), - copy.image_extent.width, copy.image_extent.height, - copy.image_subresource.num_layers, tile_size.width, - tile_size.height, output.subspan(output_offset)); + Tegra::Texture::ASTC::Decompress( + input.subspan(copy.buffer_offset), copy.image_extent.width, + copy.image_extent.height, + copy.image_subresource.num_layers * copy.image_extent.depth, tile_size.width, + tile_size.height, output.subspan(output_offset)); } else { DecompressBC4(input.subspan(copy.buffer_offset), copy.image_extent, output.subspan(output_offset)); diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 6f288b3f8..6ffca2af2 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -617,7 +617,9 @@ bool Device::ShouldBoostClocks() const { const bool is_steam_deck = vendor_id == 0x1002 && device_id == 0x163F; - return validated_driver && !is_steam_deck; + const bool is_debugging = this->HasDebuggingToolAttached(); + + return validated_driver && !is_steam_deck && !is_debugging; } bool Device::GetSuitability(bool requires_swapchain) { diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 7d5018151..5f1c63ff9 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -10,6 +10,7 @@ #include <vector> #include "common/common_types.h" +#include "common/settings.h" #include "video_core/vulkan_common/vulkan_wrapper.h" // Define all features which may be used by the implementation here. @@ -510,7 +511,7 @@ public: /// Returns true when a known debugging tool is attached. bool HasDebuggingToolAttached() const { - return has_renderdoc || has_nsight_graphics; + return has_renderdoc || has_nsight_graphics || Settings::values.renderer_debug.GetValue(); } /// Returns true when the device does not properly support cube compatibility. diff --git a/src/video_core/vulkan_common/vulkan_surface.cpp b/src/video_core/vulkan_common/vulkan_surface.cpp index fa9bafa20..c34599365 100644 --- a/src/video_core/vulkan_common/vulkan_surface.cpp +++ b/src/video_core/vulkan_common/vulkan_surface.cpp @@ -23,10 +23,10 @@ namespace Vulkan { -vk::SurfaceKHR CreateSurface(const vk::Instance& instance, - const Core::Frontend::EmuWindow& emu_window) { +vk::SurfaceKHR CreateSurface( + const vk::Instance& instance, + [[maybe_unused]] const Core::Frontend::EmuWindow::WindowSystemInfo& window_info) { [[maybe_unused]] const vk::InstanceDispatch& dld = instance.Dispatch(); - [[maybe_unused]] const auto& window_info = emu_window.GetWindowInfo(); VkSurfaceKHR unsafe_surface = nullptr; #ifdef _WIN32 diff --git a/src/video_core/vulkan_common/vulkan_surface.h b/src/video_core/vulkan_common/vulkan_surface.h index 5725143e6..5e18c06c4 100644 --- a/src/video_core/vulkan_common/vulkan_surface.h +++ b/src/video_core/vulkan_common/vulkan_surface.h @@ -3,15 +3,12 @@ #pragma once +#include "core/frontend/emu_window.h" #include "video_core/vulkan_common/vulkan_wrapper.h" -namespace Core::Frontend { -class EmuWindow; -} - namespace Vulkan { -[[nodiscard]] vk::SurfaceKHR CreateSurface(const vk::Instance& instance, - const Core::Frontend::EmuWindow& emu_window); +[[nodiscard]] vk::SurfaceKHR CreateSurface( + const vk::Instance& instance, const Core::Frontend::EmuWindow::WindowSystemInfo& window_info); } // namespace Vulkan diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 0f8c1e6a6..2d7b9ab65 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -189,6 +189,8 @@ add_executable(yuzu multiplayer/state.h multiplayer/validation.h precompiled_headers.h + qt_common.cpp + qt_common.h startup_checks.cpp startup_checks.h uisettings.cpp diff --git a/src/yuzu/applets/qt_amiibo_settings.cpp b/src/yuzu/applets/qt_amiibo_settings.cpp index 4559df5b1..4988fcc83 100644 --- a/src/yuzu/applets/qt_amiibo_settings.cpp +++ b/src/yuzu/applets/qt_amiibo_settings.cpp @@ -8,7 +8,7 @@ #include "common/assert.h" #include "common/string_util.h" -#include "core/hle/service/nfp/nfp_device.h" +#include "core/hle/service/nfc/common/device.h" #include "core/hle/service/nfp/nfp_result.h" #include "input_common/drivers/virtual_amiibo.h" #include "input_common/main.h" @@ -22,7 +22,7 @@ QtAmiiboSettingsDialog::QtAmiiboSettingsDialog(QWidget* parent, Core::Frontend::CabinetParameters parameters_, InputCommon::InputSubsystem* input_subsystem_, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device_) + std::shared_ptr<Service::NFC::NfcDevice> nfp_device_) : QDialog(parent), ui(std::make_unique<Ui::QtAmiiboSettingsDialog>()), input_subsystem{input_subsystem_}, nfp_device{std::move(nfp_device_)}, parameters(std::move(parameters_)) { @@ -52,11 +52,11 @@ void QtAmiiboSettingsDialog::LoadInfo() { return; } - if (nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagFound && - nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagMounted) { + if (nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagFound && + nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagMounted) { return; } - nfp_device->Mount(Service::NFP::MountTarget::All); + nfp_device->Mount(Service::NFP::ModelType::Amiibo, Service::NFP::MountTarget::All); LoadAmiiboInfo(); LoadAmiiboData(); @@ -261,7 +261,7 @@ void QtAmiiboSettings::Close() const { void QtAmiiboSettings::ShowCabinetApplet( const Core::Frontend::CabinetCallback& callback_, const Core::Frontend::CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const { + std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const { callback = std::move(callback_); emit MainWindowShowAmiiboSettings(parameters, nfp_device); } diff --git a/src/yuzu/applets/qt_amiibo_settings.h b/src/yuzu/applets/qt_amiibo_settings.h index bc389a33f..ee66a0255 100644 --- a/src/yuzu/applets/qt_amiibo_settings.h +++ b/src/yuzu/applets/qt_amiibo_settings.h @@ -23,9 +23,9 @@ namespace Ui { class QtAmiiboSettingsDialog; } -namespace Service::NFP { -class NfpDevice; -} // namespace Service::NFP +namespace Service::NFC { +class NfcDevice; +} // namespace Service::NFC class QtAmiiboSettingsDialog final : public QDialog { Q_OBJECT @@ -33,7 +33,7 @@ class QtAmiiboSettingsDialog final : public QDialog { public: explicit QtAmiiboSettingsDialog(QWidget* parent, Core::Frontend::CabinetParameters parameters_, InputCommon::InputSubsystem* input_subsystem_, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device_); + std::shared_ptr<Service::NFC::NfcDevice> nfp_device_); ~QtAmiiboSettingsDialog() override; int exec() override; @@ -52,7 +52,7 @@ private: std::unique_ptr<Ui::QtAmiiboSettingsDialog> ui; InputCommon::InputSubsystem* input_subsystem; - std::shared_ptr<Service::NFP::NfpDevice> nfp_device; + std::shared_ptr<Service::NFC::NfcDevice> nfp_device; // Parameters sent in from the backend HLE applet. Core::Frontend::CabinetParameters parameters; @@ -71,11 +71,11 @@ public: void Close() const override; void ShowCabinetApplet(const Core::Frontend::CabinetCallback& callback_, const Core::Frontend::CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const override; + std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const override; signals: void MainWindowShowAmiiboSettings(const Core::Frontend::CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const; + std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const; void MainWindowRequestExit() const; private: diff --git a/src/yuzu/applets/qt_profile_select.cpp b/src/yuzu/applets/qt_profile_select.cpp index 2448e46b6..1f3f23038 100644 --- a/src/yuzu/applets/qt_profile_select.cpp +++ b/src/yuzu/applets/qt_profile_select.cpp @@ -95,6 +95,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog( scroll_area->setLayout(layout); connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser); + connect(tree_view, &QTreeView::doubleClicked, this, &QtProfileSelectionDialog::accept); connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, [this](Qt::Key key) { if (!this->isActiveWindow()) { diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 4c7bf28d8..59d226113 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -1,36 +1,48 @@ // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <algorithm> +#include <array> +#include <cmath> +#include <cstring> +#include <string> +#include <tuple> +#include <type_traits> #include <glad/glad.h> -#include <QApplication> +#include <QtCore/qglobal.h> #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA +#include <QCamera> #include <QCameraImageCapture> #include <QCameraInfo> #endif +#include <QCursor> +#include <QEvent> +#include <QGuiApplication> #include <QHBoxLayout> +#include <QKeyEvent> +#include <QLayout> +#include <QList> #include <QMessageBox> -#include <QPainter> #include <QScreen> -#include <QString> -#include <QStringList> +#include <QSize> +#include <QStringLiteral> +#include <QSurfaceFormat> +#include <QTimer> #include <QWindow> +#include <QtCore/qobjectdefs.h> #ifdef HAS_OPENGL #include <QOffscreenSurface> #include <QOpenGLContext> #endif -#if !defined(WIN32) -#include <qpa/qplatformnativeinterface.h> -#endif - -#include <fmt/format.h> - -#include "common/assert.h" #include "common/microprofile.h" +#include "common/polyfill_thread.h" #include "common/scm_rev.h" #include "common/settings.h" +#include "common/settings_input.h" +#include "common/thread.h" #include "core/core.h" #include "core/cpu_manager.h" #include "core/frontend/framebuffer_layout.h" @@ -40,11 +52,16 @@ #include "input_common/drivers/tas_input.h" #include "input_common/drivers/touch_screen.h" #include "input_common/main.h" +#include "video_core/gpu.h" +#include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" #include "yuzu/bootmanager.h" #include "yuzu/main.h" +#include "yuzu/qt_common.h" -static Core::Frontend::WindowSystemType GetWindowSystemType(); +class QObject; +class QPaintEngine; +class QSurface; EmuThread::EmuThread(Core::System& system) : m_system{system} {} @@ -154,7 +171,10 @@ public: // disable vsync for any shared contexts auto format = share_context->format(); - format.setSwapInterval(main_surface ? Settings::values.use_vsync.GetValue() : 0); + const int swap_interval = + Settings::values.vsync_mode.GetValue() == Settings::VSyncMode::Immediate ? 0 : 1; + + format.setSwapInterval(main_surface ? swap_interval : 0); context = std::make_unique<QOpenGLContext>(); context->setShareContext(share_context); @@ -221,7 +241,7 @@ public: explicit RenderWidget(GRenderWindow* parent) : QWidget(parent), render_window(parent) { setAttribute(Qt::WA_NativeWindow); setAttribute(Qt::WA_PaintOnScreen); - if (GetWindowSystemType() == Core::Frontend::WindowSystemType::Wayland) { + if (QtCommon::GetWindowSystemType() == Core::Frontend::WindowSystemType::Wayland) { setAttribute(Qt::WA_DontCreateNativeAncestors); } } @@ -259,46 +279,6 @@ struct NullRenderWidget : public RenderWidget { explicit NullRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {} }; -static Core::Frontend::WindowSystemType GetWindowSystemType() { - // Determine WSI type based on Qt platform. - QString platform_name = QGuiApplication::platformName(); - if (platform_name == QStringLiteral("windows")) - return Core::Frontend::WindowSystemType::Windows; - else if (platform_name == QStringLiteral("xcb")) - return Core::Frontend::WindowSystemType::X11; - else if (platform_name == QStringLiteral("wayland")) - return Core::Frontend::WindowSystemType::Wayland; - else if (platform_name == QStringLiteral("wayland-egl")) - return Core::Frontend::WindowSystemType::Wayland; - else if (platform_name == QStringLiteral("cocoa")) - return Core::Frontend::WindowSystemType::Cocoa; - else if (platform_name == QStringLiteral("android")) - return Core::Frontend::WindowSystemType::Android; - - LOG_CRITICAL(Frontend, "Unknown Qt platform {}!", platform_name.toStdString()); - return Core::Frontend::WindowSystemType::Windows; -} - -static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) { - Core::Frontend::EmuWindow::WindowSystemInfo wsi; - wsi.type = GetWindowSystemType(); - - // Our Win32 Qt external doesn't have the private API. -#if defined(WIN32) || defined(__APPLE__) - wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; -#else - QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); - wsi.display_connection = pni->nativeResourceForWindow("display", window); - if (wsi.type == Core::Frontend::WindowSystemType::Wayland) - wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr; - else - wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; -#endif - wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f; - - return wsi; -} - GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_, std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_, Core::System& system_) @@ -904,7 +884,7 @@ bool GRenderWindow::InitRenderTarget() { } // Update the Window System information with the new render target - window_info = GetWindowSystemInfo(child_widget->windowHandle()); + window_info = QtCommon::GetWindowSystemInfo(child_widget->windowHandle()); child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); layout()->addWidget(child_widget); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index bb4eca07f..b7b9d4141 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -5,27 +5,45 @@ #include <atomic> #include <condition_variable> +#include <cstddef> #include <memory> #include <mutex> +#include <utility> +#include <vector> +#include <QByteArray> #include <QImage> +#include <QObject> +#include <QPoint> +#include <QString> #include <QStringList> #include <QThread> -#include <QTouchEvent> #include <QWidget> +#include <qglobal.h> +#include <qnamespace.h> +#include <qobjectdefs.h> +#include "common/common_types.h" +#include "common/logging/log.h" #include "common/polyfill_thread.h" #include "common/thread.h" #include "core/frontend/emu_window.h" -class GRenderWindow; class GMainWindow; class QCamera; class QCameraImageCapture; +class QCloseEvent; +class QFocusEvent; class QKeyEvent; +class QMouseEvent; +class QObject; +class QResizeEvent; +class QShowEvent; +class QTimer; +class QTouchEvent; +class QWheelEvent; namespace Core { -enum class SystemResultStatus : u32; class System; } // namespace Core @@ -40,7 +58,6 @@ enum class TasState; namespace VideoCore { enum class LoadCallbackStage; -class RendererBase; } // namespace VideoCore class EmuThread final : public QThread { diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index bb731276e..a49d12266 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -6,6 +6,7 @@ #include <QSettings> #include "common/fs/fs.h" #include "common/fs/path_util.h" +#include "common/settings.h" #include "core/core.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/hid/controllers/npad.h" @@ -497,7 +498,7 @@ void Config::ReadCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); ReadGlobalSetting(Settings::values.use_multi_core); - ReadGlobalSetting(Settings::values.use_extended_memory_layout); + ReadGlobalSetting(Settings::values.use_unsafe_extended_memory_layout); qt_config->endGroup(); } @@ -692,6 +693,7 @@ void Config::ReadRendererValues() { qt_config->beginGroup(QStringLiteral("Renderer")); ReadGlobalSetting(Settings::values.renderer_backend); + ReadGlobalSetting(Settings::values.async_presentation); ReadGlobalSetting(Settings::values.renderer_force_max_clock); ReadGlobalSetting(Settings::values.vulkan_device); ReadGlobalSetting(Settings::values.fullscreen_mode); @@ -708,17 +710,20 @@ void Config::ReadRendererValues() { ReadGlobalSetting(Settings::values.nvdec_emulation); ReadGlobalSetting(Settings::values.accelerate_astc); ReadGlobalSetting(Settings::values.async_astc); - ReadGlobalSetting(Settings::values.use_vsync); + ReadGlobalSetting(Settings::values.use_reactive_flushing); ReadGlobalSetting(Settings::values.shader_backend); ReadGlobalSetting(Settings::values.use_asynchronous_shaders); ReadGlobalSetting(Settings::values.use_fast_gpu_time); - ReadGlobalSetting(Settings::values.use_pessimistic_flushes); ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache); ReadGlobalSetting(Settings::values.bg_red); ReadGlobalSetting(Settings::values.bg_green); ReadGlobalSetting(Settings::values.bg_blue); if (global) { + Settings::values.vsync_mode.SetValue(static_cast<Settings::VSyncMode>( + ReadSetting(QString::fromStdString(Settings::values.vsync_mode.GetLabel()), + static_cast<u32>(Settings::values.vsync_mode.GetDefault())) + .value<u32>())); ReadBasicSetting(Settings::values.renderer_debug); ReadBasicSetting(Settings::values.renderer_shader_feedback); ReadBasicSetting(Settings::values.enable_nsight_aftermath); @@ -1161,7 +1166,7 @@ void Config::SaveCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); WriteGlobalSetting(Settings::values.use_multi_core); - WriteGlobalSetting(Settings::values.use_extended_memory_layout); + WriteGlobalSetting(Settings::values.use_unsafe_extended_memory_layout); qt_config->endGroup(); } @@ -1313,6 +1318,7 @@ void Config::SaveRendererValues() { static_cast<u32>(Settings::values.renderer_backend.GetValue(global)), static_cast<u32>(Settings::values.renderer_backend.GetDefault()), Settings::values.renderer_backend.UsingGlobal()); + WriteGlobalSetting(Settings::values.async_presentation); WriteGlobalSetting(Settings::values.renderer_force_max_clock); WriteGlobalSetting(Settings::values.vulkan_device); WriteSetting(QString::fromStdString(Settings::values.fullscreen_mode.GetLabel()), @@ -1350,20 +1356,22 @@ void Config::SaveRendererValues() { Settings::values.nvdec_emulation.UsingGlobal()); WriteGlobalSetting(Settings::values.accelerate_astc); WriteGlobalSetting(Settings::values.async_astc); - WriteGlobalSetting(Settings::values.use_vsync); + WriteGlobalSetting(Settings::values.use_reactive_flushing); WriteSetting(QString::fromStdString(Settings::values.shader_backend.GetLabel()), static_cast<u32>(Settings::values.shader_backend.GetValue(global)), static_cast<u32>(Settings::values.shader_backend.GetDefault()), Settings::values.shader_backend.UsingGlobal()); WriteGlobalSetting(Settings::values.use_asynchronous_shaders); WriteGlobalSetting(Settings::values.use_fast_gpu_time); - WriteGlobalSetting(Settings::values.use_pessimistic_flushes); WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache); WriteGlobalSetting(Settings::values.bg_red); WriteGlobalSetting(Settings::values.bg_green); WriteGlobalSetting(Settings::values.bg_blue); if (global) { + WriteSetting(QString::fromStdString(Settings::values.vsync_mode.GetLabel()), + static_cast<u32>(Settings::values.vsync_mode.GetValue()), + static_cast<u32>(Settings::values.vsync_mode.GetDefault())); WriteBasicSetting(Settings::values.renderer_debug); WriteBasicSetting(Settings::values.renderer_shader_feedback); WriteBasicSetting(Settings::values.enable_nsight_aftermath); diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 207bcdc4d..26258d744 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -35,9 +35,6 @@ void ConfigureGeneral::SetConfiguration() { ui->use_multi_core->setEnabled(runtime_lock); ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue()); - ui->use_extended_memory_layout->setEnabled(runtime_lock); - ui->use_extended_memory_layout->setChecked( - Settings::values.use_extended_memory_layout.GetValue()); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing.GetValue()); ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot.GetValue()); @@ -79,9 +76,6 @@ void ConfigureGeneral::ResetDefaults() { void ConfigureGeneral::ApplyConfiguration() { ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, ui->use_multi_core, use_multi_core); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_extended_memory_layout, - ui->use_extended_memory_layout, - use_extended_memory_layout); if (Settings::IsConfiguringGlobal()) { UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); @@ -141,9 +135,6 @@ void ConfigureGeneral::SetupPerGameUI() { Settings::values.use_speed_limit, use_speed_limit); ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core, use_multi_core); - ConfigurationShared::SetColoredTristate(ui->use_extended_memory_layout, - Settings::values.use_extended_memory_layout, - use_extended_memory_layout); connect(ui->toggle_speed_limit, &QCheckBox::clicked, ui->speed_limit, [this]() { ui->speed_limit->setEnabled(ui->toggle_speed_limit->isChecked() && diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index a090c1a3f..7ff63f425 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -47,7 +47,6 @@ private: ConfigurationShared::CheckState use_speed_limit; ConfigurationShared::CheckState use_multi_core; - ConfigurationShared::CheckState use_extended_memory_layout; const Core::System& system; }; diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index add110bb0..986a1625b 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -62,13 +62,6 @@ </widget> </item> <item> - <widget class="QCheckBox" name="use_extended_memory_layout"> - <property name="text"> - <string>Extended memory layout (8GB DRAM)</string> - </property> - </widget> - </item> - <item> <widget class="QCheckBox" name="toggle_check_exit"> <property name="text"> <string>Confirm exit while emulation is running</string> diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index e9388daad..76e5b7499 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -4,20 +4,76 @@ // Include this early to include Vulkan headers how we want to #include "video_core/vulkan_common/vulkan_wrapper.h" +#include <algorithm> +#include <iosfwd> +#include <iterator> +#include <string> +#include <tuple> +#include <utility> +#include <vector> +#include <QBoxLayout> +#include <QCheckBox> #include <QColorDialog> -#include <QVulkanInstance> +#include <QComboBox> +#include <QIcon> +#include <QLabel> +#include <QPixmap> +#include <QPushButton> +#include <QSlider> +#include <QStringLiteral> +#include <QtCore/qobjectdefs.h> +#include <qcoreevent.h> +#include <qglobal.h> +#include <vulkan/vulkan_core.h> #include "common/common_types.h" +#include "common/dynamic_library.h" #include "common/logging/log.h" #include "common/settings.h" #include "core/core.h" #include "ui_configure_graphics.h" #include "video_core/vulkan_common/vulkan_instance.h" #include "video_core/vulkan_common/vulkan_library.h" +#include "video_core/vulkan_common/vulkan_surface.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics.h" +#include "yuzu/qt_common.h" #include "yuzu/uisettings.h" +static const std::vector<VkPresentModeKHR> default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_FIFO_KHR}; + +// Converts a setting to a present mode (or vice versa) +static constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) { + switch (mode) { + case Settings::VSyncMode::Immediate: + return VK_PRESENT_MODE_IMMEDIATE_KHR; + case Settings::VSyncMode::Mailbox: + return VK_PRESENT_MODE_MAILBOX_KHR; + case Settings::VSyncMode::FIFO: + return VK_PRESENT_MODE_FIFO_KHR; + case Settings::VSyncMode::FIFORelaxed: + return VK_PRESENT_MODE_FIFO_RELAXED_KHR; + default: + return VK_PRESENT_MODE_FIFO_KHR; + } +} + +static constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) { + switch (mode) { + case VK_PRESENT_MODE_IMMEDIATE_KHR: + return Settings::VSyncMode::Immediate; + case VK_PRESENT_MODE_MAILBOX_KHR: + return Settings::VSyncMode::Mailbox; + case VK_PRESENT_MODE_FIFO_KHR: + return Settings::VSyncMode::FIFO; + case VK_PRESENT_MODE_FIFO_RELAXED_KHR: + return Settings::VSyncMode::FIFORelaxed; + default: + return Settings::VSyncMode::FIFO; + } +} + ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* parent) : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, system{system_} { vulkan_device = Settings::values.vulkan_device.GetValue(); @@ -39,13 +95,16 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] { UpdateAPILayout(); + PopulateVSyncModeSelection(); if (!Settings::IsConfiguringGlobal()) { ConfigurationShared::SetHighlight( ui->api_widget, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX); } }); - connect(ui->device, qOverload<int>(&QComboBox::activated), this, - [this](int device) { UpdateDeviceSelection(device); }); + connect(ui->device, qOverload<int>(&QComboBox::activated), this, [this](int device) { + UpdateDeviceSelection(device); + PopulateVSyncModeSelection(); + }); connect(ui->backend, qOverload<int>(&QComboBox::activated), this, [this](int backend) { UpdateShaderBackendSelection(backend); }); @@ -70,6 +129,43 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren ui->fsr_sharpening_label->setVisible(Settings::IsConfiguringGlobal()); } +void ConfigureGraphics::PopulateVSyncModeSelection() { + const Settings::RendererBackend backend{GetCurrentGraphicsBackend()}; + if (backend == Settings::RendererBackend::Null) { + ui->vsync_mode_combobox->setEnabled(false); + return; + } + ui->vsync_mode_combobox->setEnabled(true); + + const int current_index = //< current selected vsync mode from combobox + ui->vsync_mode_combobox->currentIndex(); + const auto current_mode = //< current selected vsync mode as a VkPresentModeKHR + current_index == -1 ? VSyncSettingToMode(Settings::values.vsync_mode.GetValue()) + : vsync_mode_combobox_enum_map[current_index]; + int index{}; + const int device{ui->device->currentIndex()}; //< current selected Vulkan device + const auto& present_modes = //< relevant vector of present modes for the selected device or API + backend == Settings::RendererBackend::Vulkan ? device_present_modes[device] + : default_present_modes; + + ui->vsync_mode_combobox->clear(); + vsync_mode_combobox_enum_map.clear(); + vsync_mode_combobox_enum_map.reserve(present_modes.size()); + for (const auto present_mode : present_modes) { + const auto mode_name = TranslateVSyncMode(present_mode, backend); + if (mode_name.isEmpty()) { + continue; + } + + ui->vsync_mode_combobox->insertItem(index, mode_name); + vsync_mode_combobox_enum_map.push_back(present_mode); + if (present_mode == current_mode) { + ui->vsync_mode_combobox->setCurrentIndex(index); + } + index++; + } +} + void ConfigureGraphics::UpdateDeviceSelection(int device) { if (device == -1) { return; @@ -99,6 +195,9 @@ void ConfigureGraphics::SetConfiguration() { ui->nvdec_emulation_widget->setEnabled(runtime_lock); ui->resolution_combobox->setEnabled(runtime_lock); ui->accelerate_astc->setEnabled(runtime_lock); + ui->vsync_mode_layout->setEnabled(runtime_lock || + Settings::values.renderer_backend.GetValue() == + Settings::RendererBackend::Vulkan); ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue()); ui->use_asynchronous_gpu_emulation->setChecked( Settings::values.use_asynchronous_gpu_emulation.GetValue()); @@ -170,7 +269,24 @@ void ConfigureGraphics::SetConfiguration() { Settings::values.bg_green.GetValue(), Settings::values.bg_blue.GetValue())); UpdateAPILayout(); + PopulateVSyncModeSelection(); //< must happen after UpdateAPILayout SetFSRIndicatorText(ui->fsr_sharpening_slider->sliderPosition()); + + // VSync setting needs to be determined after populating the VSync combobox + if (Settings::IsConfiguringGlobal()) { + const auto vsync_mode_setting = Settings::values.vsync_mode.GetValue(); + const auto vsync_mode = VSyncSettingToMode(vsync_mode_setting); + int index{}; + for (const auto mode : vsync_mode_combobox_enum_map) { + if (mode == vsync_mode) { + break; + } + index++; + } + if (static_cast<unsigned long>(index) < vsync_mode_combobox_enum_map.size()) { + ui->vsync_mode_combobox->setCurrentIndex(index); + } + } } void ConfigureGraphics::SetFSRIndicatorText(int percentage) { @@ -178,6 +294,27 @@ void ConfigureGraphics::SetFSRIndicatorText(int percentage) { tr("%1%", "FSR sharpening percentage (e.g. 50%)").arg(100 - (percentage / 2))); } +const QString ConfigureGraphics::TranslateVSyncMode(VkPresentModeKHR mode, + Settings::RendererBackend backend) const { + switch (mode) { + case VK_PRESENT_MODE_IMMEDIATE_KHR: + return backend == Settings::RendererBackend::OpenGL + ? tr("Off") + : QStringLiteral("Immediate (%1)").arg(tr("VSync Off")); + case VK_PRESENT_MODE_MAILBOX_KHR: + return QStringLiteral("Mailbox (%1)").arg(tr("Recommended")); + case VK_PRESENT_MODE_FIFO_KHR: + return backend == Settings::RendererBackend::OpenGL + ? tr("On") + : QStringLiteral("FIFO (%1)").arg(tr("VSync On")); + case VK_PRESENT_MODE_FIFO_RELAXED_KHR: + return QStringLiteral("FIFO Relaxed"); + default: + return {}; + break; + } +} + void ConfigureGraphics::ApplyConfiguration() { const auto resolution_setup = static_cast<Settings::ResolutionSetup>( ui->resolution_combobox->currentIndex() - @@ -232,6 +369,10 @@ void ConfigureGraphics::ApplyConfiguration() { Settings::values.anti_aliasing.SetValue(anti_aliasing); } Settings::values.fsr_sharpening_slider.SetValue(ui->fsr_sharpening_slider->value()); + + const auto mode = vsync_mode_combobox_enum_map[ui->vsync_mode_combobox->currentIndex()]; + const auto vsync_mode = PresentModeToSetting(mode); + Settings::values.vsync_mode.SetValue(vsync_mode); } else { if (ui->resolution_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { Settings::values.resolution_setup.SetGlobal(true); @@ -345,7 +486,9 @@ void ConfigureGraphics::UpdateAPILayout() { ui->backend_widget->setVisible(true); break; case Settings::RendererBackend::Vulkan: - ui->device->setCurrentIndex(vulkan_device); + if (static_cast<int>(vulkan_device) < ui->device->count()) { + ui->device->setCurrentIndex(vulkan_device); + } ui->device_widget->setVisible(true); ui->backend_widget->setVisible(false); break; @@ -363,16 +506,27 @@ void ConfigureGraphics::RetrieveVulkanDevices() try { using namespace Vulkan; + auto* window = this->window()->windowHandle(); + auto wsi = QtCommon::GetWindowSystemInfo(window); + vk::InstanceDispatch dld; const Common::DynamicLibrary library = OpenLibrary(); - const vk::Instance instance = CreateInstance(library, dld, VK_API_VERSION_1_1); + const vk::Instance instance = CreateInstance(library, dld, VK_API_VERSION_1_1, wsi.type); const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices(); + vk::SurfaceKHR surface = //< needed to view present modes for a device + CreateSurface(instance, wsi); vulkan_devices.clear(); vulkan_devices.reserve(physical_devices.size()); + device_present_modes.clear(); + device_present_modes.reserve(physical_devices.size()); for (const VkPhysicalDevice device : physical_devices) { - const std::string name = vk::PhysicalDevice(device, dld).GetProperties().deviceName; + const auto physical_device = vk::PhysicalDevice(device, dld); + const std::string name = physical_device.GetProperties().deviceName; + const std::vector<VkPresentModeKHR> present_modes = + physical_device.GetSurfacePresentModesKHR(*surface); vulkan_devices.push_back(QString::fromStdString(name)); + device_present_modes.push_back(present_modes); } } catch (const Vulkan::vk::Exception& exception) { LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what()); @@ -465,4 +619,6 @@ void ConfigureGraphics::SetupPerGameUI() { ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true))); ConfigurationShared::InsertGlobalItem( ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true))); + + ui->vsync_mode_layout->setVisible(false); } diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index d98d6624e..901f604a5 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -5,9 +5,21 @@ #include <memory> #include <vector> +#include <QColor> #include <QString> #include <QWidget> -#include "common/settings.h" +#include <qobjectdefs.h> +#include <vulkan/vulkan_core.h> +#include "common/common_types.h" + +class QEvent; +class QObject; + +namespace Settings { +enum class NvdecEmulation : u32; +enum class RendererBackend : u32; +enum class ShaderBackend : u32; +} // namespace Settings namespace Core { class System; @@ -35,6 +47,7 @@ private: void changeEvent(QEvent* event) override; void RetranslateUI(); + void PopulateVSyncModeSelection(); void UpdateBackgroundColorButton(QColor color); void UpdateAPILayout(); void UpdateDeviceSelection(int device); @@ -43,6 +56,10 @@ private: void RetrieveVulkanDevices(); void SetFSRIndicatorText(int percentage); + /* Turns a Vulkan present mode into a textual string for a UI + * (and eventually for a human to read) */ + const QString TranslateVSyncMode(VkPresentModeKHR mode, + Settings::RendererBackend backend) const; void SetupPerGameUI(); @@ -58,6 +75,10 @@ private: ConfigurationShared::CheckState use_asynchronous_gpu_emulation; std::vector<QString> vulkan_devices; + std::vector<std::vector<VkPresentModeKHR>> device_present_modes; + std::vector<VkPresentModeKHR> + vsync_mode_combobox_enum_map; //< Keeps track of which present mode corresponds to which + // selection in the combobox u32 vulkan_device{}; Settings::ShaderBackend shader_backend{}; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index a45ec69ec..39f70e406 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -189,6 +189,44 @@ </widget> </item> <item> + <widget class="QWidget" name="vsync_mode_layout" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="vsync_mode_label"> + <property name="text"> + <string>VSync Mode:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="vsync_mode_combobox"> + <property name="toolTip"> + <string>FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. +FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down. +Mailbox can have lower latency than FIFO and does not tear but may drop frames. +Immediate (no synchronization) just presents whatever is available and can exhibit tearing.</string> + </property> + <property name="currentText"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <widget class="QWidget" name="nvdec_emulation_widget" native="true"> <layout class="QHBoxLayout" name="nvdec_emulation_layout"> <property name="leftMargin"> @@ -366,7 +404,7 @@ </item> <item> <property name="text"> - <string>1.5X (1080p/1620p) [EXPERIMENTAL]</string> + <string>1.5X (1080p/1620p) [EXPERIMENTAL]</string> </property> </item> <item> diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 59fb1b334..627ed8b17 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -21,18 +21,19 @@ ConfigureGraphicsAdvanced::~ConfigureGraphicsAdvanced() = default; void ConfigureGraphicsAdvanced::SetConfiguration() { const bool runtime_lock = !system.IsPoweredOn(); - ui->use_vsync->setEnabled(runtime_lock); + ui->use_reactive_flushing->setEnabled(runtime_lock); + ui->async_present->setEnabled(runtime_lock); ui->renderer_force_max_clock->setEnabled(runtime_lock); ui->async_astc->setEnabled(runtime_lock); ui->use_asynchronous_shaders->setEnabled(runtime_lock); ui->anisotropic_filtering_combobox->setEnabled(runtime_lock); + ui->async_present->setChecked(Settings::values.async_presentation.GetValue()); ui->renderer_force_max_clock->setChecked(Settings::values.renderer_force_max_clock.GetValue()); - ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); + ui->use_reactive_flushing->setChecked(Settings::values.use_reactive_flushing.GetValue()); ui->async_astc->setChecked(Settings::values.async_astc.GetValue()); ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); - ui->use_pessimistic_flushes->setChecked(Settings::values.use_pessimistic_flushes.GetValue()); ui->use_vulkan_driver_pipeline_cache->setChecked( Settings::values.use_vulkan_driver_pipeline_cache.GetValue()); @@ -54,12 +55,15 @@ void ConfigureGraphicsAdvanced::SetConfiguration() { void ConfigureGraphicsAdvanced::ApplyConfiguration() { ConfigurationShared::ApplyPerGameSetting(&Settings::values.gpu_accuracy, ui->gpu_accuracy); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_presentation, + ui->async_present, async_present); ConfigurationShared::ApplyPerGameSetting(&Settings::values.renderer_force_max_clock, ui->renderer_force_max_clock, renderer_force_max_clock); ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy, ui->anisotropic_filtering_combobox); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, use_vsync); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_reactive_flushing, + ui->use_reactive_flushing, use_reactive_flushing); ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_astc, ui->async_astc, async_astc); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders, @@ -67,8 +71,6 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() { use_asynchronous_shaders); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, ui->use_fast_gpu_time, use_fast_gpu_time); - ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_pessimistic_flushes, - ui->use_pessimistic_flushes, use_pessimistic_flushes); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vulkan_driver_pipeline_cache, ui->use_vulkan_driver_pipeline_cache, use_vulkan_driver_pipeline_cache); @@ -90,15 +92,14 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { // Disable if not global (only happens during game) if (Settings::IsConfiguringGlobal()) { ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal()); + ui->async_present->setEnabled(Settings::values.async_presentation.UsingGlobal()); ui->renderer_force_max_clock->setEnabled( Settings::values.renderer_force_max_clock.UsingGlobal()); - ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal()); + ui->use_reactive_flushing->setEnabled(Settings::values.use_reactive_flushing.UsingGlobal()); ui->async_astc->setEnabled(Settings::values.async_astc.UsingGlobal()); ui->use_asynchronous_shaders->setEnabled( Settings::values.use_asynchronous_shaders.UsingGlobal()); ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); - ui->use_pessimistic_flushes->setEnabled( - Settings::values.use_pessimistic_flushes.UsingGlobal()); ui->use_vulkan_driver_pipeline_cache->setEnabled( Settings::values.use_vulkan_driver_pipeline_cache.UsingGlobal()); ui->anisotropic_filtering_combobox->setEnabled( @@ -107,10 +108,13 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { return; } + ConfigurationShared::SetColoredTristate(ui->async_present, Settings::values.async_presentation, + async_present); ConfigurationShared::SetColoredTristate(ui->renderer_force_max_clock, Settings::values.renderer_force_max_clock, renderer_force_max_clock); - ConfigurationShared::SetColoredTristate(ui->use_vsync, Settings::values.use_vsync, use_vsync); + ConfigurationShared::SetColoredTristate( + ui->use_reactive_flushing, Settings::values.use_reactive_flushing, use_reactive_flushing); ConfigurationShared::SetColoredTristate(ui->async_astc, Settings::values.async_astc, async_astc); ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders, @@ -118,9 +122,6 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { use_asynchronous_shaders); ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time, Settings::values.use_fast_gpu_time, use_fast_gpu_time); - ConfigurationShared::SetColoredTristate(ui->use_pessimistic_flushes, - Settings::values.use_pessimistic_flushes, - use_pessimistic_flushes); ConfigurationShared::SetColoredTristate(ui->use_vulkan_driver_pipeline_cache, Settings::values.use_vulkan_driver_pipeline_cache, use_vulkan_driver_pipeline_cache); diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index bf1b04749..ae3c10946 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -36,12 +36,13 @@ private: std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui; + ConfigurationShared::CheckState async_present; ConfigurationShared::CheckState renderer_force_max_clock; ConfigurationShared::CheckState use_vsync; ConfigurationShared::CheckState async_astc; + ConfigurationShared::CheckState use_reactive_flushing; ConfigurationShared::CheckState use_asynchronous_shaders; ConfigurationShared::CheckState use_fast_gpu_time; - ConfigurationShared::CheckState use_pessimistic_flushes; ConfigurationShared::CheckState use_vulkan_driver_pipeline_cache; const Core::System& system; diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index a7dbdc18c..9d8cbea09 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>404</width> - <height>321</height> + <height>376</height> </rect> </property> <property name="windowTitle"> @@ -70,22 +70,19 @@ </widget> </item> <item> - <widget class="QCheckBox" name="renderer_force_max_clock"> - <property name="toolTip"> - <string>Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed.</string> - </property> + <widget class="QCheckBox" name="async_present"> <property name="text"> - <string>Force maximum clocks (Vulkan only)</string> + <string>Enable asynchronous presentation (Vulkan only)</string> </property> </widget> </item> <item> - <widget class="QCheckBox" name="use_vsync"> + <widget class="QCheckBox" name="renderer_force_max_clock"> <property name="toolTip"> - <string>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference.</string> + <string>Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed.</string> </property> <property name="text"> - <string>Use VSync</string> + <string>Force maximum clocks (Vulkan only)</string> </property> </widget> </item> @@ -100,39 +97,39 @@ </widget> </item> <item> - <widget class="QCheckBox" name="use_asynchronous_shaders"> + <widget class="QCheckBox" name="use_reactive_flushing"> <property name="toolTip"> - <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string> + <string>Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.</string> </property> <property name="text"> - <string>Use asynchronous shader building (Hack)</string> + <string>Enable Reactive Flushing</string> </property> </widget> </item> <item> - <widget class="QCheckBox" name="use_fast_gpu_time"> + <widget class="QCheckBox" name="use_asynchronous_shaders"> <property name="toolTip"> - <string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string> + <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string> </property> <property name="text"> - <string>Use Fast GPU Time (Hack)</string> + <string>Use asynchronous shader building (Hack)</string> </property> </widget> </item> <item> - <widget class="QCheckBox" name="use_pessimistic_flushes"> + <widget class="QCheckBox" name="use_fast_gpu_time"> <property name="toolTip"> - <string>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</string> + <string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string> </property> <property name="text"> - <string>Use pessimistic buffer flushes (Hack)</string> + <string>Use Fast GPU Time (Hack)</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="use_vulkan_driver_pipeline_cache"> <property name="toolTip"> - <string>Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally.</string> + <string>Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally.</string> </property> <property name="text"> <string>Use Vulkan pipeline cache</string> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 50b62293e..2c2e7e47b 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -8,6 +8,7 @@ #include <QInputDialog> #include <QMenu> #include <QMessageBox> +#include <QMouseEvent> #include <QTimer> #include "common/assert.h" #include "common/param_package.h" @@ -206,7 +207,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { } if (param.Has("axis")) { const QString axis = QString::fromStdString(param.Get("axis", "")); - return QObject::tr("%1%2Axis %3").arg(toggle, invert, axis); + return QObject::tr("%1%2%3Axis %4").arg(toggle, inverted, invert, axis); } if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); @@ -229,7 +230,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { return QObject::tr("%1%2%3Hat %4").arg(turbo, toggle, inverted, button_name); } if (param.Has("axis")) { - return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); + return QObject::tr("%1%2%3Axis %4").arg(toggle, inverted, invert, button_name); } if (param.Has("motion")) { return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); @@ -410,6 +411,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i button_map[button_id]->setText(ButtonToText(param)); emulated_controller->SetButtonParam(button_id, param); }); + context_menu.addAction(tr("Invert button"), [&] { + const bool invert_value = !param.Get("inverted", false); + param.Set("inverted", invert_value); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); context_menu.addAction(tr("Set threshold"), [&] { const int button_threshold = static_cast<int>(param.Get("threshold", 0.5f) * 100.0f); @@ -472,6 +479,9 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i param.Set("threshold", new_threshold / 1000.0f); emulated_controller->SetMotionParam(motion_id, param); }); + context_menu.addAction(tr("Calibrate sensor"), [&] { + emulated_controller->StartMotionCalibration(); + }); } context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location)); }); diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index c287220fc..a188eef92 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -180,6 +180,10 @@ void PlayerControlPreview::ControllerUpdate(Core::HID::ControllerTriggerType typ battery_values = controller->GetBatteryValues(); needs_redraw = true; break; + case Core::HID::ControllerTriggerType::Motion: + motion_values = controller->GetMotions(); + needs_redraw = true; + break; default: break; } @@ -313,6 +317,15 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center) DrawRawJoystick(p, center + QPointF(-140, 90), QPointF(0, 0)); } + { + // Draw motion cubes + using namespace Settings::NativeMotion; + p.setPen(colors.outline); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(-140, 90), + motion_values[Settings::NativeMotion::MotionLeft].euler, 20.0f); + } + using namespace Settings::NativeButton; // D-pad constants @@ -435,6 +448,15 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center DrawRawJoystick(p, QPointF(0, 0), center + QPointF(140, 90)); } + { + // Draw motion cubes + using namespace Settings::NativeMotion; + p.setPen(colors.outline); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(140, 90), + motion_values[Settings::NativeMotion::MotionRight].euler, 20.0f); + } + using namespace Settings::NativeButton; // Face buttons constants @@ -555,6 +577,17 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center) DrawRawJoystick(p, center + QPointF(-180, 90), center + QPointF(180, 90)); } + { + // Draw motion cubes + using namespace Settings::NativeMotion; + p.setPen(colors.outline); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(-180, 90), + motion_values[Settings::NativeMotion::MotionLeft].euler, 20.0f); + Draw3dCube(p, center + QPointF(180, 90), + motion_values[Settings::NativeMotion::MotionRight].euler, 20.0f); + } + using namespace Settings::NativeButton; // Face buttons constants @@ -647,6 +680,15 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen DrawRawJoystick(p, center + QPointF(-50, 0), center + QPointF(50, 0)); } + { + // Draw motion cubes + using namespace Settings::NativeMotion; + p.setPen(colors.outline); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(0, -115), + motion_values[Settings::NativeMotion::MotionLeft].euler, 15.0f); + } + using namespace Settings::NativeButton; // Face buttons constants @@ -750,6 +792,15 @@ void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center) DrawRawJoystick(p, center + QPointF(-50, 105), center + QPointF(50, 105)); } + { + // Draw motion cubes + using namespace Settings::NativeMotion; + p.setPen(colors.button); + p.setBrush(colors.transparent); + Draw3dCube(p, center + QPointF(0, -100), + motion_values[Settings::NativeMotion::MotionLeft].euler, 15.0f); + } + using namespace Settings::NativeButton; // Face buttons constants @@ -2871,6 +2922,46 @@ void PlayerControlPreview::DrawArrow(QPainter& p, const QPointF center, const Di DrawPolygon(p, arrow_symbol); } +// Draw motion functions +void PlayerControlPreview::Draw3dCube(QPainter& p, QPointF center, const Common::Vec3f& euler, + float size) { + std::array<Common::Vec3f, 8> cube{ + Common::Vec3f{-0.7f, -1, -0.5f}, + {-0.7f, 1, -0.5f}, + {0.7f, 1, -0.5f}, + {0.7f, -1, -0.5f}, + {-0.7f, -1, 0.5f}, + {-0.7f, 1, 0.5f}, + {0.7f, 1, 0.5f}, + {0.7f, -1, 0.5f}, + }; + + for (Common::Vec3f& point : cube) { + point.RotateFromOrigin(euler.x, euler.y, euler.z); + point *= size; + } + + const std::array<QPointF, 4> front_face{ + center + QPointF{cube[0].x, cube[0].y}, + center + QPointF{cube[1].x, cube[1].y}, + center + QPointF{cube[2].x, cube[2].y}, + center + QPointF{cube[3].x, cube[3].y}, + }; + const std::array<QPointF, 4> back_face{ + center + QPointF{cube[4].x, cube[4].y}, + center + QPointF{cube[5].x, cube[5].y}, + center + QPointF{cube[6].x, cube[6].y}, + center + QPointF{cube[7].x, cube[7].y}, + }; + + DrawPolygon(p, front_face); + DrawPolygon(p, back_face); + p.drawLine(center + QPointF{cube[0].x, cube[0].y}, center + QPointF{cube[4].x, cube[4].y}); + p.drawLine(center + QPointF{cube[1].x, cube[1].y}, center + QPointF{cube[5].x, cube[5].y}); + p.drawLine(center + QPointF{cube[2].x, cube[2].y}, center + QPointF{cube[6].x, cube[6].y}); + p.drawLine(center + QPointF{cube[3].x, cube[3].y}, center + QPointF{cube[7].x, cube[7].y}); +} + template <size_t N> void PlayerControlPreview::DrawPolygon(QPainter& p, const std::array<QPointF, N>& polygon) { p.drawPolygon(polygon.data(), static_cast<int>(polygon.size())); diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h index 267d134de..a16943c3c 100644 --- a/src/yuzu/configuration/configure_input_player_widget.h +++ b/src/yuzu/configuration/configure_input_player_widget.h @@ -9,6 +9,7 @@ #include "common/input.h" #include "common/settings_input.h" +#include "common/vector_math.h" #include "core/hid/emulated_controller.h" #include "core/hid/hid_types.h" @@ -193,6 +194,9 @@ private: void DrawSymbol(QPainter& p, QPointF center, Symbol symbol, float icon_size); void DrawArrow(QPainter& p, QPointF center, Direction direction, float size); + // Draw motion functions + void Draw3dCube(QPainter& p, QPointF center, const Common::Vec3f& euler, float size); + // Draw primitive types template <size_t N> void DrawPolygon(QPainter& p, const std::array<QPointF, N>& polygon); @@ -222,4 +226,5 @@ private: Core::HID::SticksValues stick_values{}; Core::HID::TriggerValues trigger_values{}; Core::HID::BatteryValues battery_values{}; + Core::HID::MotionState motion_values{}; }; diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 6af34f793..286ccc5cd 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -111,6 +111,9 @@ void ConfigureSystem::SetConfiguration() { ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time)); ui->device_name_edit->setText( QString::fromUtf8(Settings::values.device_name.GetValue().c_str())); + ui->use_unsafe_extended_memory_layout->setEnabled(enabled); + ui->use_unsafe_extended_memory_layout->setChecked( + Settings::values.use_unsafe_extended_memory_layout.GetValue()); if (Settings::IsConfiguringGlobal()) { ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue()); @@ -160,6 +163,9 @@ void ConfigureSystem::ApplyConfiguration() { ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region); ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index, ui->combo_time_zone); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_unsafe_extended_memory_layout, + ui->use_unsafe_extended_memory_layout, + use_unsafe_extended_memory_layout); if (Settings::IsConfiguringGlobal()) { // Guard if during game and set to game-specific value @@ -215,6 +221,10 @@ void ConfigureSystem::SetupPerGameUI() { Settings::values.rng_seed.GetValue().has_value(), Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed); + ConfigurationShared::SetColoredTristate(ui->use_unsafe_extended_memory_layout, + Settings::values.use_unsafe_extended_memory_layout, + use_unsafe_extended_memory_layout); + ui->custom_rtc_checkbox->setVisible(false); ui->custom_rtc_edit->setVisible(false); } diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index ec28724a1..ce1a91601 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -41,6 +41,7 @@ private: bool enabled = false; ConfigurationShared::CheckState use_rng_seed; + ConfigurationShared::CheckState use_unsafe_extended_memory_layout; Core::System& system; }; diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index 9e7bc3b93..e0caecd5e 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -478,6 +478,13 @@ </property> </widget> </item> + <item row="7" column="0"> + <widget class="QCheckBox" name="use_unsafe_extended_memory_layout"> + <property name="text"> + <string>Unsafe extended memory layout (8GB DRAM)</string> + </property> + </widget> + </item> </layout> </item> </layout> diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index b79409a68..d932e33a7 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -27,6 +27,7 @@ #include "configuration/configure_input.h" #include "configuration/configure_per_game.h" #include "configuration/configure_tas.h" +#include "core/file_sys/romfs_factory.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" #include "core/frontend/applets/cabinet.h" @@ -570,8 +571,8 @@ void GMainWindow::RegisterMetaTypes() { // Cabinet Applet qRegisterMetaType<Core::Frontend::CabinetParameters>("Core::Frontend::CabinetParameters"); - qRegisterMetaType<std::shared_ptr<Service::NFP::NfpDevice>>( - "std::shared_ptr<Service::NFP::NfpDevice>"); + qRegisterMetaType<std::shared_ptr<Service::NFC::NfcDevice>>( + "std::shared_ptr<Service::NFC::NfcDevice>"); // Controller Applet qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters"); @@ -599,7 +600,7 @@ void GMainWindow::RegisterMetaTypes() { } void GMainWindow::AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device) { + std::shared_ptr<Service::NFC::NfcDevice> nfp_device) { cabinet_applet = new QtAmiiboSettingsDialog(this, parameters, input_subsystem.get(), nfp_device); SCOPE_EXIT({ @@ -4171,6 +4172,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { } Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); + bool all_keys_present{true}; + if (keys.BaseDeriveNecessary()) { Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory("", FileSys::Mode::Read)}; @@ -4195,6 +4198,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { errors += tr(" - Missing PRODINFO"); } if (!errors.isEmpty()) { + all_keys_present = false; QMessageBox::warning( this, tr("Derivation Components Missing"), tr("Encryption keys are missing. " @@ -4222,11 +4226,40 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { system->GetFileSystemController().CreateFactories(*vfs); + if (all_keys_present && !this->CheckSystemArchiveDecryption()) { + LOG_WARNING(Frontend, "Mii model decryption failed"); + QMessageBox::warning( + this, tr("System Archive Decryption Failed"), + tr("Encryption keys failed to decrypt firmware. " + "<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu " + "quickstart guide</a> to get all your keys, firmware and " + "games.")); + } + if (behavior == ReinitializeKeyBehavior::Warning) { game_list->PopulateAsync(UISettings::values.game_dirs); } } +bool GMainWindow::CheckSystemArchiveDecryption() { + constexpr u64 MiiModelId = 0x0100000000000802; + + auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); + if (!bis_system) { + // Not having system BIS files is not an error. + return true; + } + + auto mii_nca = bis_system->GetEntry(MiiModelId, FileSys::ContentRecordType::Data); + if (!mii_nca) { + // Not having the Mii model is not an error. + return true; + } + + // Return whether we are able to decrypt the RomFS of the Mii model. + return mii_nca->GetRomFS().get() != nullptr; +} + std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id) { const auto dlc_entries = diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 8b5c1d747..7b23f2a59 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -93,9 +93,9 @@ enum class SwkbdReplyType : u32; enum class WebExitReason : u32; } // namespace Service::AM::Applets -namespace Service::NFP { -class NfpDevice; -} // namespace Service::NFP +namespace Service::NFC { +class NfcDevice; +} // namespace Service::NFC namespace Ui { class MainWindow; @@ -188,7 +188,7 @@ public slots: void OnExit(); void OnSaveConfig(); void AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device); + std::shared_ptr<Service::NFC::NfcDevice> nfp_device); void AmiiboSettingsRequestExit(); void ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters); @@ -392,6 +392,7 @@ private: void LoadTranslation(); void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); bool CheckDarkMode(); + bool CheckSystemArchiveDecryption(); QString GetTasStateDescription() const; bool CreateShortcut(const std::string& shortcut_path, const std::string& title, diff --git a/src/yuzu/qt_common.cpp b/src/yuzu/qt_common.cpp new file mode 100644 index 000000000..5d0fd7674 --- /dev/null +++ b/src/yuzu/qt_common.cpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <QGuiApplication> +#include <QStringLiteral> +#include <QWindow> +#include "common/logging/log.h" +#include "core/frontend/emu_window.h" +#include "yuzu/qt_common.h" + +#if !defined(WIN32) && !defined(__APPLE__) +#include <qpa/qplatformnativeinterface.h> +#endif + +namespace QtCommon { +Core::Frontend::WindowSystemType GetWindowSystemType() { + // Determine WSI type based on Qt platform. + QString platform_name = QGuiApplication::platformName(); + if (platform_name == QStringLiteral("windows")) + return Core::Frontend::WindowSystemType::Windows; + else if (platform_name == QStringLiteral("xcb")) + return Core::Frontend::WindowSystemType::X11; + else if (platform_name == QStringLiteral("wayland")) + return Core::Frontend::WindowSystemType::Wayland; + else if (platform_name == QStringLiteral("wayland-egl")) + return Core::Frontend::WindowSystemType::Wayland; + else if (platform_name == QStringLiteral("cocoa")) + return Core::Frontend::WindowSystemType::Cocoa; + else if (platform_name == QStringLiteral("android")) + return Core::Frontend::WindowSystemType::Android; + + LOG_CRITICAL(Frontend, "Unknown Qt platform {}!", platform_name.toStdString()); + return Core::Frontend::WindowSystemType::Windows; +} // namespace Core::Frontend::WindowSystemType + +Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) { + Core::Frontend::EmuWindow::WindowSystemInfo wsi; + wsi.type = GetWindowSystemType(); + + // Our Win32 Qt external doesn't have the private API. +#if defined(WIN32) || defined(__APPLE__) + wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; +#else + QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); + wsi.display_connection = pni->nativeResourceForWindow("display", window); + if (wsi.type == Core::Frontend::WindowSystemType::Wayland) + wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr; + else + wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; +#endif + wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f; + + return wsi; +} +} // namespace QtCommon diff --git a/src/yuzu/qt_common.h b/src/yuzu/qt_common.h new file mode 100644 index 000000000..9c63f08f3 --- /dev/null +++ b/src/yuzu/qt_common.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <QWindow> +#include "core/frontend/emu_window.h" + +namespace QtCommon { + +Core::Frontend::WindowSystemType GetWindowSystemType(); + +Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window); + +} // namespace QtCommon diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 464da3231..abe7092fc 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -4,18 +4,8 @@ #include <memory> #include <optional> #include <sstream> - -// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wimplicit-fallthrough" -#endif -#include <SDL.h> -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - #include <INIReader.h> +#include <SDL.h> #include "common/fs/file.h" #include "common/fs/fs.h" #include "common/fs/path_util.h" @@ -274,7 +264,7 @@ void Config::ReadValues() { // Core ReadSetting("Core", Settings::values.use_multi_core); - ReadSetting("Core", Settings::values.use_extended_memory_layout); + ReadSetting("Core", Settings::values.use_unsafe_extended_memory_layout); // Cpu ReadSetting("Cpu", Settings::values.cpu_accuracy); @@ -300,6 +290,7 @@ void Config::ReadValues() { // Renderer ReadSetting("Renderer", Settings::values.renderer_backend); + ReadSetting("Renderer", Settings::values.async_presentation); ReadSetting("Renderer", Settings::values.renderer_force_max_clock); ReadSetting("Renderer", Settings::values.renderer_debug); ReadSetting("Renderer", Settings::values.renderer_shader_feedback); @@ -319,14 +310,14 @@ void Config::ReadValues() { ReadSetting("Renderer", Settings::values.use_disk_shader_cache); ReadSetting("Renderer", Settings::values.gpu_accuracy); ReadSetting("Renderer", Settings::values.use_asynchronous_gpu_emulation); - ReadSetting("Renderer", Settings::values.use_vsync); + ReadSetting("Renderer", Settings::values.vsync_mode); ReadSetting("Renderer", Settings::values.shader_backend); + ReadSetting("Renderer", Settings::values.use_reactive_flushing); ReadSetting("Renderer", Settings::values.use_asynchronous_shaders); ReadSetting("Renderer", Settings::values.nvdec_emulation); ReadSetting("Renderer", Settings::values.accelerate_astc); ReadSetting("Renderer", Settings::values.async_astc); ReadSetting("Renderer", Settings::values.use_fast_gpu_time); - ReadSetting("Renderer", Settings::values.use_pessimistic_flushes); ReadSetting("Renderer", Settings::values.use_vulkan_driver_pipeline_cache); ReadSetting("Renderer", Settings::values.bg_red); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 209cfc28a..5e7c3ac04 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -163,9 +163,9 @@ keyboard_enabled = # 0: Disabled, 1 (default): Enabled use_multi_core = -# Enable extended guest system memory layout (8GB DRAM) +# Enable unsafe extended guest system memory layout (8GB DRAM) # 0 (default): Disabled, 1: Enabled -use_extended_memory_layout = +use_unsafe_extended_memory_layout = [Cpu] # Adjusts various optimizations. @@ -264,6 +264,10 @@ cpuopt_unsafe_ignore_global_monitor = # 0: OpenGL, 1 (default): Vulkan backend = +# Whether to enable asynchronous presentation (Vulkan only) +# 0 (default): Off, 1: On +async_presentation = + # Enable graphics API debugging mode. # 0 (default): Disabled, 1: Enabled debug = @@ -321,8 +325,14 @@ aspect_ratio = # 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x max_anisotropy = -# Whether to enable V-Sync (caps the framerate at 60FPS) or not. -# 0 (default): Off, 1: On +# Whether to enable VSync or not. +# OpenGL: Values other than 0 enable VSync +# Vulkan: FIFO is selected if the requested mode is not supported by the driver. +# FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. +# FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down. +# Mailbox can have lower latency than FIFO and does not tear but may drop frames. +# Immediate (no synchronization) just presents whatever is available and can exhibit tearing. +# 0: Immediate (Off), 1: Mailbox, 2 (Default): FIFO (On), 3: FIFO Relaxed use_vsync = # Selects the OpenGL shader backend. NV_gpu_program5 is required for GLASM. If NV_gpu_program5 is @@ -330,6 +340,10 @@ use_vsync = # 0: GLSL, 1 (default): GLASM, 2: SPIR-V shader_backend = +# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory. +# 0: Off, 1 (default): On +use_reactive_flushing = + # Whether to allow asynchronous shader building. # 0 (default): Off, 1: On use_asynchronous_shaders = @@ -370,10 +384,6 @@ use_asynchronous_gpu_emulation = # 0: Off, 1 (default): On use_fast_gpu_time = -# Force unmodified buffers to be flushed, which can cost performance. -# 0: Off (default), 1: On -use_pessimistic_flushes = - # Whether to use garbage collection or not for GPU caches. # 0 (default): Off, 1: On use_caches_gc = diff --git a/vcpkg.json b/vcpkg.json index 0352dab77..19f99e89e 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -49,7 +49,7 @@ "overrides": [ { "name": "catch2", - "version": "3.0.1" + "version": "3.3.1" }, { "name": "fmt", |