mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-25 18:55:57 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88d43ff216 | |||
| 138cfba9fc | |||
| 14451ec32e | |||
| 3ef1736ac0 | |||
| 0404aea0db | |||
| dbeb73ee01 | |||
| cb73a4dcc7 | |||
| 278f411dad | |||
| 3d37a816c6 | |||
| a25e32676f | |||
| 38df54edfe | |||
| 3266c20e3f | |||
| 9cd24e85c2 | |||
| 74b5e10dc5 |
Vendored
+628
-583
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+548
-503
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+550
-508
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
Vendored
+573
-527
File diff suppressed because it is too large
Load Diff
Vendored
+547
-502
File diff suppressed because it is too large
Load Diff
+33
-21
@@ -1,32 +1,44 @@
|
|||||||
# User Handbook - Command Line
|
# User Handbook - Command Line
|
||||||
|
|
||||||
There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept command line arguments.
|
There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept the same command line arguments.
|
||||||
|
|
||||||
## eden
|
|
||||||
|
|
||||||
- `./eden <path>`: Running with a single argument and nothing else, will make the emulator look for the given file and load it, this behavior is similar to `eden-cli`; allows dragging and dropping games into the application.
|
- `./eden <path>`: Running with a single argument and nothing else, will make the emulator look for the given file and load it, this behavior is similar to `eden-cli`; allows dragging and dropping games into the application.
|
||||||
- `-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
|
- `--debug/-d`: Enter debug mode, allow gdb stub at port `1234`
|
||||||
- `-f`: Use fullscreen.
|
- `--config/-c`: Specify alternate configuration file.
|
||||||
- `-u <number>`: Select the index of the user to load as.
|
- `--fullscreen/-f`: Use fullscreen.
|
||||||
- `-input-profile <name>`: Specifies input profile name to use (for player #0 only).
|
- `--help/-h`: Display help.
|
||||||
|
- `--game/-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
|
||||||
|
- `--multiplayer/-m`: Specify multiplayer options.
|
||||||
|
- `--program/-p`: Specify the program arguments to pass (optional).
|
||||||
|
- `--user/-u <number>`: Specify the user index.
|
||||||
|
- `--version/-v`: Display version and quit.
|
||||||
|
- `--input-profile/-i <name>`: Specifies input profile name to use (for player #0 only).
|
||||||
|
- `--null-render/-n`: Forces the usage of the "Null" render backend irrespective of settings.
|
||||||
|
- `--filter/-x`: Sets the debug log filter irrespective of settings.
|
||||||
|
- `--singlecore/-s`: Forces single-core regardless of settings.
|
||||||
|
|
||||||
|
Only the Qt frontend supports the following arguments:
|
||||||
|
|
||||||
- `-qlaunch`: Launch QLaunch.
|
- `-qlaunch`: Launch QLaunch.
|
||||||
- `-hlaunch`: Launch homebrew launcher `nx-hbloader`.
|
- `-hlaunch`: Launch homebrew launcher `nx-hbloader`.
|
||||||
- Requires a copy of Atmosphere to be extracted onto `sdmc`.
|
- Requires a copy of Atmosphere to be extracted onto `sdmc`.
|
||||||
- This is a shorthand for `<eden folder>/sdmc/atmosphere/hbl.nsp`.
|
- This is a shorthand for `<eden folder>/sdmc/atmosphere/hbl.nsp`.
|
||||||
- `-setup`: Launch setup applet.
|
- `-setup`: Launch setup applet.
|
||||||
|
|
||||||
## eden-cli
|
`eden` (provided with `--room`), and `eden-room` supports the following options as well:
|
||||||
|
|
||||||
- `--debug/-d`: Enter debug mode, allow gdb stub at port `1234`
|
- `-n/--room-name`: The name of the room.
|
||||||
- `--config/-c`: Specify alternate configuration file.
|
- `-d/--room-description`: The room description.
|
||||||
- `--fullscreen/-f`: Set fullscreen.
|
- `-s/--bind-address`: The bind address for the room.
|
||||||
- `--help/-h`: Display help.
|
- `-p/--port`: The port used for the room.
|
||||||
- `--game/-g`: Specify the game to run.
|
- `-m/--max-members`: The maximum number of players for this room.
|
||||||
- `--multiplayer/-m`: Specify multiplayer options.
|
- `-w/--password`: The password for the room.
|
||||||
- `--program/-p`: Specify the program arguments to pass (optional).
|
- `-g/--preferred-game`: The preferred game for this room.
|
||||||
- `--user/-u`: Specify the user index.
|
- `-i/--preferred-game-id`: The preferred game-id for this room.
|
||||||
- `--version/-v`: Display version and quit.
|
- `-u/--username`: The username used for announce.
|
||||||
- `--input-profile/-i`: Specifies input profile name to use (for player #0 only).
|
- `-t/--token`: The token used for announce.
|
||||||
- `--null-render/-n`: Forces the usage of the "Null" render backend irrespective of settings.
|
- `-a/--web-api-url`: yuzu Web API url.
|
||||||
- `--filter/-x`: Sets the debug log filter irrespective of settings.
|
- `-b/--ban-list-file`: The file for storing the room ban list.
|
||||||
- `--singlecore/-s`: Forces single-core regardless of settings.
|
- `-l/--log-file`: The file for storing the room log.
|
||||||
|
- `-h/--help`: Display this help and exit.
|
||||||
|
- `-v/--version`: Output version information and exit.
|
||||||
|
|||||||
-1
@@ -30,7 +30,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
||||||
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
|
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
|
||||||
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
|
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
|
||||||
ENABLE_GPU_BUFFER_READBACK("enable_gpu_buffer_readback"),
|
|
||||||
SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
|
SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
|
||||||
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
|
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
|
||||||
RENDERER_DEBUG("debug"),
|
RENDERER_DEBUG("debug"),
|
||||||
|
|||||||
-7
@@ -908,13 +908,6 @@ abstract class SettingsItem(
|
|||||||
descriptionId = R.string.enable_buffer_history_description
|
descriptionId = R.string.enable_buffer_history_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
|
||||||
SwitchSetting(
|
|
||||||
BooleanSetting.ENABLE_GPU_BUFFER_READBACK,
|
|
||||||
titleId = R.string.enable_gpu_buffer_readback,
|
|
||||||
descriptionId = R.string.enable_gpu_buffer_readback_description
|
|
||||||
)
|
|
||||||
)
|
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS,
|
BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS,
|
||||||
|
|||||||
-1
@@ -549,7 +549,6 @@ class SettingsFragmentPresenter(
|
|||||||
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
||||||
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
|
add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
|
||||||
add(BooleanSetting.ENABLE_BUFFER_HISTORY.key)
|
add(BooleanSetting.ENABLE_BUFFER_HISTORY.key)
|
||||||
add(BooleanSetting.ENABLE_GPU_BUFFER_READBACK.key)
|
|
||||||
add(BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS.key)
|
add(BooleanSetting.USE_OPTIMIZED_VERTEX_BUFFERS.key)
|
||||||
|
|
||||||
add(HeaderSetting(R.string.hacks))
|
add(HeaderSetting(R.string.hacks))
|
||||||
|
|||||||
@@ -788,7 +788,7 @@ int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject
|
|||||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj,
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj,
|
||||||
jstring jprogramId,
|
jstring jprogramId,
|
||||||
jstring jupdatePath) {
|
jstring jupdatePath) {
|
||||||
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
|
const u64 program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
|
||||||
std::string updatePath = Common::Android::GetJString(env, jupdatePath);
|
std::string updatePath = Common::Android::GetJString(env, jupdatePath);
|
||||||
std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>(
|
std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>(
|
||||||
EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(
|
EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(
|
||||||
@@ -796,7 +796,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* en
|
|||||||
for (const auto& item : nsp->GetNCAs()) {
|
for (const auto& item : nsp->GetNCAs()) {
|
||||||
for (const auto& nca_details : item.second) {
|
for (const auto& nca_details : item.second) {
|
||||||
if (nca_details.second->GetName().ends_with(".cnmt.nca")) {
|
if (nca_details.second->GetName().ends_with(".cnmt.nca")) {
|
||||||
auto update_id = nca_details.second->GetTitleId() & ~0xFFFULL;
|
const auto update_id = FileSys::GetBaseTitleID(nca_details.second->GetTitleId());
|
||||||
if (update_id == program_id) {
|
if (update_id == program_id) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -580,8 +580,6 @@
|
|||||||
<string name="renderer_reactive_flushing_description">يحسن دقة العرض في بعض الألعاب على حساب الأداء.</string>
|
<string name="renderer_reactive_flushing_description">يحسن دقة العرض في بعض الألعاب على حساب الأداء.</string>
|
||||||
<string name="enable_buffer_history">تمكين سجل التخزين المؤقت</string>
|
<string name="enable_buffer_history">تمكين سجل التخزين المؤقت</string>
|
||||||
<string name="enable_buffer_history_description">يُتيح هذا الخيار الوصول إلى حالات التخزين المؤقت السابقة. وقد يُحسّن جودة العرض وثبات الأداء في بعض الألعاب.</string>
|
<string name="enable_buffer_history_description">يُتيح هذا الخيار الوصول إلى حالات التخزين المؤقت السابقة. وقد يُحسّن جودة العرض وثبات الأداء في بعض الألعاب.</string>
|
||||||
<string name="enable_gpu_buffer_readback">تفعيل قراءة مخزن وحدة معالجة الرسومات</string>
|
|
||||||
<string name="enable_gpu_buffer_readback_description">يحافظ هذا النظام على بيانات المخزن المؤقت المُعدّلة بواسطة وحدة معالجة الرسومات عن طريق قراءتها مرة أخرى قبل التحميل. تتطلب بعض الألعاب ذلك لعرض بعض التأثيرات بشكل صحيح. قد يُسبب ذلك مشاكل إذا لم يتمكن الجهاز من التعامل مع عبء العمل الإضافي.</string>
|
|
||||||
<string name="use_optimized_vertex_buffers">مخازن الرؤوس المُحسّنة</string>
|
<string name="use_optimized_vertex_buffers">مخازن الرؤوس المُحسّنة</string>
|
||||||
<string name="use_optimized_vertex_buffers_description">يُتيح ربطًا مُحسَّنًا لمخازن الرؤوس لتحسين الأداء. يتطلب برامج تشغيل Mesa 26.0+ Turnip/ برامج تشغيل QCOM. قد يتعطل على برامج تشغيل Turnip القديمة (25.3 وما دون).</string>
|
<string name="use_optimized_vertex_buffers_description">يُتيح ربطًا مُحسَّنًا لمخازن الرؤوس لتحسين الأداء. يتطلب برامج تشغيل Mesa 26.0+ Turnip/ برامج تشغيل QCOM. قد يتعطل على برامج تشغيل Turnip القديمة (25.3 وما دون).</string>
|
||||||
|
|
||||||
@@ -1099,7 +1097,6 @@
|
|||||||
<string name="gpu_fence_behavior_immediate">فوري</string>
|
<string name="gpu_fence_behavior_immediate">فوري</string>
|
||||||
<string name="gpu_fence_behavior_balanced">متوازن</string>
|
<string name="gpu_fence_behavior_balanced">متوازن</string>
|
||||||
<string name="gpu_fence_behavior_accurate">دقيق</string>
|
<string name="gpu_fence_behavior_accurate">دقيق</string>
|
||||||
<string name="gpu_fence_behavior_strict">صارم</string>
|
|
||||||
|
|
||||||
<string name="vram_usage_conservative">محافظ</string>
|
<string name="vram_usage_conservative">محافظ</string>
|
||||||
<string name="vram_usage_aggressive">عدواني</string>
|
<string name="vram_usage_aggressive">عدواني</string>
|
||||||
|
|||||||
@@ -967,7 +967,6 @@ Wirklich fortfahren?</string>
|
|||||||
<string name="gpu_fence_behavior_immediate">Direkt</string>
|
<string name="gpu_fence_behavior_immediate">Direkt</string>
|
||||||
<string name="gpu_fence_behavior_balanced">Ausgewogen</string>
|
<string name="gpu_fence_behavior_balanced">Ausgewogen</string>
|
||||||
<string name="gpu_fence_behavior_accurate">Genau</string>
|
<string name="gpu_fence_behavior_accurate">Genau</string>
|
||||||
<string name="gpu_fence_behavior_strict">Strikt</string>
|
|
||||||
|
|
||||||
<string name="vram_usage_conservative">Konservativ</string>
|
<string name="vram_usage_conservative">Konservativ</string>
|
||||||
<string name="vram_usage_aggressive">Aggressiv</string>
|
<string name="vram_usage_aggressive">Aggressiv</string>
|
||||||
|
|||||||
@@ -289,6 +289,7 @@
|
|||||||
<string name="gpu_driver_fetcher">Obtenedor de controladores de la GPU</string>
|
<string name="gpu_driver_fetcher">Obtenedor de controladores de la GPU</string>
|
||||||
<string name="gpu_driver_manager">Gestor de controladores de la GPU</string>
|
<string name="gpu_driver_manager">Gestor de controladores de la GPU</string>
|
||||||
<string name="install_gpu_driver_description">Instale los controladores alternativos para obtener un posible mejor rendimiento o precisión</string>
|
<string name="install_gpu_driver_description">Instale los controladores alternativos para obtener un posible mejor rendimiento o precisión</string>
|
||||||
|
<string name="post_processing_add">Añadir efecto</string>
|
||||||
<string name="post_processing_open_list">Abrir lista</string>
|
<string name="post_processing_open_list">Abrir lista</string>
|
||||||
<string name="post_processing_close_list">Cerrar lista</string>
|
<string name="post_processing_close_list">Cerrar lista</string>
|
||||||
<string name="post_processing_reset">Restablecer a predeterminado</string>
|
<string name="post_processing_reset">Restablecer a predeterminado</string>
|
||||||
@@ -523,8 +524,6 @@
|
|||||||
<string name="renderer_reactive_flushing_description">Mejora la precisión de renderizado en algunos juegos, pero reduce el rendimiento.</string>
|
<string name="renderer_reactive_flushing_description">Mejora la precisión de renderizado en algunos juegos, pero reduce el rendimiento.</string>
|
||||||
<string name="enable_buffer_history">Activar el historial del búfer</string>
|
<string name="enable_buffer_history">Activar el historial del búfer</string>
|
||||||
<string name="enable_buffer_history_description">Permite el acceso al estado del búfer anterior. Esta opción puede mejorar la calidad de renderizado y la consistencia en el rendimiento de algunos juegos.</string>
|
<string name="enable_buffer_history_description">Permite el acceso al estado del búfer anterior. Esta opción puede mejorar la calidad de renderizado y la consistencia en el rendimiento de algunos juegos.</string>
|
||||||
<string name="enable_gpu_buffer_readback">Activar la lectura del buffer de la GPU</string>
|
|
||||||
<string name="enable_gpu_buffer_readback_description">Conserva los datos del búfer modificados por la GPU leyéndolos antes de subirlos.\nAlgunos juegos requieren esto para renderizar correctamente ciertos efectos.\nPuede causar problemas si el hardware no puede soportar la carga de trabajo adicional.</string>
|
|
||||||
<string name="use_optimized_vertex_buffers">Búferes de vértices optimizados</string>
|
<string name="use_optimized_vertex_buffers">Búferes de vértices optimizados</string>
|
||||||
<string name="use_optimized_vertex_buffers_description">Permite la optimización del enlace del búfer de vértices para un mejor rendimiento. Requiere controladores Mesa 26.0+ Turnip/ controladores QCOM. Fallará con controladores Turnip más antiguos (versión 25.3 o inferior).</string>
|
<string name="use_optimized_vertex_buffers_description">Permite la optimización del enlace del búfer de vértices para un mejor rendimiento. Requiere controladores Mesa 26.0+ Turnip/ controladores QCOM. Fallará con controladores Turnip más antiguos (versión 25.3 o inferior).</string>
|
||||||
|
|
||||||
@@ -1037,7 +1036,6 @@
|
|||||||
<string name="gpu_fence_behavior_immediate">Inmediato</string>
|
<string name="gpu_fence_behavior_immediate">Inmediato</string>
|
||||||
<string name="gpu_fence_behavior_balanced">Equilibrado</string>
|
<string name="gpu_fence_behavior_balanced">Equilibrado</string>
|
||||||
<string name="gpu_fence_behavior_accurate">Preciso</string>
|
<string name="gpu_fence_behavior_accurate">Preciso</string>
|
||||||
<string name="gpu_fence_behavior_strict">Estricto</string>
|
|
||||||
|
|
||||||
<string name="vram_usage_conservative">Conservador</string>
|
<string name="vram_usage_conservative">Conservador</string>
|
||||||
<string name="vram_usage_aggressive">Agresivo</string>
|
<string name="vram_usage_aggressive">Agresivo</string>
|
||||||
|
|||||||
@@ -318,6 +318,13 @@
|
|||||||
<string name="frame_gen_target_rate">Целевая частота кадров</string>
|
<string name="frame_gen_target_rate">Целевая частота кадров</string>
|
||||||
<string name="frame_gen_target_rate_description">Выберите значение под ваш дисплей. Множитель подстроится сам, чтобы его держать, и откатит изменения, если игра начнёт тормозить.</string>
|
<string name="frame_gen_target_rate_description">Выберите значение под ваш дисплей. Множитель подстроится сам, чтобы его держать, и откатит изменения, если игра начнёт тормозить.</string>
|
||||||
<string name="frame_gen_target_rate_off">Использовать фиксированный множитель</string>
|
<string name="frame_gen_target_rate_off">Использовать фиксированный множитель</string>
|
||||||
|
<string name="frame_gen_on">Вкл.</string>
|
||||||
|
<string name="frame_gen_off">Выкл.</string>
|
||||||
|
<string name="frame_gen_fixed">Фикс.</string>
|
||||||
|
<string name="frame_gen_flow_auto">Авто</string>
|
||||||
|
<string name="frame_gen_quick_description">Включите или выключите генерацию кадров и выберите множитель кадров.</string>
|
||||||
|
<string name="frame_gen_target_rate_quick_description">Генерация кадров с заданной частотой кадров. Множитель регулируется автоматически.</string>
|
||||||
|
<string name="frame_gen_flow_scale_quick_description">Разрешение, используемое для оценки движения между кадрами. Меньшие значения экономят время ГПУ.</string>
|
||||||
<string name="frame_gen_queue_target">Целевой размер очереди кадров</string>
|
<string name="frame_gen_queue_target">Целевой размер очереди кадров</string>
|
||||||
<string name="frame_gen_queue_target_description">Сколько готовых кадров может ожидать перед выводом на экран. Более длинные очереди сглаживают скачки ГПУ ценой задержки ввода.</string>
|
<string name="frame_gen_queue_target_description">Сколько готовых кадров может ожидать перед выводом на экран. Более длинные очереди сглаживают скачки ГПУ ценой задержки ввода.</string>
|
||||||
<string name="frame_gen_queue_target_0">Минимальная задержка (Без буферизации)</string>
|
<string name="frame_gen_queue_target_0">Минимальная задержка (Без буферизации)</string>
|
||||||
@@ -328,12 +335,14 @@
|
|||||||
<string name="frame_gen_flow_scale">Разрешение оценки движения</string>
|
<string name="frame_gen_flow_scale">Разрешение оценки движения</string>
|
||||||
<string name="frame_gen_flow_scale_description">Разрешение прохода оптического потока в долях от выходного разрешения. Его понижение — самый дешёвый способ вернуть производительность.</string>
|
<string name="frame_gen_flow_scale_description">Разрешение прохода оптического потока в долях от выходного разрешения. Его понижение — самый дешёвый способ вернуть производительность.</string>
|
||||||
<string name="frame_gen_unsupported">Генерация кадров недоступна</string>
|
<string name="frame_gen_unsupported">Генерация кадров недоступна</string>
|
||||||
|
<string name="frame_gen_unsupported_description">В этом ГПУ драйвере отсутствует поддержка модели памяти Vulkan или половинной точности (float16), которая требуется шейдерам Lossless Scaling.</string>
|
||||||
<string name="lossless_scaling_setup_description">Опционально. Укажите свой Lossless.dll для включения генерации кадров позже.</string>
|
<string name="lossless_scaling_setup_description">Опционально. Укажите свой Lossless.dll для включения генерации кадров позже.</string>
|
||||||
<string name="lossless_scaling_install">Установить Lossless.dll</string>
|
<string name="lossless_scaling_install">Установить Lossless.dll</string>
|
||||||
<string name="lossless_scaling_install_description">Для генерации кадров требуется ваша собственная легальная копия Lossless.dll из Lossless Scaling</string>
|
<string name="lossless_scaling_install_description">Для генерации кадров требуется ваша собственная легальная копия Lossless.dll из Lossless Scaling</string>
|
||||||
<string name="lossless_scaling_replace_description">Выбрать другой файл Lossless.dll</string>
|
<string name="lossless_scaling_replace_description">Выбрать другой файл Lossless.dll</string>
|
||||||
<string name="frame_generation_support">Генерация кадров</string>
|
<string name="frame_generation_support">Генерация кадров</string>
|
||||||
<string name="frame_generation_supported">Поддерживается</string>
|
<string name="frame_generation_supported">Поддерживается</string>
|
||||||
|
<string name="frame_generation_unsupported">Не поддерживается (нет модели памяти Vulkan или float16)</string>
|
||||||
<string name="lossless_scaling_description">Предоставьте свою копию Lossless.dll для включения генерации кадров</string>
|
<string name="lossless_scaling_description">Предоставьте свою копию Lossless.dll для включения генерации кадров</string>
|
||||||
<string name="lossless_scaling_installed">Установлена</string>
|
<string name="lossless_scaling_installed">Установлена</string>
|
||||||
<string name="lossless_scaling_not_installed">Не установлена</string>
|
<string name="lossless_scaling_not_installed">Не установлена</string>
|
||||||
@@ -560,8 +569,6 @@
|
|||||||
<string name="renderer_reactive_flushing_description">Повышение точности рендеринга в некоторых играх за счет снижения производительности.</string>
|
<string name="renderer_reactive_flushing_description">Повышение точности рендеринга в некоторых играх за счет снижения производительности.</string>
|
||||||
<string name="enable_buffer_history">Включить историю буфера</string>
|
<string name="enable_buffer_history">Включить историю буфера</string>
|
||||||
<string name="enable_buffer_history_description">Позволяет обращаться к предыдущим состояниям буфера. Эта опция может повысить качество рендеринга и стабильность производительности в некоторых играх.</string>
|
<string name="enable_buffer_history_description">Позволяет обращаться к предыдущим состояниям буфера. Эта опция может повысить качество рендеринга и стабильность производительности в некоторых играх.</string>
|
||||||
<string name="enable_gpu_buffer_readback">Включить обратное чтение буфера ГПУ</string>
|
|
||||||
<string name="enable_gpu_buffer_readback_description">Сохраняет измененные ГПУ данные буфера путем чтения их обратно перед выгрузками. Некоторые игры требуют этого, чтобы рендерить определенные эффекты правильно. Может вызывать проблемы если оборудование не может обработать дополнительную рабочую нагрузку.</string>
|
|
||||||
<string name="use_optimized_vertex_buffers">Оптимизированные вершинные буферы</string>
|
<string name="use_optimized_vertex_buffers">Оптимизированные вершинные буферы</string>
|
||||||
<string name="use_optimized_vertex_buffers_description">Включает оптимизированную привязку вершинного буфера для повышения производительности. Требует Mesa Turnip 26.0+ / QCOM. Приводит к вылету на старых версиях драйверов Turnip (25.3 и ниже).</string>
|
<string name="use_optimized_vertex_buffers_description">Включает оптимизированную привязку вершинного буфера для повышения производительности. Требует Mesa Turnip 26.0+ / QCOM. Приводит к вылету на старых версиях драйверов Turnip (25.3 и ниже).</string>
|
||||||
|
|
||||||
@@ -1079,7 +1086,6 @@
|
|||||||
<string name="gpu_fence_behavior_immediate">Мгновенный</string>
|
<string name="gpu_fence_behavior_immediate">Мгновенный</string>
|
||||||
<string name="gpu_fence_behavior_balanced">Сбалансированный</string>
|
<string name="gpu_fence_behavior_balanced">Сбалансированный</string>
|
||||||
<string name="gpu_fence_behavior_accurate">Точный</string>
|
<string name="gpu_fence_behavior_accurate">Точный</string>
|
||||||
<string name="gpu_fence_behavior_strict">Строгий</string>
|
|
||||||
|
|
||||||
<string name="vram_usage_conservative">Консервативный</string>
|
<string name="vram_usage_conservative">Консервативный</string>
|
||||||
<string name="vram_usage_aggressive">Агрессивный</string>
|
<string name="vram_usage_aggressive">Агрессивный</string>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||||
|
|
||||||
|
<string name="app_disclaimer">本软件可以运行用于 Nintendo Switch 游戏机的游戏。不包含任何游戏或密钥。<br /><br />在您开始之前, 请定位您设备存储上的 <![CDATA[<b> prod.keys </b>]]> 文件。<br /><br /><![CDATA[<a href=\"https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/QuickStart.md\">了解更多</a>]]></string>
|
||||||
<string name="notice_notification_channel_name">错误与注意事项</string>
|
<string name="notice_notification_channel_name">错误与注意事项</string>
|
||||||
<string name="notice_notification_channel_description">当发生错误时显示通知。</string>
|
<string name="notice_notification_channel_description">当发生错误时显示通知。</string>
|
||||||
<string name="notification_permission_not_granted">未授予通知权限!</string>
|
<string name="notification_permission_not_granted">未授予通知权限!</string>
|
||||||
@@ -290,6 +291,25 @@
|
|||||||
<string name="gpu_driver_fetcher">GPU驱动获取器</string>
|
<string name="gpu_driver_fetcher">GPU驱动获取器</string>
|
||||||
<string name="gpu_driver_manager">GPU 驱动管理器</string>
|
<string name="gpu_driver_manager">GPU 驱动管理器</string>
|
||||||
<string name="install_gpu_driver_description">安装替代的驱动程序以获得更好的性能和精度</string>
|
<string name="install_gpu_driver_description">安装替代的驱动程序以获得更好的性能和精度</string>
|
||||||
|
<string name="post_processing">后处理效果</string>
|
||||||
|
<string name="post_processing_description">在渲染完成后应用 ReShade FX 效果</string>
|
||||||
|
<string name="post_processing_per_game_description">配置此游戏的效果链</string>
|
||||||
|
<string name="post_processing_add">添加效果</string>
|
||||||
|
<string name="post_processing_remove">移除</string>
|
||||||
|
<string name="post_processing_open_list">打开列表</string>
|
||||||
|
<string name="post_processing_close_list">关闭列表</string>
|
||||||
|
<string name="post_processing_remove_all">移除效果</string>
|
||||||
|
<string name="post_processing_preset_locked">预设已激活。在开始游戏前可以在后处理效果中编辑它。</string>
|
||||||
|
<string name="post_processing_preset_modified">数值已从原始预设被更改。</string>
|
||||||
|
<string name="post_processing_presets">预设</string>
|
||||||
|
<string name="post_processing_preset_new">新建预设</string>
|
||||||
|
<string name="post_processing_preset_new_description">把您加载的效果及当前数值保存为一个预设,以便今后选用。</string>
|
||||||
|
<string name="post_processing_preset_name_invalid">请给预设起一个不含等号的名称。</string>
|
||||||
|
<string name="post_processing_preset_none">无预设</string>
|
||||||
|
<string name="post_processing_preset_delete">删除预设</string>
|
||||||
|
<string name="post_processing_preset_reset">重置预设值</string>
|
||||||
|
<string name="post_processing_reset">重置为默认值</string>
|
||||||
|
<string name="post_processing_empty">找不到效果。请在此文件中放置 .fx 文件:</string>
|
||||||
<string name="frame_gen">帧生成</string>
|
<string name="frame_gen">帧生成</string>
|
||||||
<string name="frame_gen_per_game_description">配置针对此游戏的帧生成设定</string>
|
<string name="frame_gen_per_game_description">配置针对此游戏的帧生成设定</string>
|
||||||
<string name="frame_gen_description">设定要在使用无损缩放渲染的帧之间应用的插帧。启用后强制采用FIFO呈现模式 。</string>
|
<string name="frame_gen_description">设定要在使用无损缩放渲染的帧之间应用的插帧。启用后强制采用FIFO呈现模式 。</string>
|
||||||
@@ -304,6 +324,13 @@
|
|||||||
<string name="frame_gen_target_rate_60">60 FPS</string>
|
<string name="frame_gen_target_rate_60">60 FPS</string>
|
||||||
<string name="frame_gen_target_rate_90">90 FPS</string>
|
<string name="frame_gen_target_rate_90">90 FPS</string>
|
||||||
<string name="frame_gen_target_rate_120">120 FPS</string>
|
<string name="frame_gen_target_rate_120">120 FPS</string>
|
||||||
|
<string name="frame_gen_on">开</string>
|
||||||
|
<string name="frame_gen_off">关</string>
|
||||||
|
<string name="frame_gen_fixed">固定</string>
|
||||||
|
<string name="frame_gen_flow_auto">自动</string>
|
||||||
|
<string name="frame_gen_quick_description">打开或关闭帧生成并选择帧生成倍数。</string>
|
||||||
|
<string name="frame_gen_target_rate_quick_description">生成帧到目标帧率。倍率会自动调整。</string>
|
||||||
|
<string name="frame_gen_flow_scale_quick_description">用来估算帧间运动的分辨率。较低的值可以节省 GPU 时间。</string>
|
||||||
<string name="frame_gen_queue_target">帧队列目标</string>
|
<string name="frame_gen_queue_target">帧队列目标</string>
|
||||||
<string name="frame_gen_queue_target_description">在显示之前有多少已完成渲染的帧正在等待。较大的队列可以缓解 GPU 突发的压力,但会增加输入延迟。</string>
|
<string name="frame_gen_queue_target_description">在显示之前有多少已完成渲染的帧正在等待。较大的队列可以缓解 GPU 突发的压力,但会增加输入延迟。</string>
|
||||||
<string name="frame_gen_queue_target_0">最低延迟 (无缓冲)</string>
|
<string name="frame_gen_queue_target_0">最低延迟 (无缓冲)</string>
|
||||||
@@ -314,12 +341,14 @@
|
|||||||
<string name="frame_gen_flow_scale">运动预估分辨率</string>
|
<string name="frame_gen_flow_scale">运动预估分辨率</string>
|
||||||
<string name="frame_gen_flow_scale_description">光流通道的分辨率,以输出的比例表示。降低它是提升性能最经济的做法。</string>
|
<string name="frame_gen_flow_scale_description">光流通道的分辨率,以输出的比例表示。降低它是提升性能最经济的做法。</string>
|
||||||
<string name="frame_gen_unsupported">帧生成不可用</string>
|
<string name="frame_gen_unsupported">帧生成不可用</string>
|
||||||
|
<string name="frame_gen_unsupported_description">这个 GPU 驱动缺少 Lossless Scaling 着色器所需的 Vulkan 内存模型或半精度 (float16) 支持。</string>
|
||||||
<string name="lossless_scaling_setup_description">作为可选项。请提供您自己的 Lossless.dll 以便在之后可以启用帧生成。</string>
|
<string name="lossless_scaling_setup_description">作为可选项。请提供您自己的 Lossless.dll 以便在之后可以启用帧生成。</string>
|
||||||
<string name="lossless_scaling_install">安装 Lossless.dll</string>
|
<string name="lossless_scaling_install">安装 Lossless.dll</string>
|
||||||
<string name="lossless_scaling_install_description">帧生成需要您自己从 Lossless Scaling 获得合法的 Lossless.dll 副本</string>
|
<string name="lossless_scaling_install_description">帧生成需要您自己从 Lossless Scaling 获得合法的 Lossless.dll 副本</string>
|
||||||
<string name="lossless_scaling_replace_description">选择其它 Lossless.dll 副本</string>
|
<string name="lossless_scaling_replace_description">选择其它 Lossless.dll 副本</string>
|
||||||
<string name="frame_generation_support">帧生成</string>
|
<string name="frame_generation_support">帧生成</string>
|
||||||
<string name="frame_generation_supported">支持</string>
|
<string name="frame_generation_supported">支持</string>
|
||||||
|
<string name="frame_generation_unsupported">不支持 (无 Vulkan 内存模型或 float16)</string>
|
||||||
<string name="lossless_scaling">无损缩放</string>
|
<string name="lossless_scaling">无损缩放</string>
|
||||||
<string name="lossless_scaling_description">提供您自己的 Lossless.dll 文件以启用帧生成</string>
|
<string name="lossless_scaling_description">提供您自己的 Lossless.dll 文件以启用帧生成</string>
|
||||||
<string name="lossless_scaling_installed">已安装</string>
|
<string name="lossless_scaling_installed">已安装</string>
|
||||||
@@ -541,8 +570,6 @@
|
|||||||
<string name="renderer_reactive_flushing_description">通过牺牲性能来提升某些游戏的渲染精度。</string>
|
<string name="renderer_reactive_flushing_description">通过牺牲性能来提升某些游戏的渲染精度。</string>
|
||||||
<string name="enable_buffer_history">启用缓冲区历史</string>
|
<string name="enable_buffer_history">启用缓冲区历史</string>
|
||||||
<string name="enable_buffer_history_description">启用对先前缓冲区状态的访问。此选项可在某些游戏中提升渲染质量并保持性能的一致性。</string>
|
<string name="enable_buffer_history_description">启用对先前缓冲区状态的访问。此选项可在某些游戏中提升渲染质量并保持性能的一致性。</string>
|
||||||
<string name="enable_gpu_buffer_readback">启用 GPU 缓冲区回读</string>
|
|
||||||
<string name="enable_gpu_buffer_readback_description">在上传前回读经由 GPU 修改过的缓冲区数据,以将其保留。一些游戏会用到这项设定以正确渲染某些效果。如果硬件无法处理额外的工作负载,则可能会导致问题。</string>
|
|
||||||
<string name="use_optimized_vertex_buffers">优化顶点缓冲区</string>
|
<string name="use_optimized_vertex_buffers">优化顶点缓冲区</string>
|
||||||
<string name="use_optimized_vertex_buffers_description">启用经过优化的顶点缓冲区绑定以提升性能。需要 Mesa 26.0 及以上版本的 Turnip 或 QCOM 驱动程序。若使用较旧版本的 Turnip 驱动 (25.3 及以下版本) 则会导致崩溃。</string>
|
<string name="use_optimized_vertex_buffers_description">启用经过优化的顶点缓冲区绑定以提升性能。需要 Mesa 26.0 及以上版本的 Turnip 或 QCOM 驱动程序。若使用较旧版本的 Turnip 驱动 (25.3 及以下版本) 则会导致崩溃。</string>
|
||||||
|
|
||||||
@@ -611,6 +638,11 @@
|
|||||||
<string name="log">日志记录</string>
|
<string name="log">日志记录</string>
|
||||||
<string name="flush_by_line">按行刷新调试日志</string>
|
<string name="flush_by_line">按行刷新调试日志</string>
|
||||||
<string name="flush_by_line_description">在每行写入时刷新调试日志,使在崩溃或冻结时调试更容易。</string>
|
<string name="flush_by_line_description">在每行写入时刷新调试日志,使在崩溃或冻结时调试更容易。</string>
|
||||||
|
<string name="extended_logging">开启扩展日志</string>
|
||||||
|
<string name="extended_logging_description">将最大日志文件大小从 100 MiB 提升至 1GiB。</string>
|
||||||
|
<string name="log_filter">日志筛选器</string>
|
||||||
|
<string name="log_filter_description">控制 Eden 的日志类别。例如: *:Info Service.LM:Debug</string>
|
||||||
|
|
||||||
<!-- GPU Logging strings -->
|
<!-- GPU Logging strings -->
|
||||||
<string name="gpu_logging_header">GPU 日志</string>
|
<string name="gpu_logging_header">GPU 日志</string>
|
||||||
<string name="gpu_log_level">日志等级</string>
|
<string name="gpu_log_level">日志等级</string>
|
||||||
@@ -894,6 +926,8 @@
|
|||||||
<string name="loader_error_file_not_found">ROM 文件不存在</string>
|
<string name="loader_error_file_not_found">ROM 文件不存在</string>
|
||||||
|
|
||||||
<string name="loader_requires_firmware">游戏需要固件</string>
|
<string name="loader_requires_firmware">游戏需要固件</string>
|
||||||
|
<string name="loader_requires_firmware_description"><![CDATA[这个游戏可能需要固件才能正常运行,但您还没有安装任何固件。请在启动前安装固件,或者按 “确定” 继续启动。]]></string>
|
||||||
|
|
||||||
<!-- Intent Launch strings -->
|
<!-- Intent Launch strings -->
|
||||||
<string name="searching_for_game">正在搜索游戏...</string>
|
<string name="searching_for_game">正在搜索游戏...</string>
|
||||||
<string name="game_not_found_for_title_id">未找到标题ID的游戏: %1$s</string>
|
<string name="game_not_found_for_title_id">未找到标题ID的游戏: %1$s</string>
|
||||||
@@ -1053,7 +1087,6 @@
|
|||||||
<string name="gpu_fence_behavior_immediate">即时</string>
|
<string name="gpu_fence_behavior_immediate">即时</string>
|
||||||
<string name="gpu_fence_behavior_balanced">均衡</string>
|
<string name="gpu_fence_behavior_balanced">均衡</string>
|
||||||
<string name="gpu_fence_behavior_accurate">精确</string>
|
<string name="gpu_fence_behavior_accurate">精确</string>
|
||||||
<string name="gpu_fence_behavior_strict">严格</string>
|
|
||||||
|
|
||||||
<string name="vram_usage_conservative">保守式</string>
|
<string name="vram_usage_conservative">保守式</string>
|
||||||
<string name="vram_usage_aggressive">主动式</string>
|
<string name="vram_usage_aggressive">主动式</string>
|
||||||
|
|||||||
@@ -229,7 +229,7 @@
|
|||||||
<string name="add_directory_success">遊戲目錄新增成功</string>
|
<string name="add_directory_success">遊戲目錄新增成功</string>
|
||||||
<string name="enable_update_checks">檢查更新</string>
|
<string name="enable_update_checks">檢查更新</string>
|
||||||
<string name="enable_update_checks_description">在Eden啟動時檢查有無新版本並選擇是否要下載與安裝更新</string>
|
<string name="enable_update_checks_description">在Eden啟動時檢查有無新版本並選擇是否要下載與安裝更新</string>
|
||||||
<string name="update_available">偵測到新版本</string>
|
<string name="update_available">發現新版本</string>
|
||||||
<string name="update_available_description">有更新可供安裝:%1$s\n\n您要進行更新嗎?</string>
|
<string name="update_available_description">有更新可供安裝:%1$s\n\n您要進行更新嗎?</string>
|
||||||
<string name="downloading_update">下載更新中</string>
|
<string name="downloading_update">下載更新中</string>
|
||||||
<string name="update_download_failed">更新下載失敗</string>
|
<string name="update_download_failed">更新下載失敗</string>
|
||||||
@@ -561,8 +561,6 @@
|
|||||||
<string name="renderer_reactive_flushing_description">犧牲效能,以改善部分遊戲的轉譯準確度</string>
|
<string name="renderer_reactive_flushing_description">犧牲效能,以改善部分遊戲的轉譯準確度</string>
|
||||||
<string name="enable_buffer_history">啟用緩衝區歷史</string>
|
<string name="enable_buffer_history">啟用緩衝區歷史</string>
|
||||||
<string name="enable_buffer_history_description">允許存取先前的緩衝區狀態。此選項可能會改善部分遊戲的渲染品質與效能穩定性</string>
|
<string name="enable_buffer_history_description">允許存取先前的緩衝區狀態。此選項可能會改善部分遊戲的渲染品質與效能穩定性</string>
|
||||||
<string name="enable_gpu_buffer_readback">啟用 GPU 緩衝區讀回</string>
|
|
||||||
<string name="enable_gpu_buffer_readback_description">透過在上傳之前先將 GPU 修改過的緩衝區資料讀回來保存資料,部分遊戲需要啟用此功能才能正常渲染遊戲特效。如果硬體無法負荷可能會導致錯誤</string>
|
|
||||||
<string name="use_optimized_vertex_buffers">最佳化頂點緩衝區</string>
|
<string name="use_optimized_vertex_buffers">最佳化頂點緩衝區</string>
|
||||||
<string name="use_optimized_vertex_buffers_description">啟用最佳化的頂點緩衝區綁定。需要安裝 Mesa 26.0+ Turnip drivers/Qualcomm drivers。使用舊版 Turnip drivers 會導致當機 (25.3版和更低的版本)</string>
|
<string name="use_optimized_vertex_buffers_description">啟用最佳化的頂點緩衝區綁定。需要安裝 Mesa 26.0+ Turnip drivers/Qualcomm drivers。使用舊版 Turnip drivers 會導致當機 (25.3版和更低的版本)</string>
|
||||||
|
|
||||||
@@ -1020,9 +1018,40 @@
|
|||||||
<string name="memory_6gb">6GB (不穩定)</string>
|
<string name="memory_6gb">6GB (不穩定)</string>
|
||||||
<string name="memory_8gb">8GB (不穩定)</string>
|
<string name="memory_8gb">8GB (不穩定)</string>
|
||||||
|
|
||||||
|
<!-- CPU clock levels -->
|
||||||
|
<string name="clock_normal">正常</string>
|
||||||
|
<string name="clock_boost">加速</string>
|
||||||
|
<string name="clock_fast">超頻</string>
|
||||||
|
|
||||||
|
<!-- GPU clock levels -->
|
||||||
|
<string name="fast_gpu_normal">正常</string>
|
||||||
|
<string name="fast_gpu_medium">加速</string>
|
||||||
|
<string name="fast_gpu_high">超頻</string>
|
||||||
|
|
||||||
|
<!-- GPU swizzle texture size -->
|
||||||
|
<string name="gpu_texturesizeswizzle_verysmall">極小 (16MB)</string>
|
||||||
|
<string name="gpu_texturesizeswizzle_small">小(32MB)</string>
|
||||||
|
<string name="gpu_texturesizeswizzle_normal">正常(128MB)</string>
|
||||||
|
<string name="gpu_texturesizeswizzle_large">大(256MB)</string>
|
||||||
|
<string name="gpu_texturesizeswizzle_verylarge">極大(512MB)</string>
|
||||||
|
|
||||||
|
<!-- GPU swizzle streams -->
|
||||||
|
<string name="gpu_swizzle_verylow">極小(4MB)</string>
|
||||||
|
<string name="gpu_swizzle_low">小(8MB)</string>
|
||||||
|
<string name="gpu_swizzle_normal">正常(16MB)</string>
|
||||||
|
<string name="gpu_swizzle_medium">中(32MB)</string>
|
||||||
|
<string name="gpu_swizzle_high">大(64MB)</string>
|
||||||
|
|
||||||
|
<!-- GPU swizzle chunks -->
|
||||||
|
<string name="gpu_swizzlechunk_verylow">極小(32)</string>
|
||||||
|
<string name="gpu_swizzlechunk_low">小(64)</string>
|
||||||
|
<string name="gpu_swizzlechunk_normal">正常(128)</string>
|
||||||
|
<string name="gpu_swizzlechunk_medium">中(256)</string>
|
||||||
|
<string name="gpu_swizzlechunk_high">大(512)</string>
|
||||||
|
|
||||||
<!-- Temperature Units -->
|
<!-- Temperature Units -->
|
||||||
<string name="temperature_celsius">攝氏度</string>
|
<string name="temperature_celsius">攝氏</string>
|
||||||
<string name="temperature_fahrenheit">華氏度</string>
|
<string name="temperature_fahrenheit">華氏</string>
|
||||||
|
|
||||||
<!-- Memory Sizes -->
|
<!-- Memory Sizes -->
|
||||||
<string name="memory_byte_shorthand">B</string>
|
<string name="memory_byte_shorthand">B</string>
|
||||||
@@ -1035,6 +1064,8 @@
|
|||||||
|
|
||||||
<string name="renderer_none">無</string>
|
<string name="renderer_none">無</string>
|
||||||
|
|
||||||
|
<!-- Renderer Accuracy -->
|
||||||
|
<string name="renderer_accuracy_low">效能</string>
|
||||||
<string name="renderer_accuracy_high">準確</string>
|
<string name="renderer_accuracy_high">準確</string>
|
||||||
|
|
||||||
<!-- DMA Accuracy -->
|
<!-- DMA Accuracy -->
|
||||||
|
|||||||
@@ -558,7 +558,6 @@
|
|||||||
<item>@string/gpu_fence_behavior_immediate</item>
|
<item>@string/gpu_fence_behavior_immediate</item>
|
||||||
<item>@string/gpu_fence_behavior_balanced</item>
|
<item>@string/gpu_fence_behavior_balanced</item>
|
||||||
<item>@string/gpu_fence_behavior_accurate</item>
|
<item>@string/gpu_fence_behavior_accurate</item>
|
||||||
<item>@string/gpu_fence_behavior_strict</item>
|
|
||||||
</string-array>
|
</string-array>
|
||||||
<integer-array name="gpuFenceBehaviorValues">
|
<integer-array name="gpuFenceBehaviorValues">
|
||||||
<item>0</item>
|
<item>0</item>
|
||||||
|
|||||||
@@ -586,8 +586,6 @@
|
|||||||
<string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string>
|
<string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string>
|
||||||
<string name="enable_buffer_history">Enable buffer history</string>
|
<string name="enable_buffer_history">Enable buffer history</string>
|
||||||
<string name="enable_buffer_history_description">Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games.</string>
|
<string name="enable_buffer_history_description">Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games.</string>
|
||||||
<string name="enable_gpu_buffer_readback">Enable GPU Buffer Readback</string>
|
|
||||||
<string name="enable_gpu_buffer_readback_description">Preserves GPU-modified buffer data by reading it back before uploads. Some games require this to render certain effects properly. May cause issues if the hardware cannot handle the additional workload.</string>
|
|
||||||
<string name="use_optimized_vertex_buffers">Optimized Vertex Buffers</string>
|
<string name="use_optimized_vertex_buffers">Optimized Vertex Buffers</string>
|
||||||
<string name="use_optimized_vertex_buffers_description">Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers (25.3 and below).</string>
|
<string name="use_optimized_vertex_buffers_description">Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers (25.3 and below).</string>
|
||||||
|
|
||||||
@@ -1138,7 +1136,6 @@
|
|||||||
<string name="gpu_fence_behavior_immediate">Immediate</string>
|
<string name="gpu_fence_behavior_immediate">Immediate</string>
|
||||||
<string name="gpu_fence_behavior_balanced">Balanced</string>
|
<string name="gpu_fence_behavior_balanced">Balanced</string>
|
||||||
<string name="gpu_fence_behavior_accurate">Accurate</string>
|
<string name="gpu_fence_behavior_accurate">Accurate</string>
|
||||||
<string name="gpu_fence_behavior_strict">Strict</string>
|
|
||||||
|
|
||||||
<!-- ASTC Decoding Method Choices -->
|
<!-- ASTC Decoding Method Choices -->
|
||||||
<string name="accelerate_astc_cpu" translatable="false">CPU</string>
|
<string name="accelerate_astc_cpu" translatable="false">CPU</string>
|
||||||
|
|||||||
@@ -108,7 +108,8 @@ add_library(
|
|||||||
settings_setting.h
|
settings_setting.h
|
||||||
slot_vector.h
|
slot_vector.h
|
||||||
socket_types.h
|
socket_types.h
|
||||||
spin_lock.h
|
sparse_large_vector.cpp
|
||||||
|
sparse_large_vector.h
|
||||||
stb.cpp
|
stb.cpp
|
||||||
stb.h
|
stb.h
|
||||||
steady_clock.cpp
|
steady_clock.cpp
|
||||||
@@ -137,8 +138,6 @@ add_library(
|
|||||||
uuid.cpp
|
uuid.cpp
|
||||||
uuid.h
|
uuid.h
|
||||||
vector_math.h
|
vector_math.h
|
||||||
virtual_buffer.cpp
|
|
||||||
virtual_buffer.h
|
|
||||||
zstd_compression.cpp
|
zstd_compression.cpp
|
||||||
zstd_compression.h
|
zstd_compression.h
|
||||||
fs/ryujinx_compat.h fs/ryujinx_compat.cpp
|
fs/ryujinx_compat.h fs/ryujinx_compat.cpp
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/fiber.h"
|
#include "common/fiber.h"
|
||||||
#include "common/virtual_buffer.h"
|
|
||||||
|
|
||||||
#include <boost/context/detail/fcontext.hpp>
|
#include <boost/context/detail/fcontext.hpp>
|
||||||
|
|
||||||
|
|||||||
+40
-12
@@ -178,6 +178,14 @@ public:
|
|||||||
Release();
|
Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void* Allocate(size_t size) {
|
||||||
|
auto* ptr = VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
if (ptr == nullptr) {
|
||||||
|
LOG_CRITICAL(HW_Memory, "Failed to allocate fallback buffer with size {:#x}, error {}", size, GetLastError());
|
||||||
|
}
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) {
|
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) {
|
||||||
std::unique_lock lock{placeholder_mutex};
|
std::unique_lock lock{placeholder_mutex};
|
||||||
if (!IsNiechePlaceholder(virtual_offset, length)) {
|
if (!IsNiechePlaceholder(virtual_offset, length)) {
|
||||||
@@ -398,6 +406,10 @@ private:
|
|||||||
// For managarm: see https://github.com/managarm/managarm/issues/1370
|
// For managarm: see https://github.com/managarm/managarm/issues/1370
|
||||||
#else // ^^^ Windows ^^^ vvv POSIX vvv
|
#else // ^^^ Windows ^^^ vvv POSIX vvv
|
||||||
|
|
||||||
|
#ifndef MAP_NOCORE
|
||||||
|
#define MAP_NOCORE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ARCHITECTURE_arm64
|
#ifdef ARCHITECTURE_arm64
|
||||||
|
|
||||||
static void* ChooseVirtualBase(size_t virtual_size) {
|
static void* ChooseVirtualBase(size_t virtual_size) {
|
||||||
@@ -422,7 +434,7 @@ static void* ChooseVirtualBase(size_t virtual_size) {
|
|||||||
// Note: we may be able to take advantage of MAP_FIXED_NOREPLACE here.
|
// Note: we may be able to take advantage of MAP_FIXED_NOREPLACE here.
|
||||||
void* map_pointer =
|
void* map_pointer =
|
||||||
mmap(reinterpret_cast<void*>(hint_address), virtual_size, PROT_READ | PROT_WRITE,
|
mmap(reinterpret_cast<void*>(hint_address), virtual_size, PROT_READ | PROT_WRITE,
|
||||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
|
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_NOCORE, -1, 0);
|
||||||
|
|
||||||
// If we successfully mapped, we're done.
|
// If we successfully mapped, we're done.
|
||||||
if (reinterpret_cast<uintptr_t>(map_pointer) == hint_address) {
|
if (reinterpret_cast<uintptr_t>(map_pointer) == hint_address) {
|
||||||
@@ -442,11 +454,11 @@ static void* ChooseVirtualBase(size_t virtual_size) {
|
|||||||
|
|
||||||
static void* ChooseVirtualBase(size_t virtual_size) {
|
static void* ChooseVirtualBase(size_t virtual_size) {
|
||||||
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__managarm__) || defined(__AIX__)
|
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__managarm__) || defined(__AIX__)
|
||||||
void* virtual_base = mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_ALIGNED_SUPER, -1, 0);
|
void* virtual_base = mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_ALIGNED_SUPER | MAP_NOCORE, -1, 0);
|
||||||
if (virtual_base != MAP_FAILED)
|
if (virtual_base != MAP_FAILED)
|
||||||
return virtual_base;
|
return virtual_base;
|
||||||
#endif
|
#endif
|
||||||
return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
|
return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_NOCORE, -1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -540,13 +552,13 @@ public:
|
|||||||
}
|
}
|
||||||
if (use_anon) {
|
if (use_anon) {
|
||||||
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
|
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
|
||||||
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
|
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_NOCORE, -1, 0));
|
||||||
if (fd > 0) {
|
if (fd > 0) {
|
||||||
fd = -1;
|
fd = -1;
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
|
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NOCORE, fd, 0));
|
||||||
}
|
}
|
||||||
if (backing_base == MAP_FAILED) {
|
if (backing_base == MAP_FAILED) {
|
||||||
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
|
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
|
||||||
@@ -570,6 +582,14 @@ public:
|
|||||||
Release();
|
Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void* Allocate(size_t size) {
|
||||||
|
auto* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||||
|
if (ptr == MAP_FAILED) {
|
||||||
|
LOG_CRITICAL(HW_Memory, "Failed to allocate fallback buffer with size {:#x}, {}", size, strerror(errno));
|
||||||
|
}
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) {
|
void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) {
|
||||||
// Intersect the range with our address space.
|
// Intersect the range with our address space.
|
||||||
AdjustMap(&virtual_offset, &length);
|
AdjustMap(&virtual_offset, &length);
|
||||||
@@ -690,12 +710,10 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
|
|||||||
{
|
{
|
||||||
#if defined(__OPENORBIS__) || defined(__managarm__)
|
#if defined(__OPENORBIS__) || defined(__managarm__)
|
||||||
LOG_WARNING(HW_Memory, "Platform doesn't support fastmem");
|
LOG_WARNING(HW_Memory, "Platform doesn't support fastmem");
|
||||||
fallback_buffer.emplace(backing_size);
|
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
|
||||||
backing_base = fallback_buffer->data();
|
|
||||||
virtual_base = nullptr;
|
virtual_base = nullptr;
|
||||||
#else
|
#else
|
||||||
// Try to allocate a fastmem arena.
|
// Try to allocate a fastmem arena.
|
||||||
// The implementation will fail with std::bad_alloc on errors.
|
|
||||||
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize);
|
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize);
|
||||||
if (impl->Init()) {
|
if (impl->Init()) {
|
||||||
backing_base = impl->backing_base;
|
backing_base = impl->backing_base;
|
||||||
@@ -706,16 +724,26 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
|
|||||||
virtual_base_offset = virtual_base - impl->virtual_base;
|
virtual_base_offset = virtual_base - impl->virtual_base;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
impl.reset();
|
|
||||||
LOG_WARNING(HW_Memory, "Platform can support fastmem, but can't create it");
|
LOG_WARNING(HW_Memory, "Platform can support fastmem, but can't create it");
|
||||||
fallback_buffer.emplace(backing_size);
|
fallback_buffer = true;
|
||||||
backing_base = fallback_buffer->data();
|
backing_base = static_cast<u8*>(impl->Allocate(backing_size));
|
||||||
virtual_base = nullptr;
|
virtual_base = nullptr;
|
||||||
|
impl.reset();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
HostMemory::~HostMemory() = default;
|
HostMemory::~HostMemory() {
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (fallback_buffer) {
|
||||||
|
VirtualFree(backing_base, backing_size, MEM_RELEASE);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (fallback_buffer) {
|
||||||
|
munmap(backing_base, backing_size);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
HostMemory::HostMemory(HostMemory&&) noexcept = default;
|
HostMemory::HostMemory(HostMemory&&) noexcept = default;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/virtual_buffer.h"
|
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
@@ -86,7 +85,7 @@ private:
|
|||||||
u8* virtual_base{};
|
u8* virtual_base{};
|
||||||
size_t virtual_base_offset{};
|
size_t virtual_base_offset{};
|
||||||
// Windows requires it for kernels whom lack proper support for some functions!
|
// Windows requires it for kernels whom lack proper support for some functions!
|
||||||
std::optional<Common::VirtualBuffer<u8>> fallback_buffer;
|
bool fallback_buffer{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
@@ -13,39 +13,11 @@ PageTable::PageTable() = default;
|
|||||||
|
|
||||||
PageTable::~PageTable() noexcept = default;
|
PageTable::~PageTable() noexcept = default;
|
||||||
|
|
||||||
bool PageTable::BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context,
|
void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page_bits) {
|
||||||
Common::ProcessAddress address) const {
|
auto const num_page_table_entries = 1ULL << (address_space_width_in_bits - page_bits);
|
||||||
out_context->next_offset = GetInteger(address);
|
entries.ResizeAndClear(num_page_table_entries);
|
||||||
out_context->next_page = address / page_size;
|
|
||||||
|
|
||||||
return this->ContinueTraversal(out_entry, out_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* context) const {
|
|
||||||
// Setup invalid defaults.
|
|
||||||
out_entry->phys_addr = 0;
|
|
||||||
out_entry->block_size = page_size;
|
|
||||||
// Validate that we can read the actual entry.
|
|
||||||
if (auto const page = context->next_page; page < entries.size()) {
|
|
||||||
// Validate that the entry is mapped.
|
|
||||||
if (auto const paddr = entries[page].addr; paddr != 0) {
|
|
||||||
// Populate the results.
|
|
||||||
out_entry->phys_addr = paddr + context->next_offset;
|
|
||||||
context->next_page += 1;
|
|
||||||
context->next_offset += page_size;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context->next_page += 1;
|
|
||||||
context->next_offset += page_size;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits) {
|
|
||||||
auto const num_page_table_entries = 1ULL << (address_space_width_in_bits - page_size_in_bits);
|
|
||||||
entries.resize(num_page_table_entries);
|
|
||||||
current_address_space_width_in_bits = address_space_width_in_bits;
|
current_address_space_width_in_bits = address_space_width_in_bits;
|
||||||
page_size = 1ULL << page_size_in_bits;
|
current_page_bits = page_bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|||||||
+64
-56
@@ -9,22 +9,22 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "common/sparse_large_vector.h"
|
||||||
#include "common/typed_address.h"
|
#include "common/typed_address.h"
|
||||||
#include "common/virtual_buffer.h"
|
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
enum class PageType : u8 {
|
enum class PageType : u8 {
|
||||||
/// Page is unmapped and should cause an access error.
|
/// Page is unmapped and should cause an access error.
|
||||||
Unmapped,
|
Unmapped = 0b00,
|
||||||
/// Page is mapped to regular memory. This is the only type you can get pointers to.
|
/// Page is mapped to regular memory. This is the only type you can get pointers to.
|
||||||
Memory,
|
Memory = 0b01,
|
||||||
/// Page is mapped to regular memory, but inaccessible from CPU fastmem and must use
|
/// Page is mapped to regular memory, but inaccessible from CPU fastmem and must use
|
||||||
/// the callbacks.
|
/// the callbacks.
|
||||||
DebugMemory,
|
DebugMemory = 0b10,
|
||||||
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
||||||
/// invalidation
|
/// invalidation
|
||||||
RasterizerCachedMemory,
|
RasterizerCachedMemory = 0b11,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,57 +42,86 @@ struct PageTable {
|
|||||||
u64 next_offset{};
|
u64 next_offset{};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Number of bits reserved for attribute tagging.
|
/// Masks out bits reserved for attribute tagging.
|
||||||
/// This can be at most the guaranteed alignment of the pointers in the page table.
|
static constexpr u64 ATTRIBUTE_MASK = ((1ULL << 44) - 1) << 12;
|
||||||
static constexpr int ATTRIBUTE_BITS = 2;
|
|
||||||
|
/// Specifies sign bit for page table entries.
|
||||||
|
static constexpr u64 SIGN_BIT = 45 + 12; // 44 bits of data + page offset
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pair of host pointer and page type attribute.
|
* Atomic tuple of host pointer, page type, and block id.
|
||||||
* This uses the lower bits of a given pointer to store the attribute tag.
|
* This uses the lower bits of a given pointer to store the attributes.
|
||||||
* Writing and reading the pointer attribute pair is guaranteed to be atomic for the same method
|
* Writing and reading the pointer attribute pair is guaranteed to be atomic for the same method
|
||||||
* call. In other words, they are guaranteed to be synchronized at all times.
|
* call. In other words, they are guaranteed to be synchronized at all times.
|
||||||
*/
|
*/
|
||||||
class PageInfo {
|
class PageEntryData {
|
||||||
public:
|
public:
|
||||||
|
struct Data {
|
||||||
|
Data(bool marked_, PageType type_, u16 block_, u64 page_)
|
||||||
|
: marked(static_cast<u64>(marked_) & 0b1)
|
||||||
|
, type(static_cast<u64>(type_) & ((1ULL << 2) - 1))
|
||||||
|
, block(static_cast<u64>(block_) & ((1ULL << 9) - 1))
|
||||||
|
, page((page_ >> 12) & ((1ULL << 45) - 1))
|
||||||
|
, block2((static_cast<u64>(block_) >> 9) & ((1ULL << 7) - 1)) {}
|
||||||
|
u64 marked : 1;
|
||||||
|
u64 type : 2;
|
||||||
|
u64 block : 9;
|
||||||
|
u64 page : 45; // 44 bits of actual data (64 - page offset (12) - reserved (8)) + a sign bit
|
||||||
|
u64 block2 : 7;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] Data Raw() const noexcept {
|
||||||
|
return std::bit_cast<Data>(data_raw.load(std::memory_order_relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the page pointer
|
/// Returns the page pointer
|
||||||
[[nodiscard]] uintptr_t Pointer() const noexcept {
|
[[nodiscard]] uintptr_t Pointer(bool ignored_marked = false) const noexcept {
|
||||||
return ExtractPointer(raw.load(std::memory_order_relaxed));
|
return ExtractPointer(std::bit_cast<Data>(data_raw.load(std::memory_order_relaxed)), ignored_marked);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the page type attribute
|
/// Returns the page type attribute
|
||||||
[[nodiscard]] PageType Type() const noexcept {
|
[[nodiscard]] PageType Type() const noexcept {
|
||||||
return ExtractType(raw.load(std::memory_order_relaxed));
|
return static_cast<PageType>(std::bit_cast<Data>(data_raw.load(std::memory_order_relaxed)).type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the block identifier.
|
||||||
|
[[nodiscard]] u16 Block() const noexcept {
|
||||||
|
return ExtractBlock(std::bit_cast<Data>(data_raw.load(std::memory_order_relaxed)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the page pointer and attribute pair, extracted from the same atomic read
|
/// Returns the page pointer and attribute pair, extracted from the same atomic read
|
||||||
[[nodiscard]] std::pair<uintptr_t, PageType> PointerType() const noexcept {
|
[[nodiscard]] std::tuple<uintptr_t, PageType, u16> PointerTypeBlock(bool ignore_marked = false) const noexcept {
|
||||||
const uintptr_t non_atomic_raw = raw.load(std::memory_order_relaxed);
|
const auto non_atomic_raw = std::bit_cast<Data>(data_raw.load(std::memory_order_relaxed));
|
||||||
return {ExtractPointer(non_atomic_raw), ExtractType(non_atomic_raw)};
|
return {ExtractPointer(non_atomic_raw, ignore_marked), static_cast<PageType>(non_atomic_raw.type), ExtractBlock(non_atomic_raw)};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the raw representation of the page information.
|
/// Write page info atomically
|
||||||
/// Use ExtractPointer and ExtractType to unpack the value.
|
inline void Store(bool marked, PageType type, u16 block, uintptr_t pointer) noexcept {
|
||||||
[[nodiscard]] uintptr_t Raw() const noexcept {
|
data_raw.store(std::bit_cast<u64>(Data{marked, type, block, pointer}));
|
||||||
return raw.load(std::memory_order_relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a page pointer and type pair atomically
|
inline void MarkRasterizerCached() noexcept {
|
||||||
void Store(uintptr_t pointer, PageType type) noexcept {
|
data_raw.fetch_or(0b111);
|
||||||
raw.store(pointer | uintptr_t(type));
|
}
|
||||||
|
|
||||||
|
inline void MarkDebug(u64 ptr, u16 block) noexcept {
|
||||||
|
Store(true, PageType::DebugMemory, block, ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unpack a pointer from a page info raw representation
|
/// Unpack a pointer from a page info raw representation
|
||||||
[[nodiscard]] static uintptr_t ExtractPointer(uintptr_t raw) noexcept {
|
[[nodiscard]] static uintptr_t ExtractPointer(Data raw, bool ignore_marked = false) noexcept {
|
||||||
return raw & (~uintptr_t{0} << ATTRIBUTE_BITS);
|
return raw.marked && !ignore_marked ? 0
|
||||||
|
// shift raw.page's fake sign bit to the actual sign bit, then sign extend
|
||||||
|
: ((s64)(raw.page << (64 - 44))) >> (64 - 44 - 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unpack a page type from a page info raw representation
|
[[nodiscard]] static u16 ExtractBlock(Data raw) noexcept {
|
||||||
[[nodiscard]] static PageType ExtractType(uintptr_t raw) noexcept {
|
return static_cast<u16>(raw.block | (raw.block2 << 9));
|
||||||
return static_cast<PageType>(raw & ((uintptr_t{1} << ATTRIBUTE_BITS) - 1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::atomic<uintptr_t> raw;
|
std::atomic<u64> data_raw;
|
||||||
|
static_assert(sizeof(Data) == sizeof(std::atomic<u64>));
|
||||||
};
|
};
|
||||||
|
|
||||||
PageTable();
|
PageTable();
|
||||||
@@ -100,13 +129,8 @@ struct PageTable {
|
|||||||
|
|
||||||
PageTable(const PageTable&) = delete;
|
PageTable(const PageTable&) = delete;
|
||||||
PageTable& operator=(const PageTable&) = delete;
|
PageTable& operator=(const PageTable&) = delete;
|
||||||
|
PageTable(PageTable&&) noexcept = delete;
|
||||||
PageTable(PageTable&&) noexcept = default;
|
PageTable& operator=(PageTable&&) noexcept = delete;
|
||||||
PageTable& operator=(PageTable&&) noexcept = default;
|
|
||||||
|
|
||||||
bool BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context,
|
|
||||||
Common::ProcessAddress address) const;
|
|
||||||
bool ContinueTraversal(TraversalEntry* out_entry, TraversalContext* context) const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resizes the page table to be able to accommodate enough pages within
|
* Resizes the page table to be able to accommodate enough pages within
|
||||||
@@ -121,30 +145,14 @@ struct PageTable {
|
|||||||
return current_address_space_width_in_bits;
|
return current_address_space_width_in_bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetPhysicalAddress(Common::PhysicalAddress* out_phys_addr,
|
|
||||||
Common::ProcessAddress virt_addr) const {
|
|
||||||
if (virt_addr > (1ULL << this->GetAddressSpaceBits())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*out_phys_addr = entries[virt_addr / page_size].addr + GetInteger(virt_addr);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Vector of memory pointers backing each page. An entry can only be non-null if the
|
/// Vector of memory pointers backing each page. An entry can only be non-null if the
|
||||||
/// corresponding attribute element is of type `Memory`.
|
/// corresponding attribute element is of type `Memory`.
|
||||||
struct PageEntryData {
|
SparseLargeVector<PageEntryData> entries;
|
||||||
PageInfo ptr;
|
static_assert(sizeof(PageEntryData) == 8);
|
||||||
u64 block;
|
|
||||||
u64 addr;
|
|
||||||
u64 padding;
|
|
||||||
};
|
|
||||||
VirtualBuffer<PageEntryData> entries;
|
|
||||||
static_assert(sizeof(PageEntryData) == 32);
|
|
||||||
|
|
||||||
u8* fastmem_arena{};
|
u8* fastmem_arena{};
|
||||||
std::size_t current_address_space_width_in_bits{};
|
std::size_t current_address_space_width_in_bits{};
|
||||||
std::size_t page_size{};
|
std::size_t current_page_bits{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|||||||
@@ -178,10 +178,6 @@ bool IsGPUFenceBehaviorAccurate() {
|
|||||||
return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Accurate;
|
return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Accurate;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsGPUFenceBehaviorStrict() {
|
|
||||||
return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Strict;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsFastmemEnabled() {
|
bool IsFastmemEnabled() {
|
||||||
if (values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Debugging)
|
if (values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Debugging)
|
||||||
return bool(values.cpuopt_fastmem);
|
return bool(values.cpuopt_fastmem);
|
||||||
|
|||||||
@@ -525,7 +525,7 @@ struct Values {
|
|||||||
SwitchableSetting<GpuFenceBehavior, true> gpu_fence_behavior{linkage,
|
SwitchableSetting<GpuFenceBehavior, true> gpu_fence_behavior{linkage,
|
||||||
GpuFenceBehavior::Default,
|
GpuFenceBehavior::Default,
|
||||||
GpuFenceBehavior::Default,
|
GpuFenceBehavior::Default,
|
||||||
GpuFenceBehavior::Strict,
|
GpuFenceBehavior::Accurate,
|
||||||
"gpu_fence_behavior",
|
"gpu_fence_behavior",
|
||||||
Category::RendererAdvanced,
|
Category::RendererAdvanced,
|
||||||
Specialization::Default,
|
Specialization::Default,
|
||||||
@@ -655,13 +655,6 @@ struct Values {
|
|||||||
|
|
||||||
SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack",
|
SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack",
|
||||||
Category::RendererHacks};
|
Category::RendererHacks};
|
||||||
SwitchableSetting<bool> enable_gpu_buffer_readback{linkage,
|
|
||||||
false,
|
|
||||||
"enable_gpu_buffer_readback",
|
|
||||||
Category::RendererAdvanced,
|
|
||||||
Specialization::Default,
|
|
||||||
true,
|
|
||||||
true};
|
|
||||||
|
|
||||||
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
|
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
|
||||||
Category::RendererHacks};
|
Category::RendererHacks};
|
||||||
@@ -979,7 +972,6 @@ bool IsDMALevelSafe();
|
|||||||
bool IsGPUFenceBehaviorDefault();
|
bool IsGPUFenceBehaviorDefault();
|
||||||
bool IsGPUFenceBehaviorBalanced();
|
bool IsGPUFenceBehaviorBalanced();
|
||||||
bool IsGPUFenceBehaviorAccurate();
|
bool IsGPUFenceBehaviorAccurate();
|
||||||
bool IsGPUFenceBehaviorStrict();
|
|
||||||
|
|
||||||
bool IsFastmemEnabled();
|
bool IsFastmemEnabled();
|
||||||
void SetNceEnabled(bool is_64bit);
|
void SetNceEnabled(bool is_64bit);
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ ENUM(VramUsageMode, Conservative, Aggressive);
|
|||||||
ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV);
|
ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV);
|
||||||
ENUM(GpuAccuracy, Low, High);
|
ENUM(GpuAccuracy, Low, High);
|
||||||
ENUM(DmaAccuracy, Default, Unsafe, Safe);
|
ENUM(DmaAccuracy, Default, Unsafe, Safe);
|
||||||
ENUM(GpuFenceBehavior, Default, Immediate, Balanced, Accurate, Strict);
|
ENUM(GpuFenceBehavior, Default, Immediate, Balanced, Accurate);
|
||||||
ENUM(CpuBackend, Dynarmic, Nce);
|
ENUM(CpuBackend, Dynarmic, Nce);
|
||||||
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid, Debugging);
|
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid, Debugging);
|
||||||
ENUM(CpuClock, Normal, Boost, Overclock)
|
ENUM(CpuClock, Normal, Boost, Overclock)
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
/* virtual_buffer.cpp */
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <mutex>
|
||||||
|
#else
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "common/alignment.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/sparse_large_vector.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
static std::vector<std::pair<u64, u64>> vector_regions {};
|
||||||
|
|
||||||
|
// Workaround for handling non-commited memory accessed by Dynarmic; usually result of an error
|
||||||
|
static LONG WINAPI FakePageFaultHandler(PEXCEPTION_POINTERS info) {
|
||||||
|
DWORD code = info->ExceptionRecord->ExceptionCode;
|
||||||
|
u64 exception_addr = reinterpret_cast<u64>(info->ExceptionRecord->ExceptionAddress);
|
||||||
|
|
||||||
|
if (code != EXCEPTION_ACCESS_VIOLATION) {
|
||||||
|
// Not our problem
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 addr = 0, addr2 = 0;
|
||||||
|
|
||||||
|
for (auto region: vector_regions) {
|
||||||
|
auto addr_shifted = exception_addr >> HostPageBits;
|
||||||
|
if (region.first <= addr_shifted && addr_shifted <= region.second) {
|
||||||
|
addr = addr_shifted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page-boundary accesses
|
||||||
|
if (auto addr_ = (exception_addr + 0x40) >> HostPageBits; addr_ != addr_shifted && region.first <= addr_ && addr_ <= region.second) {
|
||||||
|
addr2 = addr_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addr != 0 || addr2 != 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addr == 0 && addr2 == 0) {
|
||||||
|
// Not our problem
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(HW_Memory, "Accessing an unallocated region of a SparseLargeVector at {:#x}; this shouldn't happen and is likely a Dynarmic error!", exception_addr);
|
||||||
|
|
||||||
|
// Commit this region
|
||||||
|
if (addr != 0) {
|
||||||
|
if (!CommitVectorPage(addr << HostPageBits, false)) {
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Commit next region if needed
|
||||||
|
if (addr2 != 0) {
|
||||||
|
if (!CommitVectorPage(addr2 << HostPageBits, false)) {
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXCEPTION_CONTINUE_EXECUTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommitVectorPage(uintptr_t addr, bool write) noexcept {
|
||||||
|
MEMORY_BASIC_INFORMATION info {};
|
||||||
|
auto res = VirtualQuery(reinterpret_cast<void*>(addr), &info, sizeof(info));
|
||||||
|
if (res == 0) {
|
||||||
|
LOG_CRITICAL(HW_Memory, "Failed to query large buffer region at {:#x} with error {}, will try committing anyway", addr, GetLastError());
|
||||||
|
} else if (info.State != MEM_RESERVE) {
|
||||||
|
LOG_ERROR(HW_Memory, "Tried to commit an unreserved large buffer region at {:#x} that is not mapped or is already committed (state {:#x})", addr, info.State);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto perm = write ? PAGE_READWRITE : PAGE_READONLY;
|
||||||
|
void* res2 = VirtualAlloc(reinterpret_cast<LPVOID>(addr), HostPageSize, MEM_COMMIT, perm);
|
||||||
|
if (res2 == nullptr) {
|
||||||
|
LOG_ERROR(HW_Memory, "Failed to commit large buffer region at {:#x}, error {}", addr, GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAP_NOCORE
|
||||||
|
#define MAP_NOCORE 0
|
||||||
|
#endif
|
||||||
|
#ifndef MADV_FREE
|
||||||
|
#define MADV_FREE MADV_DONTNEED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void DecommitVectorPage(uintptr_t base) noexcept {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
VirtualFree(reinterpret_cast<LPVOID>(base), HostPageSize, MEM_DECOMMIT);
|
||||||
|
#elif defined(__linux__)
|
||||||
|
// Linux's MADV_DONTNEED zeros out pages for us
|
||||||
|
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_DONTNEED);
|
||||||
|
#else
|
||||||
|
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_FREE);
|
||||||
|
std::memset(reinterpret_cast<void*>(base), 0, HostPageSize);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void* AllocateMemoryPages(std::size_t size) noexcept {
|
||||||
|
if (auto page = HostPageSize; size % page != 0) {
|
||||||
|
LOG_WARNING(HW_Memory, "Allocating unaligned large vector with size {:#x}; aligning to {} page size", size, page);
|
||||||
|
size = AlignUp(size, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// We will never use this memory entirely so instead of committing it up front let's just reserve it and commit each page individually
|
||||||
|
void* base = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
|
||||||
|
|
||||||
|
if (base != nullptr) {
|
||||||
|
vector_regions.emplace_back(reinterpret_cast<u64>(base), reinterpret_cast<u64>(base) + size);
|
||||||
|
|
||||||
|
static std::once_flag flag;
|
||||||
|
std::call_once(flag, []() { AddVectoredExceptionHandler(1, FakePageFaultHandler); });
|
||||||
|
} else {
|
||||||
|
// Try committing everything instead??
|
||||||
|
LOG_WARNING(HW_Memory, "Failed to reserve large vector region with error {}, trying to commit instead..", GetLastError());
|
||||||
|
base = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
}
|
||||||
|
ASSERT_MSG(base, "Failed to reserve {:#x} sized region with error {}", size, GetLastError());
|
||||||
|
#else
|
||||||
|
void* base = mmap(nullptr, size, PROT_READ, MAP_ANON | MAP_PRIVATE | MAP_NOCORE, -1, 0);
|
||||||
|
if (base == MAP_FAILED)
|
||||||
|
base = nullptr;
|
||||||
|
ASSERT_MSG(base, "Failed to allocate {:#x} sized region with error {}", size, strerror(errno));
|
||||||
|
#endif
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept {
|
||||||
|
if (auto page = HostPageSize; size % page != 0) {
|
||||||
|
size = AlignUp(size, page);
|
||||||
|
}
|
||||||
|
if (!base)
|
||||||
|
return;
|
||||||
|
#ifdef _WIN32
|
||||||
|
ASSERT(VirtualFree(base, 0, MEM_RELEASE));
|
||||||
|
#else
|
||||||
|
ASSERT(munmap(base, size) == 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
/* virtual_buffer.h */
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <bit>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "common/alignment.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
constexpr u64 HostPageSize = 0x1000;
|
||||||
|
constexpr u64 HostPageBits = 12;
|
||||||
|
constexpr u64 HostPageMask = ~(HostPageSize - 1);
|
||||||
|
bool CommitVectorPage(uintptr_t addr, bool write) noexcept;
|
||||||
|
#else
|
||||||
|
const u64 HostPageSize = sysconf(_SC_PAGESIZE);
|
||||||
|
const u64 HostPageBits = std::countr_zero(HostPageSize);
|
||||||
|
const u64 HostPageMask = ~(HostPageSize - 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void* AllocateMemoryPages(std::size_t size) noexcept;
|
||||||
|
void FreeMemoryPages(void* base, std::size_t size) noexcept;
|
||||||
|
void DecommitVectorPage(uintptr_t base) noexcept;
|
||||||
|
|
||||||
|
/// A large page-aligned buffer that has optimized memory usage for zero-writes.
|
||||||
|
template <typename T>
|
||||||
|
// MSVC doesn't regard structs with atomics as trivially copyable
|
||||||
|
// requires std::is_trivially_copyable_v<T>
|
||||||
|
class SparseLargeVector final {
|
||||||
|
public:
|
||||||
|
constexpr SparseLargeVector() = default;
|
||||||
|
|
||||||
|
explicit SparseLargeVector(std::size_t count) noexcept
|
||||||
|
: alloc_size{count * sizeof(T)}
|
||||||
|
{
|
||||||
|
base_ptr = static_cast<T*>(AllocateMemoryPages(alloc_size));
|
||||||
|
|
||||||
|
// each item in vector holds information for 64 pages
|
||||||
|
auto denom = HostPageSize * 64;
|
||||||
|
committed_pages = std::vector<std::atomic<u64>>((alloc_size + denom - 1) / denom);
|
||||||
|
}
|
||||||
|
|
||||||
|
~SparseLargeVector() noexcept {
|
||||||
|
FreeMemoryPages(base_ptr, alloc_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
SparseLargeVector(const SparseLargeVector&) = delete;
|
||||||
|
SparseLargeVector& operator=(const SparseLargeVector&) = delete;
|
||||||
|
SparseLargeVector(SparseLargeVector&& other) = delete;
|
||||||
|
SparseLargeVector& operator=(SparseLargeVector&& other) = delete;
|
||||||
|
|
||||||
|
void ResizeAndClear(std::size_t count) noexcept {
|
||||||
|
if (auto const new_size = count * sizeof(T); new_size != alloc_size) {
|
||||||
|
FreeMemoryPages(base_ptr, alloc_size);
|
||||||
|
alloc_size = new_size;
|
||||||
|
base_ptr = static_cast<T*>(AllocateMemoryPages(alloc_size));
|
||||||
|
|
||||||
|
auto denom = HostPageSize * 64;
|
||||||
|
committed_pages = std::vector<std::atomic<u64>>((alloc_size + denom - 1) / denom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the value of the requested index and allocates memory if needed.
|
||||||
|
T& GetAndFault(std::size_t index) noexcept {
|
||||||
|
if (index > alloc_size / sizeof(T)) {
|
||||||
|
UNREACHABLE_MSG("Out of bounds RW access on SparseLargeVector @ {}", index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsCommittedPage(index)) {
|
||||||
|
CommitPage(index);
|
||||||
|
}
|
||||||
|
return base_ptr[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the value of the requested index if initialized, or will otherwise return a zero-initialized object.
|
||||||
|
const T& GetOrDefault(std::size_t index) const {
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (!IsCommittedPage(index)) {
|
||||||
|
return *reinterpret_cast<const T*>(&default_val);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// On non-Windows, OS page table should optimize this by pointing to a zero page if unallocated.
|
||||||
|
return base_ptr[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Set(std::size_t index, const T& value) noexcept {
|
||||||
|
if (index > alloc_size / sizeof(T)) {
|
||||||
|
LOG_CRITICAL(Common_Memory, "Out of bounds write on SparseLargeVector @ {}", index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!IsCommittedPage(index))
|
||||||
|
CommitPage(index);
|
||||||
|
base_ptr[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZeroRegion(std::size_t start, std::size_t end_) noexcept {
|
||||||
|
u64 base = reinterpret_cast<u64>(&base_ptr[start]);
|
||||||
|
const u64 end = reinterpret_cast<u64>(&base_ptr[end_]);
|
||||||
|
|
||||||
|
const u64 end_page = AlignUp(base, HostPageSize);
|
||||||
|
const u64 first_size = (std::min)(end_page, end) - base;
|
||||||
|
|
||||||
|
if (IsCommittedPage(start)) {
|
||||||
|
std::memset(reinterpret_cast<void*>(base), 0, first_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end <= end_page)
|
||||||
|
return;
|
||||||
|
|
||||||
|
base = end_page;
|
||||||
|
|
||||||
|
for (u64 page = base; page < end; page += HostPageSize) {
|
||||||
|
auto index = (page - reinterpret_cast<u64>(base_ptr)) / sizeof(T);
|
||||||
|
if (!IsCommittedPage(index)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end - page >= HostPageSize) {
|
||||||
|
DecommitPage(index);
|
||||||
|
} else {
|
||||||
|
std::memset(reinterpret_cast<void*>(page), 0, end - page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void CommitRegion(size_t index, size_t end_) {
|
||||||
|
const u64 base = static_cast<u64>(index) * sizeof(T);
|
||||||
|
const u64 end = static_cast<u64>(end_) * sizeof(T);
|
||||||
|
|
||||||
|
for (u64 page = AlignDown(base, HostPageSize); page < end; page += HostPageSize) {
|
||||||
|
if (!IsCommittedPage(page / sizeof(T))) {
|
||||||
|
CommitPage(page / sizeof(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr T& GetUnchecked(size_t index) {
|
||||||
|
return base_ptr[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr const T& operator[](std::size_t index) const noexcept {
|
||||||
|
return GetOrDefault(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr const T* data() const noexcept {
|
||||||
|
return base_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr std::size_t size() const noexcept {
|
||||||
|
return alloc_size / sizeof(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] constexpr bool IsCommittedPage(std::size_t index) const noexcept {
|
||||||
|
if (index > alloc_size / sizeof(T)) {
|
||||||
|
LOG_CRITICAL(Common_Memory, "Out of bounds access on large vector @ {}", index);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto page = (index * sizeof(T)) >> HostPageBits;
|
||||||
|
auto val = committed_pages[page >> 6].load(std::memory_order_acquire);
|
||||||
|
return (val >> (page & 63)) & 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void CommitPage(std::size_t index) noexcept {
|
||||||
|
auto page_index = (index * sizeof(T)) >> HostPageBits;
|
||||||
|
auto page = reinterpret_cast<uintptr_t>(base_ptr + index) & HostPageMask;
|
||||||
|
#if defined(_WIN32)
|
||||||
|
CommitVectorPage(page, true);
|
||||||
|
#else
|
||||||
|
mprotect(reinterpret_cast<void*>(page), HostPageSize, PROT_READ | PROT_WRITE);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
committed_pages[page_index >> 6].fetch_or(1ULL << (page_index & 63), std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void DecommitPage(std::size_t index) noexcept {
|
||||||
|
auto page_index = (index * sizeof(T)) >> HostPageBits;
|
||||||
|
auto page = reinterpret_cast<uintptr_t>(base_ptr + index) & HostPageMask;
|
||||||
|
|
||||||
|
committed_pages[page_index >> 6].fetch_and(~(1ULL << (page_index & 63)), std::memory_order_release);
|
||||||
|
DecommitVectorPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t alloc_size{};
|
||||||
|
T* base_ptr{};
|
||||||
|
|
||||||
|
std::vector<std::atomic<u64>> committed_pages{};
|
||||||
|
#ifdef _WIN32
|
||||||
|
const std::array<u8, sizeof(T)> default_val{};
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#else
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "common/assert.h"
|
|
||||||
#include "common/virtual_buffer.h"
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
void* AllocateMemoryPages(std::size_t size) noexcept {
|
|
||||||
#ifdef _WIN32
|
|
||||||
void* base = VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
|
||||||
if (base == nullptr) {
|
|
||||||
// Probably failing to reserve is less likely than failing to commit
|
|
||||||
base = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
void* base = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
|
|
||||||
if (base == MAP_FAILED)
|
|
||||||
base = nullptr;
|
|
||||||
#endif
|
|
||||||
ASSERT(base);
|
|
||||||
return base;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept {
|
|
||||||
if (!base)
|
|
||||||
return;
|
|
||||||
#ifdef _WIN32
|
|
||||||
ASSERT(VirtualFree(base, 0, MEM_RELEASE));
|
|
||||||
#else
|
|
||||||
ASSERT(munmap(base, size) == 0);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Common
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
void* AllocateMemoryPages(std::size_t size) noexcept;
|
|
||||||
void FreeMemoryPages(void* base, std::size_t size) noexcept;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class VirtualBuffer final {
|
|
||||||
public:
|
|
||||||
// TODO: Uncomment this and change Common::PageTable::PageInfo to be trivially constructible
|
|
||||||
// using std::atomic_ref once libc++ has support for it
|
|
||||||
// static_assert(
|
|
||||||
// std::is_trivially_constructible_v<T>,
|
|
||||||
// "T must be trivially constructible, as non-trivial constructors will not be executed "
|
|
||||||
// "with the current allocator");
|
|
||||||
|
|
||||||
constexpr VirtualBuffer() = default;
|
|
||||||
explicit VirtualBuffer(std::size_t count) noexcept
|
|
||||||
: alloc_size{count * sizeof(T)}
|
|
||||||
{
|
|
||||||
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
|
|
||||||
}
|
|
||||||
|
|
||||||
~VirtualBuffer() noexcept {
|
|
||||||
FreeMemoryPages(base_ptr, alloc_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualBuffer(const VirtualBuffer&) = delete;
|
|
||||||
VirtualBuffer& operator=(const VirtualBuffer&) = delete;
|
|
||||||
|
|
||||||
VirtualBuffer(VirtualBuffer&& other) noexcept
|
|
||||||
: alloc_size{std::exchange(other.alloc_size, 0)}
|
|
||||||
, base_ptr{std::exchange(other.base_ptr, nullptr)}
|
|
||||||
{}
|
|
||||||
|
|
||||||
VirtualBuffer& operator=(VirtualBuffer&& other) noexcept {
|
|
||||||
alloc_size = std::exchange(other.alloc_size, 0);
|
|
||||||
base_ptr = std::exchange(other.base_ptr, nullptr);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void resize(std::size_t count) noexcept {
|
|
||||||
if (auto const new_size = count * sizeof(T); new_size != alloc_size) {
|
|
||||||
FreeMemoryPages(base_ptr, alloc_size);
|
|
||||||
alloc_size = new_size;
|
|
||||||
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr const T& operator[](std::size_t index) const noexcept {
|
|
||||||
return base_ptr[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr T& operator[](std::size_t index) noexcept {
|
|
||||||
return base_ptr[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr T* data() noexcept {
|
|
||||||
return base_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr const T* data() const noexcept {
|
|
||||||
return base_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr std::size_t size() const noexcept {
|
|
||||||
return alloc_size / sizeof(T);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::size_t alloc_size{};
|
|
||||||
T* base_ptr{};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Common
|
|
||||||
@@ -23,6 +23,8 @@ add_library(core STATIC
|
|||||||
core_timing.h
|
core_timing.h
|
||||||
cpu_manager.cpp
|
cpu_manager.cpp
|
||||||
cpu_manager.h
|
cpu_manager.h
|
||||||
|
launch_params.cpp
|
||||||
|
launch_params.h
|
||||||
crypto/aes_util.cpp
|
crypto/aes_util.cpp
|
||||||
crypto/aes_util.h
|
crypto/aes_util.h
|
||||||
crypto/ctr_encryption_layer.cpp
|
crypto/ctr_encryption_layer.cpp
|
||||||
@@ -1203,6 +1205,9 @@ endif()
|
|||||||
|
|
||||||
target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS})
|
target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS})
|
||||||
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
|
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
|
||||||
|
if (MSVC)
|
||||||
|
target_link_libraries(core PRIVATE getopt)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (BOOST_NO_HEADERS)
|
if (BOOST_NO_HEADERS)
|
||||||
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc)
|
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc)
|
||||||
|
|||||||
@@ -156,12 +156,12 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) {
|
|||||||
if (page_table) {
|
if (page_table) {
|
||||||
constexpr size_t PageBits = 12;
|
constexpr size_t PageBits = 12;
|
||||||
constexpr size_t NumPageTableEntries = 1 << (32 - PageBits);
|
constexpr size_t NumPageTableEntries = 1 << (32 - PageBits);
|
||||||
constexpr size_t PageLog2Stride = 5;
|
|
||||||
static_assert(1 << PageLog2Stride == sizeof(Common::PageTable::PageEntryData));
|
|
||||||
|
|
||||||
config.page_table = reinterpret_cast<std::array<std::uint8_t*, NumPageTableEntries>*>(page_table->entries.data());
|
// Dynarmic will not write to the page table, const_cast is safe here
|
||||||
config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS;
|
config.page_table = reinterpret_cast<std::array<std::uint8_t*, NumPageTableEntries>*>(
|
||||||
config.page_table_log2_stride = PageLog2Stride;
|
const_cast<Common::PageTable::PageEntryData*>(page_table->entries.data()));
|
||||||
|
config.page_table_pointer_mask = Common::PageTable::ATTRIBUTE_MASK;
|
||||||
|
config.page_table_marked_bit = uint8_t(0);
|
||||||
config.absolute_offset_page_table = true;
|
config.absolute_offset_page_table = true;
|
||||||
config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
|
config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
|
||||||
config.only_detect_misalignment_via_page_table_on_page_boundary = true;
|
config.only_detect_misalignment_via_page_table_on_page_boundary = true;
|
||||||
@@ -172,6 +172,13 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) {
|
|||||||
|
|
||||||
config.fastmem_exclusive_access = config.fastmem_pointer != std::nullopt;
|
config.fastmem_exclusive_access = config.fastmem_pointer != std::nullopt;
|
||||||
config.recompile_on_exclusive_fastmem_failure = true;
|
config.recompile_on_exclusive_fastmem_failure = true;
|
||||||
|
|
||||||
|
if (reinterpret_cast<u64>(m_system.DeviceMemory().buffer.BackingBasePointer() +
|
||||||
|
Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize()) < (1ULL << 39)) {
|
||||||
|
// Systems like FreeBSD allocate memory really low by default, and since we pack our page table entries,
|
||||||
|
// we have to manually sign extend when our actual pointer is negative.
|
||||||
|
config.page_table_sign_extension = std::uint8_t(Common::PageTable::SIGN_BIT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi-process state
|
// Multi-process state
|
||||||
@@ -404,6 +411,7 @@ void ArmDynarmic32::SignalInterrupt(Kernel::KThread* thread) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ArmDynarmic32::ClearInstructionCache() {
|
void ArmDynarmic32::ClearInstructionCache() {
|
||||||
|
m_cb->last_code_addr = u64(-1);
|
||||||
m_jit->ClearCache();
|
m_jit->ClearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,13 +197,12 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s
|
|||||||
|
|
||||||
// Memory
|
// Memory
|
||||||
if (page_table) {
|
if (page_table) {
|
||||||
constexpr size_t PageLog2Stride = 5;
|
// Dynarmic will not write to the page table, const_cast is safe here
|
||||||
static_assert(1 << PageLog2Stride == sizeof(Common::PageTable::PageEntryData));
|
config.page_table = reinterpret_cast<void**>(
|
||||||
|
const_cast<Common::PageTable::PageEntryData*>(page_table->entries.data()));
|
||||||
config.page_table = reinterpret_cast<void**>(page_table->entries.data());
|
|
||||||
config.page_table_address_space_bits = std::uint32_t(address_space_bits);
|
config.page_table_address_space_bits = std::uint32_t(address_space_bits);
|
||||||
config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS;
|
config.page_table_pointer_mask = Common::PageTable::ATTRIBUTE_MASK;
|
||||||
config.page_table_log2_stride = PageLog2Stride;
|
config.page_table_marked_bit = uint8_t(0);
|
||||||
config.silently_mirror_page_table = false;
|
config.silently_mirror_page_table = false;
|
||||||
config.absolute_offset_page_table = true;
|
config.absolute_offset_page_table = true;
|
||||||
config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
|
config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
|
||||||
@@ -217,6 +216,13 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s
|
|||||||
|
|
||||||
config.fastmem_exclusive_access = config.fastmem_pointer != std::nullopt;
|
config.fastmem_exclusive_access = config.fastmem_pointer != std::nullopt;
|
||||||
config.recompile_on_exclusive_fastmem_failure = true;
|
config.recompile_on_exclusive_fastmem_failure = true;
|
||||||
|
|
||||||
|
if (reinterpret_cast<u64>(m_system.DeviceMemory().buffer.BackingBasePointer() +
|
||||||
|
Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize()) < (1ULL << 39)) {
|
||||||
|
// Systems like FreeBSD allocate memory really low by default, and since we pack our page table entries,
|
||||||
|
// we have to manually sign extend when our actual pointer is negative.
|
||||||
|
config.page_table_sign_extension = std::uint8_t(Common::PageTable::SIGN_BIT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi-process state
|
// Multi-process state
|
||||||
@@ -433,6 +439,7 @@ void ArmDynarmic64::SignalInterrupt(Kernel::KThread* thread) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ArmDynarmic64::ClearInstructionCache() {
|
void ArmDynarmic64::ClearInstructionCache() {
|
||||||
|
m_cb->last_code_addr = u64(-1);
|
||||||
m_jit->ClearCache();
|
m_jit->ClearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -26,8 +29,11 @@ public:
|
|||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
Common::PhysicalAddress GetPhysicalAddr(const T* ptr) const {
|
Common::PhysicalAddress GetPhysicalAddr(const T* ptr) const {
|
||||||
return (reinterpret_cast<uintptr_t>(ptr) -
|
return GetPhysicalAddr(reinterpret_cast<uintptr_t>(ptr));
|
||||||
reinterpret_cast<uintptr_t>(buffer.BackingBasePointer())) +
|
}
|
||||||
|
|
||||||
|
Common::PhysicalAddress GetPhysicalAddr(uintptr_t ptr) const {
|
||||||
|
return (ptr - reinterpret_cast<uintptr_t>(buffer.BackingBasePointer())) +
|
||||||
DramMemoryMap::Base;
|
DramMemoryMap::Base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/range_mutex.h"
|
#include "common/range_mutex.h"
|
||||||
#include "common/scratch_buffer.h"
|
#include "common/scratch_buffer.h"
|
||||||
#include "common/virtual_buffer.h"
|
#include "common/sparse_large_vector.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
@@ -178,8 +178,8 @@ private:
|
|||||||
u32 continuity_tracker;
|
u32 continuity_tracker;
|
||||||
u32 compressed_physical_ptr;
|
u32 compressed_physical_ptr;
|
||||||
};
|
};
|
||||||
Common::VirtualBuffer<u32> compressed_device_addr;
|
Common::SparseLargeVector<u32> compressed_device_addr;
|
||||||
Common::VirtualBuffer<TrackedEntry> tracked_entries;
|
Common::SparseLargeVector<TrackedEntry> tracked_entries;
|
||||||
|
|
||||||
// Process memory interfaces
|
// Process memory interfaces
|
||||||
|
|
||||||
@@ -200,8 +200,8 @@ private:
|
|||||||
return std::make_pair(asid, address);
|
return std::make_pair(asid, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InsertCPUBacking(size_t page_index, VAddr address, Asid asid) {
|
constexpr void InsertCPUBacking(size_t page_index, VAddr address, Asid asid) {
|
||||||
tracked_entries[page_index].cpu_backing_address = address | (asid.id << asid_start_bit);
|
tracked_entries.GetUnchecked(page_index).cpu_backing_address = address | (asid.id << asid_start_bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<TranslationEntry, 4> t_slot{};
|
std::array<TranslationEntry, 4> t_slot{};
|
||||||
|
|||||||
@@ -177,17 +177,6 @@ DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memo
|
|||||||
{
|
{
|
||||||
impl = std::make_unique<DeviceMemoryManagerAllocator<Traits>>();
|
impl = std::make_unique<DeviceMemoryManagerAllocator<Traits>>();
|
||||||
cached_pages = std::make_unique<CachedPages>();
|
cached_pages = std::make_unique<CachedPages>();
|
||||||
|
|
||||||
const size_t total_virtual = device_as_size >> Memory::YUZU_PAGEBITS;
|
|
||||||
for (size_t i = 0; i < total_virtual; i++) {
|
|
||||||
tracked_entries[i].compressed_physical_ptr = 0;
|
|
||||||
tracked_entries[i].continuity_tracker = 1;
|
|
||||||
tracked_entries[i].cpu_backing_address = 0;
|
|
||||||
}
|
|
||||||
const size_t total_phys = 1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS);
|
|
||||||
for (size_t i = 0; i < total_phys; i++) {
|
|
||||||
compressed_device_addr[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
@@ -220,26 +209,28 @@ void DeviceMemoryManager<Traits>::Map(DAddr address, VAddr virtual_address, size
|
|||||||
size_t start_page_d = address >> Memory::YUZU_PAGEBITS;
|
size_t start_page_d = address >> Memory::YUZU_PAGEBITS;
|
||||||
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
|
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
|
||||||
std::scoped_lock lk(mapping_guard);
|
std::scoped_lock lk(mapping_guard);
|
||||||
|
|
||||||
|
tracked_entries.CommitRegion(start_page_d, start_page_d + num_pages);
|
||||||
for (size_t i = 0; i < num_pages; i++) {
|
for (size_t i = 0; i < num_pages; i++) {
|
||||||
const VAddr new_vaddress = virtual_address + i * Memory::YUZU_PAGESIZE;
|
const VAddr new_vaddress = virtual_address + i * Memory::YUZU_PAGESIZE;
|
||||||
auto* ptr = process_memory->GetPointerSilent(Common::ProcessAddress(new_vaddress));
|
auto* ptr = process_memory->GetPointerSilent(Common::ProcessAddress(new_vaddress));
|
||||||
if (ptr == nullptr) [[unlikely]] {
|
if (ptr == nullptr) [[unlikely]] {
|
||||||
tracked_entries[start_page_d + i].compressed_physical_ptr = 0;
|
tracked_entries.GetUnchecked(start_page_d + i).compressed_physical_ptr = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto phys_addr = static_cast<u32>(GetRawPhysicalAddr(ptr) >> Memory::YUZU_PAGEBITS) + 1U;
|
auto phys_addr = static_cast<u32>(GetRawPhysicalAddr(ptr) >> Memory::YUZU_PAGEBITS) + 1U;
|
||||||
tracked_entries[start_page_d + i].compressed_physical_ptr = phys_addr;
|
tracked_entries.GetUnchecked(start_page_d + i).compressed_physical_ptr = phys_addr;
|
||||||
InsertCPUBacking(start_page_d + i, new_vaddress, asid);
|
InsertCPUBacking(start_page_d + i, new_vaddress, asid);
|
||||||
const u32 base_dev = compressed_device_addr[phys_addr - 1U];
|
const u32 base_dev = compressed_device_addr[phys_addr - 1U];
|
||||||
const u32 new_dev = static_cast<u32>(start_page_d + i);
|
const u32 new_dev = static_cast<u32>(start_page_d + i);
|
||||||
if (base_dev == 0) [[likely]] {
|
if (base_dev == 0) [[likely]] {
|
||||||
compressed_device_addr[phys_addr - 1U] = new_dev;
|
compressed_device_addr.GetAndFault(phys_addr - 1U) = new_dev;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
u32 start_id = base_dev & MULTI_MASK;
|
u32 start_id = base_dev & MULTI_MASK;
|
||||||
if ((base_dev >> MULTI_FLAG_BITS) == 0) {
|
if ((base_dev >> MULTI_FLAG_BITS) == 0) {
|
||||||
start_id = impl->multi_dev_address.Register(base_dev);
|
start_id = impl->multi_dev_address.Register(base_dev);
|
||||||
compressed_device_addr[phys_addr - 1U] = MULTI_FLAG | start_id;
|
compressed_device_addr.GetAndFault(phys_addr - 1U) = MULTI_FLAG | start_id;
|
||||||
}
|
}
|
||||||
impl->multi_dev_address.Register(new_dev, start_id);
|
impl->multi_dev_address.Register(new_dev, start_id);
|
||||||
}
|
}
|
||||||
@@ -255,24 +246,26 @@ void DeviceMemoryManager<Traits>::Unmap(DAddr address, size_t size) {
|
|||||||
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
|
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
|
||||||
device_inter->InvalidateRegion(address, size);
|
device_inter->InvalidateRegion(address, size);
|
||||||
std::scoped_lock lk(mapping_guard);
|
std::scoped_lock lk(mapping_guard);
|
||||||
|
|
||||||
|
tracked_entries.CommitRegion(start_page_d, start_page_d + num_pages); // should already be committed, but just in case
|
||||||
for (size_t i = 0; i < num_pages; i++) {
|
for (size_t i = 0; i < num_pages; i++) {
|
||||||
auto phys_addr = tracked_entries[start_page_d + i].compressed_physical_ptr;
|
auto& entry = tracked_entries.GetUnchecked(start_page_d + i);
|
||||||
tracked_entries[start_page_d + i].compressed_physical_ptr = 0;
|
auto phys_addr = entry.compressed_physical_ptr;
|
||||||
tracked_entries[start_page_d + i].cpu_backing_address = 0;
|
entry.compressed_physical_ptr = 0;
|
||||||
|
entry.cpu_backing_address = 0;
|
||||||
if (phys_addr != 0) [[likely]] {
|
if (phys_addr != 0) [[likely]] {
|
||||||
const u32 base_dev = compressed_device_addr[phys_addr - 1U];
|
u32& base_dev = compressed_device_addr.GetAndFault(phys_addr - 1U);
|
||||||
if ((base_dev >> MULTI_FLAG_BITS) == 0) [[likely]] {
|
if ((base_dev >> MULTI_FLAG_BITS) == 0) [[likely]] {
|
||||||
compressed_device_addr[phys_addr - 1] = 0;
|
base_dev = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto [more_entries, new_start] = impl->multi_dev_address.Unregister(
|
const auto [more_entries, new_start] = impl->multi_dev_address.Unregister(
|
||||||
static_cast<u32>(start_page_d + i), base_dev & MULTI_MASK);
|
static_cast<u32>(start_page_d + i), base_dev & MULTI_MASK);
|
||||||
if (!more_entries) {
|
if (!more_entries) {
|
||||||
compressed_device_addr[phys_addr - 1] =
|
base_dev = impl->multi_dev_address.ReleaseEntry(new_start);
|
||||||
impl->multi_dev_address.ReleaseEntry(new_start);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
compressed_device_addr[phys_addr - 1] = new_start | MULTI_FLAG;
|
base_dev = new_start | MULTI_FLAG;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t_slot = {};
|
t_slot = {};
|
||||||
@@ -285,6 +278,8 @@ void DeviceMemoryManager<Traits>::TrackContinuityImpl(DAddr address, VAddr virtu
|
|||||||
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
|
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
|
||||||
uintptr_t last_ptr = 0;
|
uintptr_t last_ptr = 0;
|
||||||
size_t page_count = 1;
|
size_t page_count = 1;
|
||||||
|
|
||||||
|
tracked_entries.CommitRegion(start_page_d, start_page_d + num_pages);
|
||||||
for (size_t i = num_pages; i > 0; i--) {
|
for (size_t i = num_pages; i > 0; i--) {
|
||||||
size_t index = i - 1;
|
size_t index = i - 1;
|
||||||
const VAddr new_vaddress = virtual_address + index * Memory::YUZU_PAGESIZE;
|
const VAddr new_vaddress = virtual_address + index * Memory::YUZU_PAGESIZE;
|
||||||
@@ -296,14 +291,14 @@ void DeviceMemoryManager<Traits>::TrackContinuityImpl(DAddr address, VAddr virtu
|
|||||||
page_count = 1;
|
page_count = 1;
|
||||||
}
|
}
|
||||||
last_ptr = new_ptr;
|
last_ptr = new_ptr;
|
||||||
tracked_entries[start_page_d + index].continuity_tracker = static_cast<u32>(page_count);
|
tracked_entries.GetUnchecked(start_page_d + index).continuity_tracker = static_cast<u32>(page_count) - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
u8* DeviceMemoryManager<Traits>::GetSpan(const DAddr src_addr, const std::size_t size) {
|
u8* DeviceMemoryManager<Traits>::GetSpan(const DAddr src_addr, const std::size_t size) {
|
||||||
size_t page_index = src_addr >> page_bits;
|
size_t page_index = src_addr >> page_bits;
|
||||||
size_t subbits = src_addr & page_mask;
|
size_t subbits = src_addr & page_mask;
|
||||||
if ((static_cast<size_t>(tracked_entries[page_index].continuity_tracker) << page_bits) >= size + subbits) {
|
if ((static_cast<size_t>(tracked_entries[page_index].continuity_tracker+1) << page_bits) >= size + subbits) {
|
||||||
return GetPointer<u8>(src_addr);
|
return GetPointer<u8>(src_addr);
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -313,7 +308,7 @@ template <typename Traits>
|
|||||||
const u8* DeviceMemoryManager<Traits>::GetSpan(const DAddr src_addr, const std::size_t size) const {
|
const u8* DeviceMemoryManager<Traits>::GetSpan(const DAddr src_addr, const std::size_t size) const {
|
||||||
size_t page_index = src_addr >> page_bits;
|
size_t page_index = src_addr >> page_bits;
|
||||||
size_t subbits = src_addr & page_mask;
|
size_t subbits = src_addr & page_mask;
|
||||||
if ((static_cast<size_t>(tracked_entries[page_index].continuity_tracker) << page_bits) >= size + subbits) {
|
if ((static_cast<size_t>(tracked_entries[page_index].continuity_tracker+1) << page_bits) >= size + subbits) {
|
||||||
return GetPointer<u8>(src_addr);
|
return GetPointer<u8>(src_addr);
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -383,7 +378,7 @@ void DeviceMemoryManager<Traits>::WalkBlock(DAddr addr, std::size_t size, auto o
|
|||||||
std::size_t page_index = addr >> Memory::YUZU_PAGEBITS;
|
std::size_t page_index = addr >> Memory::YUZU_PAGEBITS;
|
||||||
std::size_t page_offset = addr & Memory::YUZU_PAGEMASK;
|
std::size_t page_offset = addr & Memory::YUZU_PAGEMASK;
|
||||||
while (remaining_size) {
|
while (remaining_size) {
|
||||||
const size_t next_pages = std::size_t(tracked_entries[page_index].continuity_tracker);
|
const size_t next_pages = std::size_t(tracked_entries[page_index].continuity_tracker+1);
|
||||||
const std::size_t copy_amount = (std::min)((next_pages << Memory::YUZU_PAGEBITS) - page_offset, remaining_size);
|
const std::size_t copy_amount = (std::min)((next_pages << Memory::YUZU_PAGEBITS) - page_offset, remaining_size);
|
||||||
const auto current_vaddr = u64((page_index << Memory::YUZU_PAGEBITS) + page_offset);
|
const auto current_vaddr = u64((page_index << Memory::YUZU_PAGEBITS) + page_offset);
|
||||||
SCOPE_EXIT{
|
SCOPE_EXIT{
|
||||||
|
|||||||
@@ -35,19 +35,22 @@ static IPSFileType IdentifyMagic(std::span<const u8> magic) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool IsEOF(IPSFileType type, std::span<const u8> magic) {
|
static bool IsEOF(IPSFileType type, std::span<const u8> magic) {
|
||||||
return (type == IPSFileType::IPS && magic.size() > 3 && std::memcmp(magic.data(), "EOF", 3) == 0)
|
return (type == IPSFileType::IPS && magic.size() >= 3 && std::memcmp(magic.data(), "EOF", 3) == 0)
|
||||||
|| (type == IPSFileType::IPS32 && magic.size() > 4 && std::memcmp(magic.data(), "EEOF", 4) == 0);
|
|| (type == IPSFileType::IPS32 && magic.size() >= 4 && std::memcmp(magic.data(), "EEOF", 4) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||||
if (in == nullptr || ips == nullptr)
|
if (in == nullptr || ips == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
auto in_data = in->ReadAllBytes();
|
const auto type = IdentifyMagic(ips->ReadBytes(0x5));
|
||||||
auto const type = IdentifyMagic(in_data);
|
|
||||||
if (type == IPSFileType::Error)
|
if (type == IPSFileType::Error)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
|
auto in_data = in->ReadAllBytes();
|
||||||
|
if (in_data.size() == 0)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
|
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
|
||||||
u64 offset = 5; // After header
|
u64 offset = 5; // After header
|
||||||
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
|
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
|
||||||
|
|||||||
@@ -635,6 +635,36 @@ Result KPageTableBase::CheckMemoryState(const KMemoryInfo& info, KMemoryState st
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool KPageTableBase::BeginTraversal(const Common::PageTable &impl, TraversalEntry *out_entry, TraversalContext *out_context,
|
||||||
|
Common::ProcessAddress address) const {
|
||||||
|
out_context->next_offset = GetInteger(address);
|
||||||
|
out_context->next_page = GetInteger(address) >> PageBits;
|
||||||
|
|
||||||
|
return ContinueTraversal(impl, out_entry, out_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KPageTableBase::ContinueTraversal(const Common::PageTable &impl, TraversalEntry *out_entry,
|
||||||
|
TraversalContext *context) const {
|
||||||
|
// Setup invalid defaults.
|
||||||
|
out_entry->phys_addr = 0;
|
||||||
|
out_entry->block_size = PageSize;
|
||||||
|
// Validate that we can read the actual entry.
|
||||||
|
if (auto const page = context->next_page; page < impl.entries.size()) {
|
||||||
|
// Validate that the entry is mapped.
|
||||||
|
if (auto const paddr = impl.entries[page].Pointer(true); paddr != 0) {
|
||||||
|
// Populate the results and return true
|
||||||
|
out_entry->phys_addr = GetInteger(m_system.DeviceMemory().GetPhysicalAddr(paddr + context->next_offset));
|
||||||
|
context->next_page += 1;
|
||||||
|
context->next_offset += PageSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context->next_page += 1;
|
||||||
|
context->next_offset += PageSize;
|
||||||
|
// Otherwise return false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Result KPageTableBase::CheckMemoryStateContiguous(size_t* out_blocks_needed, KProcessAddress addr,
|
Result KPageTableBase::CheckMemoryStateContiguous(size_t* out_blocks_needed, KProcessAddress addr,
|
||||||
size_t size, KMemoryState state_mask,
|
size_t size, KMemoryState state_mask,
|
||||||
KMemoryState state, KMemoryPermission perm_mask,
|
KMemoryState state, KMemoryPermission perm_mask,
|
||||||
@@ -940,7 +970,7 @@ Result KPageTableBase::QueryMappingImpl(KProcessAddress* out, KPhysicalAddress a
|
|||||||
size_t tot_size = 0;
|
size_t tot_size = 0;
|
||||||
|
|
||||||
next_valid =
|
next_valid =
|
||||||
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), region_start);
|
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), region_start);
|
||||||
next_entry.block_size =
|
next_entry.block_size =
|
||||||
(next_entry.block_size - (GetInteger(region_start) & (next_entry.block_size - 1)));
|
(next_entry.block_size - (GetInteger(region_start) & (next_entry.block_size - 1)));
|
||||||
|
|
||||||
@@ -976,7 +1006,7 @@ Result KPageTableBase::QueryMappingImpl(KProcessAddress* out, KPhysicalAddress a
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
next_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
next_valid = ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the last entry.
|
// Check the last entry.
|
||||||
@@ -1754,7 +1784,7 @@ Result KPageTableBase::MakePageGroup(KPageGroup& pg, KProcessAddress addr, size_
|
|||||||
// Begin traversal.
|
// Begin traversal.
|
||||||
TraversalContext context;
|
TraversalContext context;
|
||||||
TraversalEntry next_entry;
|
TraversalEntry next_entry;
|
||||||
R_UNLESS(impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), addr),
|
R_UNLESS(BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), addr),
|
||||||
ResultInvalidCurrentMemory);
|
ResultInvalidCurrentMemory);
|
||||||
|
|
||||||
// Prepare tracking variables.
|
// Prepare tracking variables.
|
||||||
@@ -1764,7 +1794,7 @@ Result KPageTableBase::MakePageGroup(KPageGroup& pg, KProcessAddress addr, size_
|
|||||||
|
|
||||||
// Iterate, adding to group as we go.
|
// Iterate, adding to group as we go.
|
||||||
while (tot_size < size) {
|
while (tot_size < size) {
|
||||||
R_UNLESS(impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)),
|
R_UNLESS(ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context)),
|
||||||
ResultInvalidCurrentMemory);
|
ResultInvalidCurrentMemory);
|
||||||
|
|
||||||
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
||||||
@@ -1828,7 +1858,7 @@ bool KPageTableBase::IsValidPageGroup(const KPageGroup& pg, KProcessAddress addr
|
|||||||
// Begin traversal.
|
// Begin traversal.
|
||||||
TraversalContext context;
|
TraversalContext context;
|
||||||
TraversalEntry next_entry;
|
TraversalEntry next_entry;
|
||||||
if (!impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), addr)) {
|
if (!BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), addr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1839,7 +1869,7 @@ bool KPageTableBase::IsValidPageGroup(const KPageGroup& pg, KProcessAddress addr
|
|||||||
|
|
||||||
// Iterate, comparing expected to actual.
|
// Iterate, comparing expected to actual.
|
||||||
while (tot_size < size) {
|
while (tot_size < size) {
|
||||||
if (!impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context))) {
|
if (!ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1896,7 +1926,7 @@ Result KPageTableBase::GetContiguousMemoryRangeWithState(
|
|||||||
// Begin a traversal.
|
// Begin a traversal.
|
||||||
TraversalContext context;
|
TraversalContext context;
|
||||||
TraversalEntry cur_entry = {.phys_addr = 0, .block_size = 0};
|
TraversalEntry cur_entry = {.phys_addr = 0, .block_size = 0};
|
||||||
R_UNLESS(impl.BeginTraversal(std::addressof(cur_entry), std::addressof(context), address),
|
R_UNLESS(BeginTraversal(impl, std::addressof(cur_entry), std::addressof(context), address),
|
||||||
ResultInvalidCurrentMemory);
|
ResultInvalidCurrentMemory);
|
||||||
|
|
||||||
// Traverse until we have enough size or we aren't contiguous any more.
|
// Traverse until we have enough size or we aren't contiguous any more.
|
||||||
@@ -1905,7 +1935,7 @@ Result KPageTableBase::GetContiguousMemoryRangeWithState(
|
|||||||
for (contig_size =
|
for (contig_size =
|
||||||
cur_entry.block_size - (GetInteger(phys_address) & (cur_entry.block_size - 1));
|
cur_entry.block_size - (GetInteger(phys_address) & (cur_entry.block_size - 1));
|
||||||
contig_size < size; contig_size += cur_entry.block_size) {
|
contig_size < size; contig_size += cur_entry.block_size) {
|
||||||
if (!impl.ContinueTraversal(std::addressof(cur_entry), std::addressof(context))) {
|
if (!ContinueTraversal(impl, std::addressof(cur_entry), std::addressof(context))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (cur_entry.phys_addr != phys_address + contig_size) {
|
if (cur_entry.phys_addr != phys_address + contig_size) {
|
||||||
@@ -2334,7 +2364,7 @@ Result KPageTableBase::QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out,
|
|||||||
TraversalContext context;
|
TraversalContext context;
|
||||||
TraversalEntry next_entry;
|
TraversalEntry next_entry;
|
||||||
bool traverse_valid =
|
bool traverse_valid =
|
||||||
m_impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), virt_addr);
|
BeginTraversal(m_impl, std::addressof(next_entry), std::addressof(context), virt_addr);
|
||||||
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
|
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
|
||||||
|
|
||||||
// Set tracking variables.
|
// Set tracking variables.
|
||||||
@@ -2345,7 +2375,7 @@ Result KPageTableBase::QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out,
|
|||||||
while (true) {
|
while (true) {
|
||||||
// Continue the traversal.
|
// Continue the traversal.
|
||||||
traverse_valid =
|
traverse_valid =
|
||||||
m_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
ContinueTraversal(m_impl, std::addressof(next_entry), std::addressof(context));
|
||||||
if (!traverse_valid) {
|
if (!traverse_valid) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2567,7 +2597,7 @@ Result KPageTableBase::UnmapIoRegion(KProcessAddress dst_address, KPhysicalAddre
|
|||||||
TraversalContext context;
|
TraversalContext context;
|
||||||
TraversalEntry next_entry;
|
TraversalEntry next_entry;
|
||||||
ASSERT(
|
ASSERT(
|
||||||
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_address));
|
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), dst_address));
|
||||||
|
|
||||||
// Check that the physical region matches.
|
// Check that the physical region matches.
|
||||||
R_UNLESS(next_entry.phys_addr == phys_addr, ResultInvalidMemoryRegion);
|
R_UNLESS(next_entry.phys_addr == phys_addr, ResultInvalidMemoryRegion);
|
||||||
@@ -2577,7 +2607,7 @@ Result KPageTableBase::UnmapIoRegion(KProcessAddress dst_address, KPhysicalAddre
|
|||||||
next_entry.block_size - (GetInteger(phys_addr) & (next_entry.block_size - 1));
|
next_entry.block_size - (GetInteger(phys_addr) & (next_entry.block_size - 1));
|
||||||
checked_size < size; checked_size += next_entry.block_size) {
|
checked_size < size; checked_size += next_entry.block_size) {
|
||||||
// Continue the traversal.
|
// Continue the traversal.
|
||||||
ASSERT(impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)));
|
ASSERT(ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context)));
|
||||||
|
|
||||||
// Check that the physical region matches.
|
// Check that the physical region matches.
|
||||||
R_UNLESS(next_entry.phys_addr == phys_addr + checked_size, ResultInvalidMemoryRegion);
|
R_UNLESS(next_entry.phys_addr == phys_addr + checked_size, ResultInvalidMemoryRegion);
|
||||||
@@ -3029,7 +3059,7 @@ Result KPageTableBase::InvalidateProcessDataCache(KProcessAddress address, size_
|
|||||||
TraversalContext context;
|
TraversalContext context;
|
||||||
TraversalEntry next_entry;
|
TraversalEntry next_entry;
|
||||||
bool traverse_valid =
|
bool traverse_valid =
|
||||||
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), address);
|
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), address);
|
||||||
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
|
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
|
||||||
|
|
||||||
// Prepare tracking variables.
|
// Prepare tracking variables.
|
||||||
@@ -3041,7 +3071,7 @@ Result KPageTableBase::InvalidateProcessDataCache(KProcessAddress address, size_
|
|||||||
while (tot_size < size) {
|
while (tot_size < size) {
|
||||||
// Continue the traversal.
|
// Continue the traversal.
|
||||||
traverse_valid =
|
traverse_valid =
|
||||||
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
|
||||||
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
|
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
|
||||||
|
|
||||||
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
||||||
@@ -3129,7 +3159,7 @@ Result KPageTableBase::ReadDebugMemory(KProcessAddress dst_address, KProcessAddr
|
|||||||
TraversalContext context;
|
TraversalContext context;
|
||||||
TraversalEntry next_entry;
|
TraversalEntry next_entry;
|
||||||
bool traverse_valid =
|
bool traverse_valid =
|
||||||
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_address);
|
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), src_address);
|
||||||
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
|
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
|
||||||
|
|
||||||
// Prepare tracking variables.
|
// Prepare tracking variables.
|
||||||
@@ -3167,7 +3197,7 @@ Result KPageTableBase::ReadDebugMemory(KProcessAddress dst_address, KProcessAddr
|
|||||||
while (tot_size < size) {
|
while (tot_size < size) {
|
||||||
// Continue the traversal.
|
// Continue the traversal.
|
||||||
traverse_valid =
|
traverse_valid =
|
||||||
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
||||||
@@ -3225,7 +3255,7 @@ Result KPageTableBase::WriteDebugMemory(KProcessAddress dst_address, KProcessAdd
|
|||||||
TraversalContext context;
|
TraversalContext context;
|
||||||
TraversalEntry next_entry;
|
TraversalEntry next_entry;
|
||||||
bool traverse_valid =
|
bool traverse_valid =
|
||||||
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_address);
|
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), dst_address);
|
||||||
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
|
R_UNLESS(traverse_valid, ResultInvalidCurrentMemory);
|
||||||
|
|
||||||
// Prepare tracking variables.
|
// Prepare tracking variables.
|
||||||
@@ -3267,7 +3297,7 @@ Result KPageTableBase::WriteDebugMemory(KProcessAddress dst_address, KProcessAdd
|
|||||||
while (tot_size < size) {
|
while (tot_size < size) {
|
||||||
// Continue the traversal.
|
// Continue the traversal.
|
||||||
traverse_valid =
|
traverse_valid =
|
||||||
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
||||||
@@ -3728,7 +3758,7 @@ Result KPageTableBase::CopyMemoryFromLinearToUser(
|
|||||||
TraversalContext context;
|
TraversalContext context;
|
||||||
TraversalEntry next_entry;
|
TraversalEntry next_entry;
|
||||||
bool traverse_valid =
|
bool traverse_valid =
|
||||||
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_addr);
|
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), src_addr);
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
// Prepare tracking variables.
|
// Prepare tracking variables.
|
||||||
@@ -3768,7 +3798,7 @@ Result KPageTableBase::CopyMemoryFromLinearToUser(
|
|||||||
while (tot_size < size) {
|
while (tot_size < size) {
|
||||||
// Continue the traversal.
|
// Continue the traversal.
|
||||||
traverse_valid =
|
traverse_valid =
|
||||||
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
||||||
@@ -3822,7 +3852,7 @@ Result KPageTableBase::CopyMemoryFromLinearToKernel(
|
|||||||
TraversalContext context;
|
TraversalContext context;
|
||||||
TraversalEntry next_entry;
|
TraversalEntry next_entry;
|
||||||
bool traverse_valid =
|
bool traverse_valid =
|
||||||
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_addr);
|
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), src_addr);
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
// Prepare tracking variables.
|
// Prepare tracking variables.
|
||||||
@@ -3845,7 +3875,7 @@ Result KPageTableBase::CopyMemoryFromLinearToKernel(
|
|||||||
while (tot_size < size) {
|
while (tot_size < size) {
|
||||||
// Continue the traversal.
|
// Continue the traversal.
|
||||||
traverse_valid =
|
traverse_valid =
|
||||||
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
||||||
@@ -3902,7 +3932,7 @@ Result KPageTableBase::CopyMemoryFromUserToLinear(
|
|||||||
TraversalContext context;
|
TraversalContext context;
|
||||||
TraversalEntry next_entry;
|
TraversalEntry next_entry;
|
||||||
bool traverse_valid =
|
bool traverse_valid =
|
||||||
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_addr);
|
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), dst_addr);
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
// Prepare tracking variables.
|
// Prepare tracking variables.
|
||||||
@@ -3941,7 +3971,7 @@ Result KPageTableBase::CopyMemoryFromUserToLinear(
|
|||||||
while (tot_size < size) {
|
while (tot_size < size) {
|
||||||
// Continue the traversal.
|
// Continue the traversal.
|
||||||
traverse_valid =
|
traverse_valid =
|
||||||
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
||||||
@@ -3997,7 +4027,7 @@ Result KPageTableBase::CopyMemoryFromKernelToLinear(KProcessAddress dst_addr, si
|
|||||||
TraversalContext context;
|
TraversalContext context;
|
||||||
TraversalEntry next_entry;
|
TraversalEntry next_entry;
|
||||||
bool traverse_valid =
|
bool traverse_valid =
|
||||||
impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_addr);
|
BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), dst_addr);
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
// Prepare tracking variables.
|
// Prepare tracking variables.
|
||||||
@@ -4020,7 +4050,7 @@ Result KPageTableBase::CopyMemoryFromKernelToLinear(KProcessAddress dst_addr, si
|
|||||||
while (tot_size < size) {
|
while (tot_size < size) {
|
||||||
// Continue the traversal.
|
// Continue the traversal.
|
||||||
traverse_valid =
|
traverse_valid =
|
||||||
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context));
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
if (next_entry.phys_addr != (cur_addr + cur_size)) {
|
||||||
@@ -4089,10 +4119,10 @@ Result KPageTableBase::CopyMemoryFromHeapToHeap(
|
|||||||
bool traverse_valid;
|
bool traverse_valid;
|
||||||
|
|
||||||
// Begin traversal.
|
// Begin traversal.
|
||||||
traverse_valid = src_impl.BeginTraversal(std::addressof(src_next_entry),
|
traverse_valid = BeginTraversal(src_impl, std::addressof(src_next_entry),
|
||||||
std::addressof(src_context), src_addr);
|
std::addressof(src_context), src_addr);
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
traverse_valid = dst_impl.BeginTraversal(std::addressof(dst_next_entry),
|
traverse_valid = BeginTraversal(dst_impl, std::addressof(dst_next_entry),
|
||||||
std::addressof(dst_context), dst_addr);
|
std::addressof(dst_context), dst_addr);
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
@@ -4127,7 +4157,7 @@ Result KPageTableBase::CopyMemoryFromHeapToHeap(
|
|||||||
if (ofs + cur_copy_size != size) {
|
if (ofs + cur_copy_size != size) {
|
||||||
if (cur_src_addr + cur_min_size == cur_src_block_addr + cur_src_size) {
|
if (cur_src_addr + cur_min_size == cur_src_block_addr + cur_src_size) {
|
||||||
// Continue the src traversal.
|
// Continue the src traversal.
|
||||||
traverse_valid = src_impl.ContinueTraversal(std::addressof(src_next_entry),
|
traverse_valid = ContinueTraversal(src_impl, std::addressof(src_next_entry),
|
||||||
std::addressof(src_context));
|
std::addressof(src_context));
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
@@ -4138,7 +4168,7 @@ Result KPageTableBase::CopyMemoryFromHeapToHeap(
|
|||||||
if (cur_dst_addr + cur_min_size ==
|
if (cur_dst_addr + cur_min_size ==
|
||||||
dst_next_entry.phys_addr + dst_next_entry.block_size) {
|
dst_next_entry.phys_addr + dst_next_entry.block_size) {
|
||||||
// Continue the dst traversal.
|
// Continue the dst traversal.
|
||||||
traverse_valid = dst_impl.ContinueTraversal(std::addressof(dst_next_entry),
|
traverse_valid = ContinueTraversal(dst_impl, std::addressof(dst_next_entry),
|
||||||
std::addressof(dst_context));
|
std::addressof(dst_context));
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
@@ -4223,10 +4253,10 @@ Result KPageTableBase::CopyMemoryFromHeapToHeapWithoutCheckDestination(
|
|||||||
bool traverse_valid;
|
bool traverse_valid;
|
||||||
|
|
||||||
// Begin traversal.
|
// Begin traversal.
|
||||||
traverse_valid = src_impl.BeginTraversal(std::addressof(src_next_entry),
|
traverse_valid = BeginTraversal(src_impl, std::addressof(src_next_entry),
|
||||||
std::addressof(src_context), src_addr);
|
std::addressof(src_context), src_addr);
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
traverse_valid = dst_impl.BeginTraversal(std::addressof(dst_next_entry),
|
traverse_valid = BeginTraversal(dst_impl, std::addressof(dst_next_entry),
|
||||||
std::addressof(dst_context), dst_addr);
|
std::addressof(dst_context), dst_addr);
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
@@ -4261,7 +4291,7 @@ Result KPageTableBase::CopyMemoryFromHeapToHeapWithoutCheckDestination(
|
|||||||
if (ofs + cur_copy_size != size) {
|
if (ofs + cur_copy_size != size) {
|
||||||
if (cur_src_addr + cur_min_size == cur_src_block_addr + cur_src_size) {
|
if (cur_src_addr + cur_min_size == cur_src_block_addr + cur_src_size) {
|
||||||
// Continue the src traversal.
|
// Continue the src traversal.
|
||||||
traverse_valid = src_impl.ContinueTraversal(std::addressof(src_next_entry),
|
traverse_valid = ContinueTraversal(src_impl, std::addressof(src_next_entry),
|
||||||
std::addressof(src_context));
|
std::addressof(src_context));
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
@@ -4272,7 +4302,7 @@ Result KPageTableBase::CopyMemoryFromHeapToHeapWithoutCheckDestination(
|
|||||||
if (cur_dst_addr + cur_min_size ==
|
if (cur_dst_addr + cur_min_size ==
|
||||||
dst_next_entry.phys_addr + dst_next_entry.block_size) {
|
dst_next_entry.phys_addr + dst_next_entry.block_size) {
|
||||||
// Continue the dst traversal.
|
// Continue the dst traversal.
|
||||||
traverse_valid = dst_impl.ContinueTraversal(std::addressof(dst_next_entry),
|
traverse_valid = ContinueTraversal(dst_impl, std::addressof(dst_next_entry),
|
||||||
std::addressof(dst_context));
|
std::addressof(dst_context));
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
@@ -4547,7 +4577,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size,
|
|||||||
// Begin traversal.
|
// Begin traversal.
|
||||||
TraversalContext context;
|
TraversalContext context;
|
||||||
TraversalEntry next_entry;
|
TraversalEntry next_entry;
|
||||||
bool traverse_valid = src_impl.BeginTraversal(std::addressof(next_entry),
|
bool traverse_valid = BeginTraversal(src_impl, std::addressof(next_entry),
|
||||||
std::addressof(context), aligned_src_start);
|
std::addressof(context), aligned_src_start);
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
@@ -4597,7 +4627,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size,
|
|||||||
// If the block's size was one page, we may need to continue traversal.
|
// If the block's size was one page, we may need to continue traversal.
|
||||||
if (cur_block_size == 0 && aligned_src_size > PageSize) {
|
if (cur_block_size == 0 && aligned_src_size > PageSize) {
|
||||||
traverse_valid =
|
traverse_valid =
|
||||||
src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
ContinueTraversal(src_impl, std::addressof(next_entry), std::addressof(context));
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
cur_block_addr = next_entry.phys_addr;
|
cur_block_addr = next_entry.phys_addr;
|
||||||
@@ -4610,7 +4640,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size,
|
|||||||
while (aligned_src_start + tot_block_size < mapping_src_end) {
|
while (aligned_src_start + tot_block_size < mapping_src_end) {
|
||||||
// Continue the traversal.
|
// Continue the traversal.
|
||||||
traverse_valid =
|
traverse_valid =
|
||||||
src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
ContinueTraversal(src_impl, std::addressof(next_entry), std::addressof(context));
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
// Process the block.
|
// Process the block.
|
||||||
@@ -4653,7 +4683,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size,
|
|||||||
if (mapped_block_end + cur_block_size < aligned_src_end &&
|
if (mapped_block_end + cur_block_size < aligned_src_end &&
|
||||||
cur_block_size == last_block_size) {
|
cur_block_size == last_block_size) {
|
||||||
traverse_valid =
|
traverse_valid =
|
||||||
src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
|
ContinueTraversal(src_impl, std::addressof(next_entry), std::addressof(context));
|
||||||
ASSERT(traverse_valid);
|
ASSERT(traverse_valid);
|
||||||
|
|
||||||
cur_block_addr = next_entry.phys_addr;
|
cur_block_addr = next_entry.phys_addr;
|
||||||
@@ -5601,7 +5631,7 @@ Result KPageTableBase::UnmapProcessMemory(KProcessAddress dst_address, size_t si
|
|||||||
ContiguousRangeInfo(KPageTableBase& pt, KProcessAddress address, size_t size)
|
ContiguousRangeInfo(KPageTableBase& pt, KProcessAddress address, size_t size)
|
||||||
: m_pt(pt), m_remaining_size(size) {
|
: m_pt(pt), m_remaining_size(size) {
|
||||||
// Begin a traversal.
|
// Begin a traversal.
|
||||||
ASSERT(m_pt.GetImpl().BeginTraversal(std::addressof(m_entry),
|
ASSERT(m_pt.BeginTraversal(m_pt.GetImpl(), std::addressof(m_entry),
|
||||||
std::addressof(m_context), address));
|
std::addressof(m_context), address));
|
||||||
|
|
||||||
// Setup tracking fields.
|
// Setup tracking fields.
|
||||||
@@ -5632,7 +5662,7 @@ Result KPageTableBase::UnmapProcessMemory(KProcessAddress dst_address, size_t si
|
|||||||
void DetermineContiguousBlockExtents() {
|
void DetermineContiguousBlockExtents() {
|
||||||
// Continue traversing until we're not contiguous, or we have enough.
|
// Continue traversing until we're not contiguous, or we have enough.
|
||||||
while (m_cur_size < m_remaining_size) {
|
while (m_cur_size < m_remaining_size) {
|
||||||
ASSERT(m_pt.GetImpl().ContinueTraversal(std::addressof(m_entry),
|
ASSERT(m_pt.ContinueTraversal(m_pt.GetImpl(), std::addressof(m_entry),
|
||||||
std::addressof(m_context)));
|
std::addressof(m_context)));
|
||||||
|
|
||||||
// If we're not contiguous, we're done.
|
// If we're not contiguous, we're done.
|
||||||
|
|||||||
@@ -370,6 +370,10 @@ private:
|
|||||||
size_t num_pages, size_t alignment, size_t offset,
|
size_t num_pages, size_t alignment, size_t offset,
|
||||||
size_t guard_pages) const;
|
size_t guard_pages) const;
|
||||||
|
|
||||||
|
bool BeginTraversal(const Common::PageTable& impl, TraversalEntry* out_entry, TraversalContext* out_context,
|
||||||
|
Common::ProcessAddress address) const;
|
||||||
|
bool ContinueTraversal(const Common::PageTable& impl, TraversalEntry* out_entry, TraversalContext* context) const;
|
||||||
|
|
||||||
Result CheckMemoryStateContiguous(size_t* out_blocks_needed, KProcessAddress addr, size_t size,
|
Result CheckMemoryStateContiguous(size_t* out_blocks_needed, KProcessAddress addr, size_t size,
|
||||||
KMemoryState state_mask, KMemoryState state,
|
KMemoryState state_mask, KMemoryState state,
|
||||||
KMemoryPermission perm_mask, KMemoryPermission perm,
|
KMemoryPermission perm_mask, KMemoryPermission perm,
|
||||||
@@ -474,7 +478,14 @@ private:
|
|||||||
// Validate pre-conditions.
|
// Validate pre-conditions.
|
||||||
ASSERT(this->IsLockedByCurrentThread());
|
ASSERT(this->IsLockedByCurrentThread());
|
||||||
|
|
||||||
return this->GetImpl().GetPhysicalAddress(out, virt_addr);
|
if (virt_addr > (1ULL << m_address_space_width)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = m_system.DeviceMemory().GetPhysicalAddr(
|
||||||
|
this->GetImpl().entries[GetInteger(virt_addr) >> PageBits].Pointer(true) + GetInteger(virt_addr));
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ void LoopProcess(Core::System& system) {
|
|||||||
server_manager->RegisterNamedService("audout:d", std::make_shared<IAudioOutManagerForDebugger>(system), 30);
|
server_manager->RegisterNamedService("audout:d", std::make_shared<IAudioOutManagerForDebugger>(system), 30);
|
||||||
server_manager->RegisterNamedService("audin:d", std::make_shared<IAudioInManagerForDebugger>(system), 30);
|
server_manager->RegisterNamedService("audin:d", std::make_shared<IAudioInManagerForDebugger>(system), 30);
|
||||||
server_manager->RegisterNamedService("audrec:d", std::make_shared<IFinalOutputRecorderManagerForDebugger>(system), 30);
|
server_manager->RegisterNamedService("audrec:d", std::make_shared<IFinalOutputRecorderManagerForDebugger>(system), 30);
|
||||||
server_manager->RegisterNamedService("audren:d", std::make_shared<IAudioInManager>(system), 30);
|
server_manager->RegisterNamedService("audren:d", std::make_shared<IAudioRendererManagerForDebugger>(system), 30);
|
||||||
|
|
||||||
server_manager->RegisterNamedService("audin:u", std::make_shared<IAudioInManager>(system), 30);
|
server_manager->RegisterNamedService("audin:u", std::make_shared<IAudioInManager>(system), 30);
|
||||||
server_manager->RegisterNamedService("audin:a", std::make_shared<IAudioInManagerForApplet>(system), 30);
|
server_manager->RegisterNamedService("audin:a", std::make_shared<IAudioInManagerForApplet>(system), 30);
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/logging.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#include "common/scm_rev.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/hle/service/acc/profile_manager.h"
|
||||||
|
#include "core/launch_params.h"
|
||||||
|
|
||||||
|
#undef _UNICODE
|
||||||
|
#include <getopt.h>
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
LaunchParams ParseLaunchParams(Core::System& system, int argc, char *argv[], wchar_t *argv_w[]) noexcept {
|
||||||
|
LaunchParams p{};
|
||||||
|
|
||||||
|
int option_index = 0;
|
||||||
|
static struct option long_options[] = {
|
||||||
|
// clang-format off
|
||||||
|
{"debug", no_argument, 0, 'd'},
|
||||||
|
{"config", required_argument, 0, 'c'},
|
||||||
|
{"fullscreen", no_argument, 0, 'f'},
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{"game", required_argument, 0, 'g'},
|
||||||
|
{"multiplayer", required_argument, 0, 'm'},
|
||||||
|
{"program", optional_argument, 0, 'p'},
|
||||||
|
{"user", required_argument, 0, 'u'},
|
||||||
|
{"version", no_argument, 0, 'v'},
|
||||||
|
{"input-profile", no_argument, 0, 'i'},
|
||||||
|
{"null-render", no_argument, 0, 'n'},
|
||||||
|
{"singlecore", no_argument, 0, 's'},
|
||||||
|
{"filter", no_argument, 0, 'x'},
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
|
while (optind < argc) {
|
||||||
|
if (int arg = getopt_long(argc, argv, "dc:fhg:m:p:u:vinsx", long_options, &option_index); arg != -1) {
|
||||||
|
switch (char(arg)) {
|
||||||
|
case 'd':
|
||||||
|
p.override_gdb_port = uint16_t(atoi(optarg));
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
p.config_path = optarg;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
p.fullscreen = true;
|
||||||
|
LOG_INFO(Frontend, "Starting in fullscreen mode...");
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
p.print_help = true;
|
||||||
|
break;
|
||||||
|
case 'g':
|
||||||
|
p.filepath = std::string(optarg);
|
||||||
|
break;
|
||||||
|
case 'i': {
|
||||||
|
p.input_profile = std::string(optarg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'm': {
|
||||||
|
p.use_multiplayer = true;
|
||||||
|
const std::string str_arg(optarg);
|
||||||
|
// regex to check if the format is nickname:password@ip:port
|
||||||
|
// with optional :password
|
||||||
|
const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
|
||||||
|
if (std::regex_match(str_arg, re)) {
|
||||||
|
std::smatch match;
|
||||||
|
std::regex_search(str_arg, match, re);
|
||||||
|
ASSERT(match.size() == 5);
|
||||||
|
p.nickname = match[1];
|
||||||
|
p.password = match[2];
|
||||||
|
p.address = match[3];
|
||||||
|
if (!match[4].str().empty()) {
|
||||||
|
p.port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0));
|
||||||
|
}
|
||||||
|
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
|
||||||
|
ASSERT(std::regex_match(p.nickname, nickname_re) && "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
|
||||||
|
ASSERT(!p.address.empty() && "Address is empty");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'p':
|
||||||
|
p.program_args = argv[optind];
|
||||||
|
++optind;
|
||||||
|
break;
|
||||||
|
case 'u': {
|
||||||
|
// Launch game with a specific user
|
||||||
|
bool argument_ok = isdigit(optarg[0]);
|
||||||
|
p.selected_user = atoi(optarg);
|
||||||
|
if (!argument_ok) {
|
||||||
|
// try to look it up by username, only finds the first username that matches.
|
||||||
|
auto const user_idx = system.GetProfileManager().GetUserIndex(optarg);
|
||||||
|
if (user_idx != std::nullopt) {
|
||||||
|
p.selected_user = user_idx.value();
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Frontend, "Invalid user argument '{}'", optarg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (system.GetProfileManager().UserExistsIndex(*p.selected_user)) {
|
||||||
|
Settings::values.current_user = s32(*p.selected_user);
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Frontend, "Selected user {} doesn't exist", *p.selected_user);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'v':
|
||||||
|
p.print_version = true;
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
p.force_null_render = true;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
p.force_single_core = true;
|
||||||
|
break;
|
||||||
|
case 'x':
|
||||||
|
p.log_filter = argv[optind];
|
||||||
|
++optind;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// only kept due to shortcuts made by Qt frontend which use "-qlaunch"
|
||||||
|
if (strcmp(argv[optind], "-hlaunch") == 0) {
|
||||||
|
p.launch_hlaunch = true;
|
||||||
|
} else if (strcmp(argv[optind], "-qlaunch") == 0) {
|
||||||
|
p.launch_qlaunch = true;
|
||||||
|
} else if (strcmp(argv[optind], "-setup") == 0) {
|
||||||
|
p.launch_setup = true;
|
||||||
|
} else {
|
||||||
|
// qt feeds utf8 data, sdl frontend feeds raw utf16 data
|
||||||
|
#ifdef _WIN32
|
||||||
|
p.filepath = argv_w != nullptr
|
||||||
|
? Common::UTF16ToUTF8(argv_w[optind])
|
||||||
|
: argv[optind];
|
||||||
|
#else
|
||||||
|
p.filepath = argv[optind];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
optind++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.argv0 = argv[0];
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyLaunchParams(LaunchParams const& lp) noexcept {
|
||||||
|
// apply the log_filter setting
|
||||||
|
// the logger was initialized before and doesn't pick up the filter on its own
|
||||||
|
Common::Log::Filter filter{};
|
||||||
|
filter.ParseFilterString(lp.log_filter.value_or(Settings::values.log_filter.GetValue()));
|
||||||
|
Common::Log::SetGlobalFilter(filter);
|
||||||
|
|
||||||
|
if (!lp.program_args.empty()) {
|
||||||
|
Settings::values.program_args = lp.program_args;
|
||||||
|
}
|
||||||
|
if (!lp.input_profile.empty()) {
|
||||||
|
auto& players = Settings::values.players.GetValue();
|
||||||
|
players[0].profile_name = lp.input_profile;
|
||||||
|
}
|
||||||
|
if (lp.selected_user.has_value()) {
|
||||||
|
Settings::values.current_user = std::clamp(*lp.selected_user, 0, 7);
|
||||||
|
}
|
||||||
|
if (lp.override_gdb_port.has_value()) {
|
||||||
|
Settings::values.use_gdbstub = true;
|
||||||
|
Settings::values.gdbstub_port = *lp.override_gdb_port;
|
||||||
|
}
|
||||||
|
if (lp.force_single_core) {
|
||||||
|
Settings::values.use_multi_core = false;
|
||||||
|
}
|
||||||
|
if (lp.force_null_render) {
|
||||||
|
Settings::values.renderer_backend = Settings::RendererBackend::Null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lp.print_version) {
|
||||||
|
LOG_INFO(Frontend, "Eden {} {}", Common::g_scm_branch, Common::g_scm_desc);
|
||||||
|
}
|
||||||
|
if (lp.print_help) {
|
||||||
|
LOG_INFO(Frontend,
|
||||||
|
"Usage: {}"
|
||||||
|
" [options] <filename>\n"
|
||||||
|
"-c, --config Load the specified configuration file\n"
|
||||||
|
"-f, --fullscreen Start in fullscreen mode\n"
|
||||||
|
"-g, --game File path of the game to load\n"
|
||||||
|
"-h, --help Display this help and exit\n"
|
||||||
|
"-m, --multiplayer=nick:password@address:port"
|
||||||
|
" Nickname, password, address and port for multiplayer\n"
|
||||||
|
"-p, --program Pass following string as arguments to executable\n"
|
||||||
|
"-u, --user Select a specific user profile from 0 to 7\n"
|
||||||
|
"-d, --debug Run the GDB stub on a port from 1 to 65535\n"
|
||||||
|
"-v, --version Output version information and exit\n",
|
||||||
|
lp.argv0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <optional>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "network/room.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
class System;
|
||||||
|
struct LaunchParams {
|
||||||
|
std::string argv0{};
|
||||||
|
std::string filepath{};
|
||||||
|
std::string nickname{};
|
||||||
|
std::string password{};
|
||||||
|
std::string address{};
|
||||||
|
std::string input_profile{};
|
||||||
|
std::string program_args{};
|
||||||
|
std::optional<std::string> config_path{};
|
||||||
|
std::optional<int> selected_user{};
|
||||||
|
std::optional<u16> override_gdb_port{};
|
||||||
|
std::optional<std::string> log_filter{};
|
||||||
|
u16 port = Network::DefaultRoomPort;
|
||||||
|
bool use_multiplayer = false;
|
||||||
|
bool fullscreen = false;
|
||||||
|
bool force_null_render = false;
|
||||||
|
bool force_single_core = false;
|
||||||
|
// print
|
||||||
|
bool print_version = false;
|
||||||
|
bool print_help = false;
|
||||||
|
// qt
|
||||||
|
bool launch_hlaunch = false;
|
||||||
|
bool launch_qlaunch = false;
|
||||||
|
bool launch_setup = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
LaunchParams ParseLaunchParams(Core::System& system, int argc, char *argv[], wchar_t *argv_w[]) noexcept;
|
||||||
|
void ApplyLaunchParams(LaunchParams const& lp) noexcept;
|
||||||
|
|
||||||
|
}
|
||||||
+42
-39
@@ -101,8 +101,10 @@ struct Memory::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
u64 protect_bytes = 0, protect_begin = 0;
|
u64 protect_bytes = 0, protect_begin = 0;
|
||||||
|
|
||||||
|
current_page_table->entries.CommitRegion(vaddr >> YUZU_PAGEBITS, (vaddr + size) >> YUZU_PAGEBITS);
|
||||||
for (u64 addr = vaddr; addr < vaddr + size; addr += YUZU_PAGESIZE) {
|
for (u64 addr = vaddr; addr < vaddr + size; addr += YUZU_PAGESIZE) {
|
||||||
const Common::PageType page_type = current_page_table->entries[addr >> YUZU_PAGEBITS].ptr.Type();
|
const Common::PageType page_type = current_page_table->entries.GetUnchecked(addr >> YUZU_PAGEBITS).Type();
|
||||||
switch (page_type) {
|
switch (page_type) {
|
||||||
case Common::PageType::RasterizerCachedMemory:
|
case Common::PageType::RasterizerCachedMemory:
|
||||||
if (protect_bytes > 0) {
|
if (protect_bytes > 0) {
|
||||||
@@ -123,16 +125,14 @@ struct Memory::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const {
|
[[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const {
|
||||||
Common::PhysicalAddress const paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr;
|
if (u64 paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].Pointer(true); paddr)
|
||||||
if (paddr)
|
return reinterpret_cast<u8*>(paddr) + vaddr;
|
||||||
return system.DeviceMemory().GetPointer<u8>(paddr + vaddr);
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] u8* GetPointerFromDebugMemory(u64 vaddr) const {
|
[[nodiscard]] u8* GetPointerFromDebugMemory(u64 vaddr) const {
|
||||||
const Common::PhysicalAddress paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr;
|
if (u64 paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].Pointer(true); paddr)
|
||||||
if (paddr != 0)
|
return reinterpret_cast<u8*>(paddr) + vaddr;
|
||||||
return system.DeviceMemory().GetPointer<u8>(paddr + vaddr);
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,10 +243,12 @@ struct Memory::Impl {
|
|||||||
std::size_t page_index = addr >> YUZU_PAGEBITS;
|
std::size_t page_index = addr >> YUZU_PAGEBITS;
|
||||||
std::size_t page_offset = addr & YUZU_PAGEMASK;
|
std::size_t page_offset = addr & YUZU_PAGEMASK;
|
||||||
bool user_accessible = true;
|
bool user_accessible = true;
|
||||||
|
|
||||||
|
current_page_table->entries.CommitRegion(page_index, page_index + (size >> YUZU_PAGEBITS) + 1);
|
||||||
while (remaining_size != 0) {
|
while (remaining_size != 0) {
|
||||||
const std::size_t copy_amount = (std::min)(std::size_t(YUZU_PAGESIZE) - page_offset, remaining_size);
|
const std::size_t copy_amount = (std::min)(std::size_t(YUZU_PAGESIZE) - page_offset, remaining_size);
|
||||||
const auto current_vaddr = u64((page_index << YUZU_PAGEBITS) + page_offset);
|
const auto current_vaddr = u64((page_index << YUZU_PAGEBITS) + page_offset);
|
||||||
const auto [pointer, type] = current_page_table->entries[page_index].ptr.PointerType();
|
const auto [pointer, type, _] = current_page_table->entries.GetUnchecked(page_index).PointerTypeBlock();
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Common::PageType::Unmapped: {
|
case Common::PageType::Unmapped: {
|
||||||
user_accessible = false;
|
user_accessible = false;
|
||||||
@@ -297,10 +299,10 @@ struct Memory::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] inline const u8* GetSpan(const VAddr addr, const std::size_t size) const noexcept {
|
[[nodiscard]] inline const u8* GetSpan(const VAddr addr, const std::size_t size) const noexcept {
|
||||||
return (current_page_table->entries[addr >> YUZU_PAGEBITS].block == current_page_table->entries[(addr + size) >> YUZU_PAGEBITS].block) ? GetPointerSilent(addr) : nullptr;
|
return (current_page_table->entries[addr >> YUZU_PAGEBITS].Block() == current_page_table->entries[(addr + size) >> YUZU_PAGEBITS].Block()) ? GetPointerSilent(addr) : nullptr;
|
||||||
}
|
}
|
||||||
[[nodiscard]] inline u8* GetSpan(const VAddr addr, const std::size_t size) noexcept {
|
[[nodiscard]] inline u8* GetSpan(const VAddr addr, const std::size_t size) noexcept {
|
||||||
return (current_page_table->entries[addr >> YUZU_PAGEBITS].block == current_page_table->entries[(addr + size) >> YUZU_PAGEBITS].block) ? GetPointerSilent(addr) : nullptr;
|
return (current_page_table->entries[addr >> YUZU_PAGEBITS].Block() == current_page_table->entries[(addr + size) >> YUZU_PAGEBITS].Block()) ? GetPointerSilent(addr) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WriteBlockImpl(const Common::ProcessAddress addr, const void* buffer, const std::size_t size, bool unsafe) {
|
bool WriteBlockImpl(const Common::ProcessAddress addr, const void* buffer, const std::size_t size, bool unsafe) {
|
||||||
@@ -404,11 +406,14 @@ struct Memory::Impl {
|
|||||||
// The region is at a granularity of CPU pages.
|
// The region is at a granularity of CPU pages.
|
||||||
|
|
||||||
const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1;
|
const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1;
|
||||||
|
|
||||||
|
current_page_table->entries.CommitRegion(vaddr >> YUZU_PAGEBITS, (vaddr >> YUZU_PAGEBITS) + num_pages);
|
||||||
for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) {
|
for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) {
|
||||||
const Common::PageType page_type = current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Type();
|
auto& entry = current_page_table->entries.GetUnchecked(vaddr >> YUZU_PAGEBITS);
|
||||||
|
const auto [pointer, type, block] = entry.PointerTypeBlock(true);
|
||||||
if (debug) {
|
if (debug) {
|
||||||
// Switch page type to debug if now debug
|
// Switch page type to debug if now debug
|
||||||
switch (page_type) {
|
switch (type) {
|
||||||
case Common::PageType::Unmapped:
|
case Common::PageType::Unmapped:
|
||||||
ASSERT(false && "Attempted to mark unmapped pages as debug");
|
ASSERT(false && "Attempted to mark unmapped pages as debug");
|
||||||
break;
|
break;
|
||||||
@@ -417,14 +422,14 @@ struct Memory::Impl {
|
|||||||
// Page is already marked.
|
// Page is already marked.
|
||||||
break;
|
break;
|
||||||
case Common::PageType::Memory:
|
case Common::PageType::Memory:
|
||||||
current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(0, Common::PageType::DebugMemory);
|
entry.MarkDebug(pointer, block);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Switch page type to non-debug if now non-debug
|
// Switch page type to non-debug if now non-debug
|
||||||
switch (page_type) {
|
switch (type) {
|
||||||
case Common::PageType::Unmapped:
|
case Common::PageType::Unmapped:
|
||||||
ASSERT(false && "Attempted to mark unmapped pages as non-debug");
|
ASSERT(false && "Attempted to mark unmapped pages as non-debug");
|
||||||
break;
|
break;
|
||||||
@@ -433,8 +438,7 @@ struct Memory::Impl {
|
|||||||
// Don't mess with already non-debug or rasterizer memory.
|
// Don't mess with already non-debug or rasterizer memory.
|
||||||
break;
|
break;
|
||||||
case Common::PageType::DebugMemory: {
|
case Common::PageType::DebugMemory: {
|
||||||
u8* const pointer = GetPointerFromDebugMemory(vaddr & ~YUZU_PAGEMASK);
|
entry.Store(false, Common::PageType::Memory, block, pointer);
|
||||||
current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(uintptr_t(pointer) - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -466,8 +470,10 @@ struct Memory::Impl {
|
|||||||
// is different). This assumes the specified GPU address region is contiguous as well.
|
// is different). This assumes the specified GPU address region is contiguous as well.
|
||||||
|
|
||||||
const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1;
|
const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1;
|
||||||
|
current_page_table->entries.CommitRegion(vaddr >> YUZU_PAGEBITS, (vaddr >> YUZU_PAGEBITS) + num_pages);
|
||||||
for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) {
|
for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) {
|
||||||
const Common::PageType page_type= current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Type();
|
auto& entry = current_page_table->entries.GetUnchecked(vaddr >> YUZU_PAGEBITS);
|
||||||
|
const Common::PageType page_type = entry.Type();
|
||||||
if (cached) {
|
if (cached) {
|
||||||
// Switch page type to cached if now cached
|
// Switch page type to cached if now cached
|
||||||
switch (page_type) {
|
switch (page_type) {
|
||||||
@@ -477,7 +483,7 @@ struct Memory::Impl {
|
|||||||
break;
|
break;
|
||||||
case Common::PageType::DebugMemory:
|
case Common::PageType::DebugMemory:
|
||||||
case Common::PageType::Memory:
|
case Common::PageType::Memory:
|
||||||
current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(0, Common::PageType::RasterizerCachedMemory);
|
entry.MarkRasterizerCached();
|
||||||
break;
|
break;
|
||||||
case Common::PageType::RasterizerCachedMemory:
|
case Common::PageType::RasterizerCachedMemory:
|
||||||
// There can be more than one GPU region mapped per CPU region, so it's common
|
// There can be more than one GPU region mapped per CPU region, so it's common
|
||||||
@@ -499,13 +505,13 @@ struct Memory::Impl {
|
|||||||
// that this area is already unmarked as cached.
|
// that this area is already unmarked as cached.
|
||||||
break;
|
break;
|
||||||
case Common::PageType::RasterizerCachedMemory: {
|
case Common::PageType::RasterizerCachedMemory: {
|
||||||
if (u8* const pointer = GetPointerFromRasterizerCachedMemory(vaddr & ~YUZU_PAGEMASK); pointer == nullptr) {
|
if (auto [ptr, _, block] = entry.PointerTypeBlock(true); ptr == 0) {
|
||||||
// It's possible that this function has been called while updating the
|
// It's possible that this function has been called while updating the
|
||||||
// pagetable after unmapping a VMA. In that case the underlying VMA will no
|
// pagetable after unmapping a VMA. In that case the underlying VMA will no
|
||||||
// longer exist, and we should just leave the pagetable entry blank.
|
// longer exist, and we should just leave the pagetable entry blank.
|
||||||
current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(0, Common::PageType::Unmapped);
|
entry.Store(false, Common::PageType::Unmapped, block, 0);
|
||||||
} else {
|
} else {
|
||||||
current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(uintptr_t(pointer) - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory);
|
entry.Store(false, Common::PageType::Memory, block, ptr);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -539,22 +545,18 @@ struct Memory::Impl {
|
|||||||
ASSERT_MSG(type != Common::PageType::Memory,
|
ASSERT_MSG(type != Common::PageType::Memory,
|
||||||
"Mapping memory page without a pointer @ {:016x}", base * YUZU_PAGESIZE);
|
"Mapping memory page without a pointer @ {:016x}", base * YUZU_PAGESIZE);
|
||||||
|
|
||||||
while (base != end) {
|
page_table.entries.ZeroRegion(base, end);
|
||||||
page_table.entries[base].ptr.Store(0, type);
|
|
||||||
page_table.entries[base].addr = 0;
|
|
||||||
page_table.entries[base].block = 0;
|
|
||||||
base += 1;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
auto orig_base = base;
|
auto current_block = block_count.fetch_add(1, std::memory_order_relaxed);
|
||||||
while (base != end) {
|
ASSERT(current_block != 65535);
|
||||||
auto host_ptr = uintptr_t(system.DeviceMemory().GetPointer<u8>(target)) - (base << YUZU_PAGEBITS);
|
|
||||||
auto backing = GetInteger(target) - (base << YUZU_PAGEBITS);
|
|
||||||
page_table.entries[base].ptr.Store(host_ptr, type);
|
|
||||||
page_table.entries[base].addr = backing;
|
|
||||||
page_table.entries[base].block = orig_base << YUZU_PAGEBITS;
|
|
||||||
|
|
||||||
ASSERT_MSG(page_table.entries[base].ptr.Pointer(),
|
page_table.entries.CommitRegion(base, end);
|
||||||
|
while (base != end) {
|
||||||
|
auto host_ptr = reinterpret_cast<u64>(system.DeviceMemory().GetPointer<u8>(target)) - (base << YUZU_PAGEBITS);;
|
||||||
|
auto& entry = page_table.entries.GetUnchecked(base);
|
||||||
|
|
||||||
|
entry.Store(false, type, current_block, host_ptr);
|
||||||
|
ASSERT_MSG(page_table.entries[base].Pointer(),
|
||||||
"memory mapping base yield a nullptr within the table");
|
"memory mapping base yield a nullptr within the table");
|
||||||
|
|
||||||
base += 1;
|
base += 1;
|
||||||
@@ -569,11 +571,11 @@ struct Memory::Impl {
|
|||||||
vaddr &= 0xffffffffffffULL;
|
vaddr &= 0xffffffffffffULL;
|
||||||
if (AddressSpaceContains(*current_page_table, vaddr, 1)) [[likely]] {
|
if (AddressSpaceContains(*current_page_table, vaddr, 1)) [[likely]] {
|
||||||
// Avoid adding any extra logic to this fast-path block
|
// Avoid adding any extra logic to this fast-path block
|
||||||
const uintptr_t raw_pointer = current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Raw();
|
const auto raw = current_page_table->entries[vaddr >> YUZU_PAGEBITS].Raw();
|
||||||
if (const uintptr_t pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) [[likely]] {
|
if (auto pointer = Common::PageTable::PageEntryData::ExtractPointer(raw); pointer) [[likely]] {
|
||||||
return reinterpret_cast<u8*>(pointer + vaddr);
|
return reinterpret_cast<u8*>(pointer + vaddr);
|
||||||
} else {
|
} else {
|
||||||
switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) {
|
switch (static_cast<Common::PageType>(raw.type)) {
|
||||||
case Common::PageType::Memory:
|
case Common::PageType::Memory:
|
||||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:#016x}", vaddr);
|
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:#016x}", vaddr);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -773,6 +775,7 @@ struct Memory::Impl {
|
|||||||
#else
|
#else
|
||||||
Common::HostMemory* host_buffer{};
|
Common::HostMemory* host_buffer{};
|
||||||
#endif
|
#endif
|
||||||
|
std::atomic<u16> block_count = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
Memory::Memory(Core::System& system_) : system{system_} {
|
Memory::Memory(Core::System& system_) : system{system_} {
|
||||||
@@ -811,7 +814,7 @@ bool Memory::IsValidVirtualAddress(const Common::ProcessAddress vaddr) const {
|
|||||||
if (page >= page_table.entries.size()) {
|
if (page >= page_table.entries.size()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto [pointer, type] = page_table.entries[page].ptr.PointerType();
|
const auto [pointer, type, _] = page_table.entries[page].PointerTypeBlock();
|
||||||
return pointer != 0 || type == Common::PageType::RasterizerCachedMemory ||
|
return pointer != 0 || type == Common::PageType::RasterizerCachedMemory ||
|
||||||
type == Common::PageType::DebugMemory;
|
type == Common::PageType::DebugMemory;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -407,8 +407,10 @@ EmitConfig A32AddressSpace::GetEmitConfig() {
|
|||||||
|
|
||||||
.page_table_pointer = std::bit_cast<u64>(conf.page_table),
|
.page_table_pointer = std::bit_cast<u64>(conf.page_table),
|
||||||
.page_table_address_space_bits = 32,
|
.page_table_address_space_bits = 32,
|
||||||
.page_table_pointer_mask_bits = conf.page_table_pointer_mask_bits,
|
.page_table_pointer_mask = conf.page_table_pointer_mask,
|
||||||
.page_table_log2_stride = conf.page_table_log2_stride,
|
.page_table_log2_stride = conf.page_table_log2_stride,
|
||||||
|
.page_table_marked_bit = conf.page_table_marked_bit,
|
||||||
|
.page_table_sign_extension = conf.page_table_sign_extension,
|
||||||
.silently_mirror_page_table = true,
|
.silently_mirror_page_table = true,
|
||||||
.absolute_offset_page_table = conf.absolute_offset_page_table,
|
.absolute_offset_page_table = conf.absolute_offset_page_table,
|
||||||
.detect_misaligned_access_via_page_table = conf.detect_misaligned_access_via_page_table,
|
.detect_misaligned_access_via_page_table = conf.detect_misaligned_access_via_page_table,
|
||||||
|
|||||||
@@ -579,8 +579,10 @@ EmitConfig A64AddressSpace::GetEmitConfig() {
|
|||||||
|
|
||||||
.page_table_pointer = std::bit_cast<u64>(conf.page_table),
|
.page_table_pointer = std::bit_cast<u64>(conf.page_table),
|
||||||
.page_table_address_space_bits = conf.page_table_address_space_bits,
|
.page_table_address_space_bits = conf.page_table_address_space_bits,
|
||||||
.page_table_pointer_mask_bits = conf.page_table_pointer_mask_bits,
|
.page_table_pointer_mask = conf.page_table_pointer_mask,
|
||||||
.page_table_log2_stride = conf.page_table_log2_stride,
|
.page_table_log2_stride = conf.page_table_log2_stride,
|
||||||
|
.page_table_marked_bit = conf.page_table_marked_bit,
|
||||||
|
.page_table_sign_extension = conf.page_table_sign_extension,
|
||||||
.silently_mirror_page_table = conf.silently_mirror_page_table,
|
.silently_mirror_page_table = conf.silently_mirror_page_table,
|
||||||
.absolute_offset_page_table = conf.absolute_offset_page_table,
|
.absolute_offset_page_table = conf.absolute_offset_page_table,
|
||||||
.detect_misaligned_access_via_page_table = conf.detect_misaligned_access_via_page_table,
|
.detect_misaligned_access_via_page_table = conf.detect_misaligned_access_via_page_table,
|
||||||
|
|||||||
@@ -128,8 +128,10 @@ struct EmitConfig {
|
|||||||
// Page table
|
// Page table
|
||||||
u64 page_table_pointer;
|
u64 page_table_pointer;
|
||||||
std::size_t page_table_address_space_bits;
|
std::size_t page_table_address_space_bits;
|
||||||
int page_table_pointer_mask_bits;
|
u64 page_table_pointer_mask;
|
||||||
std::size_t page_table_log2_stride;
|
std::size_t page_table_log2_stride;
|
||||||
|
std::optional<std::uint8_t> page_table_marked_bit;
|
||||||
|
std::optional<std::uint8_t> page_table_sign_extension;
|
||||||
bool silently_mirror_page_table;
|
bool silently_mirror_page_table;
|
||||||
bool absolute_offset_page_table;
|
bool absolute_offset_page_table;
|
||||||
u8 detect_misaligned_access_via_page_table;
|
u8 detect_misaligned_access_via_page_table;
|
||||||
|
|||||||
@@ -273,9 +273,18 @@ std::pair<oaknut::XReg, oaknut::XReg> InlinePageTableEmitVAddrLookup(oaknut::Cod
|
|||||||
// load x0 = *<(u8*)pagetable + index>
|
// load x0 = *<(u8*)pagetable + index>
|
||||||
code.LDR(Xscratch0, Xpagetable, Xscratch0);
|
code.LDR(Xscratch0, Xpagetable, Xscratch0);
|
||||||
|
|
||||||
if (ctx.conf.page_table_pointer_mask_bits != 0) {
|
if (ctx.conf.page_table_marked_bit) {
|
||||||
const u64 mask = u64(~u64(0)) << ctx.conf.page_table_pointer_mask_bits;
|
code.TST(Xscratch0, 1ULL << *ctx.conf.page_table_marked_bit);
|
||||||
code.AND(Xscratch0, Xscratch0, mask);
|
code.B(NE, *fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.conf.page_table_pointer_mask != 0) {
|
||||||
|
code.AND(Xscratch0, Xscratch0, ctx.conf.page_table_pointer_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: combine this with page_table_pointer_mask
|
||||||
|
if (ctx.conf.page_table_sign_extension) {
|
||||||
|
code.SBFM(Xscratch0, Xscratch0, 0, *ctx.conf.page_table_sign_extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
code.CBZ(Xscratch0, *fallback);
|
code.CBZ(Xscratch0, *fallback);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <bit>
|
#include <bit>
|
||||||
|
#include <utility>
|
||||||
#include "dynarmic/backend/x64/xbyak.h"
|
#include "dynarmic/backend/x64/xbyak.h"
|
||||||
|
|
||||||
#include "dynarmic/backend/x64/a32_emit_x64.h"
|
#include "dynarmic/backend/x64/a32_emit_x64.h"
|
||||||
@@ -78,27 +79,46 @@ Xbyak::RegExp EmitVAddrLookup(BlockOfCode& code, EmitContext& ctx, size_t bitsiz
|
|||||||
template<>
|
template<>
|
||||||
[[maybe_unused]] Xbyak::RegExp EmitVAddrLookup<A32EmitContext>(BlockOfCode& code, A32EmitContext& ctx, size_t bitsize, Xbyak::Label& abort, Xbyak::Reg64 vaddr) {
|
[[maybe_unused]] Xbyak::RegExp EmitVAddrLookup<A32EmitContext>(BlockOfCode& code, A32EmitContext& ctx, size_t bitsize, Xbyak::Label& abort, Xbyak::Reg64 vaddr) {
|
||||||
const Xbyak::Reg64 page = ctx.reg_alloc.ScratchGpr(code);
|
const Xbyak::Reg64 page = ctx.reg_alloc.ScratchGpr(code);
|
||||||
const Xbyak::Reg32 tmp = ctx.conf.absolute_offset_page_table ? page.cvt32() : ctx.reg_alloc.ScratchGpr(code).cvt32();
|
const Xbyak::Reg64 tmp = ctx.conf.absolute_offset_page_table && ctx.conf.page_table_pointer_mask == 0 ? page : ctx.reg_alloc.ScratchGpr(code);
|
||||||
|
|
||||||
EmitDetectMisalignedVAddr(code, ctx, bitsize, abort, vaddr, tmp.cvt64());
|
EmitDetectMisalignedVAddr(code, ctx, bitsize, abort, vaddr, tmp);
|
||||||
|
|
||||||
// TODO: This code assumes vaddr has been zext from 32-bits to 64-bits.
|
code.mov(tmp, vaddr);
|
||||||
|
|
||||||
code.mov(tmp, vaddr.cvt32());
|
|
||||||
code.shr(tmp, int(page_table_const_bits));
|
code.shr(tmp, int(page_table_const_bits));
|
||||||
code.shl(tmp, int(ctx.conf.page_table_log2_stride));
|
|
||||||
code.mov(page, qword[r14 + tmp.cvt64()]);
|
if (ctx.conf.page_table_log2_stride > 3) {
|
||||||
if (ctx.conf.page_table_pointer_mask_bits == 0) {
|
code.shl(tmp, int(ctx.conf.page_table_log2_stride));
|
||||||
code.test(page, page);
|
code.mov(page, qword[r14 + tmp.cvt64()]);
|
||||||
} else {
|
} else {
|
||||||
code.and_(page, ~u32(0) << ctx.conf.page_table_pointer_mask_bits);
|
code.mov(page, qword[r14 + tmp.cvt64() * int(1 << ctx.conf.page_table_log2_stride)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for marked bit, use as unmapped if marked
|
||||||
|
if (ctx.conf.page_table_marked_bit) {
|
||||||
|
code.bt(page, *ctx.conf.page_table_marked_bit);
|
||||||
|
code.jc(abort, code.T_NEAR);
|
||||||
|
}
|
||||||
|
// mask away attributes
|
||||||
|
if (ctx.conf.page_table_pointer_mask == 0) {
|
||||||
|
code.test(page, page);
|
||||||
|
} else if (std::in_range<s32>(ctx.conf.page_table_pointer_mask)) {
|
||||||
|
code.and_(page, ctx.conf.page_table_pointer_mask);
|
||||||
|
} else {
|
||||||
|
code.mov(tmp, ctx.conf.page_table_pointer_mask);
|
||||||
|
code.and_(page, tmp);
|
||||||
|
}
|
||||||
|
// check for sign bit, apply sign extension as needed
|
||||||
|
if (ctx.conf.page_table_sign_extension) {
|
||||||
|
code.shl(page, 63 - int(*ctx.conf.page_table_sign_extension));
|
||||||
|
code.sar(page, 63 - int(*ctx.conf.page_table_sign_extension));
|
||||||
|
}
|
||||||
|
|
||||||
code.jz(abort, code.T_NEAR);
|
code.jz(abort, code.T_NEAR);
|
||||||
if (ctx.conf.absolute_offset_page_table) {
|
if (ctx.conf.absolute_offset_page_table) {
|
||||||
return page + vaddr;
|
return page + vaddr;
|
||||||
}
|
}
|
||||||
code.mov(tmp, vaddr.cvt32());
|
code.mov(tmp, vaddr);
|
||||||
code.and_(tmp, static_cast<u32>(page_table_const_mask));
|
code.and_(tmp, u32(page_table_const_mask));
|
||||||
return page + tmp.cvt64();
|
return page + tmp.cvt64();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +128,7 @@ template<>
|
|||||||
const size_t unused_top_bits = 64 - ctx.conf.page_table_address_space_bits;
|
const size_t unused_top_bits = 64 - ctx.conf.page_table_address_space_bits;
|
||||||
|
|
||||||
const Xbyak::Reg64 page = ctx.reg_alloc.ScratchGpr(code);
|
const Xbyak::Reg64 page = ctx.reg_alloc.ScratchGpr(code);
|
||||||
const Xbyak::Reg64 tmp = ctx.conf.absolute_offset_page_table ? page : ctx.reg_alloc.ScratchGpr(code);
|
const Xbyak::Reg64 tmp = ctx.conf.absolute_offset_page_table && ctx.conf.page_table_pointer_mask == 0 ? page : ctx.reg_alloc.ScratchGpr(code);
|
||||||
|
|
||||||
EmitDetectMisalignedVAddr(code, ctx, bitsize, abort, vaddr, tmp);
|
EmitDetectMisalignedVAddr(code, ctx, bitsize, abort, vaddr, tmp);
|
||||||
|
|
||||||
@@ -141,13 +161,33 @@ template<>
|
|||||||
code.jnz(abort, code.T_NEAR);
|
code.jnz(abort, code.T_NEAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
code.shl(tmp, int(ctx.conf.page_table_log2_stride));
|
if (ctx.conf.page_table_log2_stride > 3) {
|
||||||
code.mov(page, qword[r14 + tmp]);
|
code.shl(tmp, int(ctx.conf.page_table_log2_stride));
|
||||||
if (ctx.conf.page_table_pointer_mask_bits == 0) {
|
code.mov(page, qword[r14 + tmp.cvt64()]);
|
||||||
code.test(page, page);
|
|
||||||
} else {
|
} else {
|
||||||
code.and_(page, ~u32(0) << ctx.conf.page_table_pointer_mask_bits);
|
code.mov(page, qword[r14 + tmp.cvt64() * int(1 << ctx.conf.page_table_log2_stride)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for marked bit, use as unmapped if marked
|
||||||
|
if (ctx.conf.page_table_marked_bit) {
|
||||||
|
code.bt(page, *ctx.conf.page_table_marked_bit);
|
||||||
|
code.jc(abort, code.T_NEAR);
|
||||||
|
}
|
||||||
|
// mask away attributes
|
||||||
|
if (ctx.conf.page_table_pointer_mask == 0) {
|
||||||
|
code.test(page, page);
|
||||||
|
} else if (std::in_range<s32>(ctx.conf.page_table_pointer_mask)) {
|
||||||
|
code.and_(page, ctx.conf.page_table_pointer_mask);
|
||||||
|
} else {
|
||||||
|
code.mov(tmp, ctx.conf.page_table_pointer_mask);
|
||||||
|
code.and_(page, tmp);
|
||||||
|
}
|
||||||
|
// check for sign bit, apply sign extension as needed
|
||||||
|
if (ctx.conf.page_table_sign_extension) {
|
||||||
|
code.shl(page, *ctx.conf.page_table_sign_extension);
|
||||||
|
code.sar(page, *ctx.conf.page_table_sign_extension);
|
||||||
|
}
|
||||||
|
|
||||||
code.jz(abort, code.T_NEAR);
|
code.jz(abort, code.T_NEAR);
|
||||||
if (ctx.conf.absolute_offset_page_table) {
|
if (ctx.conf.absolute_offset_page_table) {
|
||||||
return page + vaddr;
|
return page + vaddr;
|
||||||
|
|||||||
@@ -155,14 +155,23 @@ struct UserConfig {
|
|||||||
/// Maximum size is limited by the maximum length of a x86_64 / arm64 jump.
|
/// Maximum size is limited by the maximum length of a x86_64 / arm64 jump.
|
||||||
std::uint32_t code_cache_size = 128 * 1024 * 1024; // bytes
|
std::uint32_t code_cache_size = 128 * 1024 * 1024; // bytes
|
||||||
|
|
||||||
/// Masks out the first N bits in host pointers from the page table.
|
/// Applies a bit mask to the bits in host pointers from the page table.
|
||||||
/// The intention behind this is to allow users of Dynarmic to pack attributes in the
|
/// The intention behind this is to allow users of Dynarmic to pack attributes in the
|
||||||
/// same integer and update the pointer attribute pair atomically.
|
/// same integer and update the pointer attribute pair atomically.
|
||||||
/// If the configured value is 3, all pointers will be forcefully aligned to 8 bytes.
|
/// If the configured value is ~(0b111ULL), all pointers will be forcefully aligned to 8 bytes.
|
||||||
std::int32_t page_table_pointer_mask_bits = 0;
|
std::uint64_t page_table_pointer_mask = 0;
|
||||||
|
|
||||||
// Log2 of the size per page entry, value should be either 3 or 4
|
/// Log2 of the size per page entry, value should be either 3 or 4
|
||||||
std::size_t page_table_log2_stride = 3;
|
std::uint32_t page_table_log2_stride = 3;
|
||||||
|
|
||||||
|
/// Setting this value has Dynarmic check the specified bit of the page pointer provided by page table.
|
||||||
|
/// If the bit is set to 1, Dynarmic will treat it as unmapped.
|
||||||
|
/// This bit should be included as part of `page_table_pointer_mask_bits`.
|
||||||
|
std::optional<std::uint8_t> page_table_marked_bit = std::nullopt;
|
||||||
|
|
||||||
|
/// If this value is set, Dynarmic will sign extend the page table pointer by this bit.
|
||||||
|
/// Useful for compacting bits into the page table and should be used as part of `page_table_pointer_mask`.
|
||||||
|
std::optional<std::uint8_t> page_table_sign_extension = std::nullopt;
|
||||||
|
|
||||||
/// Select the architecture version to use.
|
/// Select the architecture version to use.
|
||||||
/// There are minor behavioural differences between versions.
|
/// There are minor behavioural differences between versions.
|
||||||
|
|||||||
@@ -169,14 +169,23 @@ struct UserConfig {
|
|||||||
/// This is only used if page_table is not nullptr.
|
/// This is only used if page_table is not nullptr.
|
||||||
std::uint32_t page_table_address_space_bits = 36;
|
std::uint32_t page_table_address_space_bits = 36;
|
||||||
|
|
||||||
/// Masks out the first N bits in host pointers from the page table.
|
/// Applies a bit mask to the bits in host pointers from the page table.
|
||||||
/// The intention behind this is to allow users of Dynarmic to pack attributes in the
|
/// The intention behind this is to allow users of Dynarmic to pack attributes in the
|
||||||
/// same integer and update the pointer attribute pair atomically.
|
/// same integer and update the pointer attribute pair atomically.
|
||||||
/// If the configured value is 3, all pointers will be forcefully aligned to 8 bytes.
|
/// If the configured value is ~(0b111ULL), all pointers will be forcefully aligned to 8 bytes.
|
||||||
std::int32_t page_table_pointer_mask_bits = 0;
|
std::uint64_t page_table_pointer_mask = 0;
|
||||||
|
|
||||||
// Log2 of the size per page entry, value should be either 3 or 4
|
/// Log2 of the size per page entry, value should be either 3 or 4
|
||||||
std::size_t page_table_log2_stride = 3;
|
std::uint32_t page_table_log2_stride = 3;
|
||||||
|
|
||||||
|
/// Setting this value has Dynarmic check the specified bit of the page pointer provided by page table.
|
||||||
|
/// If the bit is set to 1, Dynarmic will treat it as unmapped.
|
||||||
|
/// This bit should be included as part of `page_table_pointer_mask`.
|
||||||
|
std::optional<std::uint8_t> page_table_marked_bit = std::nullopt;
|
||||||
|
|
||||||
|
/// If this value is set, Dynarmic will sign extend the page table pointer by this bit.
|
||||||
|
/// Useful for compacting bits into the page table and should be used as part of `page_table_pointer_mask`.
|
||||||
|
std::optional<std::uint8_t> page_table_sign_extension = std::nullopt;
|
||||||
|
|
||||||
/// Counter-timer frequency register. The value of the register is not interpreted by
|
/// Counter-timer frequency register. The value of the register is not interpreted by
|
||||||
/// dynarmic.
|
/// dynarmic.
|
||||||
|
|||||||
@@ -226,9 +226,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
|||||||
INSERT(Settings, dma_accuracy, tr("DMA Accuracy:"),
|
INSERT(Settings, dma_accuracy, tr("DMA Accuracy:"),
|
||||||
tr("Controls the DMA read mode.\nUnsafe is faster, while Safe is more stable and can fix issues in some games.\nDefault follows the GPU Accuracy setting."));
|
tr("Controls the DMA read mode.\nUnsafe is faster, while Safe is more stable and can fix issues in some games.\nDefault follows the GPU Accuracy setting."));
|
||||||
INSERT(Settings, gpu_fence_behavior, tr("GPU Fence Behavior:"),
|
INSERT(Settings, gpu_fence_behavior, tr("GPU Fence Behavior:"),
|
||||||
tr("Controls the GPU fence synchronization behavior.\nImmediate is the fastest option, but can introduce some issues.\nBalanced offers better compatibility and may fix issues in some games.\nAccurate further improves compatibility at the cost of some performance.\nStrict is the slowest option, but can fix issues that require stricter synchronization.\nDefault follows the GPU Accuracy setting."));
|
tr("Controls the GPU fence synchronization behavior.\nImmediate is the fastest option, but can introduce some issues.\nBalanced offers better compatibility and may fix issues in some games.\nAccurate further improves compatibility at the cost of some performance.\nDefault follows the GPU Mode setting."));
|
||||||
INSERT(Settings, enable_gpu_buffer_readback, tr("Enable GPU buffer readback"),
|
|
||||||
tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly."));
|
|
||||||
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
|
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
|
||||||
tr("May reduce shader stutter."));
|
tr("May reduce shader stutter."));
|
||||||
INSERT(Settings, gpu_clock, tr("GPU Clocks"),
|
INSERT(Settings, gpu_clock, tr("GPU Clocks"),
|
||||||
@@ -442,7 +440,6 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
|
|||||||
PAIR(GpuFenceBehavior, Immediate, tr("Immediate")),
|
PAIR(GpuFenceBehavior, Immediate, tr("Immediate")),
|
||||||
PAIR(GpuFenceBehavior, Balanced, tr("Balanced")),
|
PAIR(GpuFenceBehavior, Balanced, tr("Balanced")),
|
||||||
PAIR(GpuFenceBehavior, Accurate, tr("Accurate")),
|
PAIR(GpuFenceBehavior, Accurate, tr("Accurate")),
|
||||||
PAIR(GpuFenceBehavior, Strict, tr("Strict")),
|
|
||||||
}});
|
}});
|
||||||
translations->insert(
|
translations->insert(
|
||||||
{Settings::EnumMetadata<Settings::CpuAccuracy>::Index(),
|
{Settings::EnumMetadata<Settings::CpuAccuracy>::Index(),
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -28,11 +31,6 @@ struct FuncTraits<ReturnType_ (*)(Args...)> {
|
|||||||
using ArgType = std::tuple_element_t<I, std::tuple<Args...>>;
|
using ArgType = std::tuple_element_t<I, std::tuple<Args...>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <auto func, typename... Args>
|
|
||||||
void SetDefinition(EmitContext& ctx, IR::Inst* inst, Args... args) {
|
|
||||||
inst->SetDefinition<Id>(func(ctx, std::forward<Args>(args)...));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename ArgType>
|
template <typename ArgType>
|
||||||
auto Arg(EmitContext& ctx, const IR::Value& arg) {
|
auto Arg(EmitContext& ctx, const IR::Value& arg) {
|
||||||
if constexpr (std::is_same_v<ArgType, std::string_view>) {
|
if constexpr (std::is_same_v<ArgType, std::string_view>) {
|
||||||
@@ -53,21 +51,10 @@ auto Arg(EmitContext& ctx, const IR::Value& arg) {
|
|||||||
template <auto func, bool is_first_arg_inst, size_t... I>
|
template <auto func, bool is_first_arg_inst, size_t... I>
|
||||||
void Invoke(EmitContext& ctx, IR::Inst* inst, std::index_sequence<I...>) {
|
void Invoke(EmitContext& ctx, IR::Inst* inst, std::index_sequence<I...>) {
|
||||||
using Traits = FuncTraits<decltype(func)>;
|
using Traits = FuncTraits<decltype(func)>;
|
||||||
if constexpr (std::is_same_v<typename Traits::ReturnType, Id>) {
|
if constexpr (is_first_arg_inst) {
|
||||||
if constexpr (is_first_arg_inst) {
|
func(ctx, *inst, Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
|
||||||
SetDefinition<func>(
|
|
||||||
ctx, inst, *inst,
|
|
||||||
Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
|
|
||||||
} else {
|
|
||||||
SetDefinition<func>(
|
|
||||||
ctx, inst, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if constexpr (is_first_arg_inst) {
|
func(ctx, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
|
||||||
func(ctx, *inst, Arg<typename Traits::template ArgType<I + 2>>(ctx, inst->Arg(I))...);
|
|
||||||
} else {
|
|
||||||
func(ctx, Arg<typename Traits::template ArgType<I + 1>>(ctx, inst->Arg(I))...);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ void BufferCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size
|
|||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
|
void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
|
||||||
if (memory_tracker.IsRegionGpuModified(device_addr, size)) {
|
if (IsRegionGpuModified(device_addr, size)) {
|
||||||
ClearDownload(device_addr, size);
|
ClearDownload(device_addr, size);
|
||||||
gpu_modified_ranges.Subtract(device_addr, size);
|
gpu_modified_ranges.Subtract(device_addr, size);
|
||||||
}
|
}
|
||||||
@@ -249,6 +249,10 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
|
|||||||
runtime.CopyBuffer(dest_buffer, src_buffer, copies, true);
|
runtime.CopyBuffer(dest_buffer, src_buffer, copies, true);
|
||||||
if (has_new_downloads) {
|
if (has_new_downloads) {
|
||||||
memory_tracker.MarkRegionAsGpuModified(*cpu_dest_address, amount);
|
memory_tracker.MarkRegionAsGpuModified(*cpu_dest_address, amount);
|
||||||
|
const bool should_sync = Settings::IsGPUFenceBehaviorBalanced() || Settings::IsGPUFenceBehaviorAccurate();
|
||||||
|
if (should_sync) {
|
||||||
|
runtime.Finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Tegra::Memory::DeviceGuestMemoryScoped<u8, Tegra::Memory::GuestMemoryFlags::UnsafeReadWrite>
|
Tegra::Memory::DeviceGuestMemoryScoped<u8, Tegra::Memory::GuestMemoryFlags::UnsafeReadWrite>
|
||||||
@@ -311,11 +315,8 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainCPUBuffer(
|
|||||||
MarkWrittenBuffer(buffer_id, device_addr, size);
|
MarkWrittenBuffer(buffer_id, device_addr, size);
|
||||||
break;
|
break;
|
||||||
case ObtainBufferOperation::DiscardWrite: {
|
case ObtainBufferOperation::DiscardWrite: {
|
||||||
const DAddr device_addr_start = Common::AlignDown(device_addr, 64);
|
ClearDownload(device_addr, size);
|
||||||
const DAddr device_addr_end = Common::AlignUp(device_addr + size, 64);
|
gpu_modified_ranges.Subtract(device_addr, size);
|
||||||
const size_t new_size = device_addr_end - device_addr_start;
|
|
||||||
ClearDownload(device_addr_start, new_size);
|
|
||||||
gpu_modified_ranges.Subtract(device_addr_start, new_size);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -1233,7 +1234,7 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
|
|||||||
buffer.MarkUsage(offset, size);
|
buffer.MarkUsage(offset, size);
|
||||||
|
|
||||||
if (is_written) {
|
if (is_written) {
|
||||||
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
|
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
|
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
|
||||||
@@ -1519,10 +1520,12 @@ void BufferCache<P>::UpdateComputeTextureBuffers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size) {
|
void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size, bool needs_sync) {
|
||||||
if constexpr (!IS_OPENGL) {
|
if constexpr (!IS_OPENGL) {
|
||||||
Buffer& buffer = slot_buffers[buffer_id];
|
if (needs_sync) {
|
||||||
buffer.setWriteTick(runtime.CurrentTick());
|
Buffer& buffer = slot_buffers[buffer_id];
|
||||||
|
buffer.setWriteTick(runtime.CurrentTick());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
memory_tracker.MarkRegionAsGpuModified(device_addr, size);
|
memory_tracker.MarkRegionAsGpuModified(device_addr, size);
|
||||||
gpu_modified_ranges.Add(device_addr, size);
|
gpu_modified_ranges.Add(device_addr, size);
|
||||||
@@ -1538,8 +1541,11 @@ BufferId BufferCache<P>::FindBuffer(DAddr device_addr, u32 size, bool sparse_com
|
|||||||
const BufferId buffer_id = page_table[page];
|
const BufferId buffer_id = page_table[page];
|
||||||
if (buffer_id) {
|
if (buffer_id) {
|
||||||
Buffer& buffer = slot_buffers[buffer_id];
|
Buffer& buffer = slot_buffers[buffer_id];
|
||||||
WaitForGpuFenceIfNeeded(buffer);
|
|
||||||
if (buffer.IsInBounds(device_addr, size)) {
|
if (buffer.IsInBounds(device_addr, size)) {
|
||||||
|
const bool should_sync = Settings::IsGPUFenceBehaviorAccurate();
|
||||||
|
if (should_sync) {
|
||||||
|
SynchronizeBufferWrites(buffer);
|
||||||
|
}
|
||||||
bool usable = true;
|
bool usable = true;
|
||||||
if constexpr (requires { buffer.IsSparseCompatible(); }) {
|
if constexpr (requires { buffer.IsSparseCompatible(); }) {
|
||||||
if (sparse_compatible && !buffer.IsSparseCompatible()) {
|
if (sparse_compatible && !buffer.IsSparseCompatible()) {
|
||||||
@@ -1555,17 +1561,11 @@ BufferId BufferCache<P>::FindBuffer(DAddr device_addr, u32 size, bool sparse_com
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::WaitForGpuFenceIfNeeded(Buffer& buffer) {
|
void BufferCache<P>::SynchronizeBufferWrites(Buffer& buffer) {
|
||||||
if constexpr (!IS_OPENGL) {
|
if constexpr (!IS_OPENGL) {
|
||||||
const bool gpu_fence_accurate = Settings::IsGPUFenceBehaviorAccurate();
|
const u64 buffer_tick = buffer.getWriteTick();
|
||||||
const bool gpu_fence_strict = Settings::IsGPUFenceBehaviorStrict();
|
if (!runtime.IsFree(buffer_tick)) {
|
||||||
if (gpu_fence_accurate || gpu_fence_strict) {
|
runtime.Wait(buffer_tick);
|
||||||
const u64 gpu_tick_delay = gpu_fence_strict ? 0 : 3;
|
|
||||||
const u64 buffer_tick = buffer.getWriteTick();
|
|
||||||
const u64 gpu_tick = runtime.KnownGpuTick();
|
|
||||||
if (buffer_tick > gpu_tick + gpu_tick_delay) {
|
|
||||||
runtime.Wait(buffer_tick);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1742,14 +1742,24 @@ bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 si
|
|||||||
u64 total_size_bytes = 0;
|
u64 total_size_bytes = 0;
|
||||||
u64 largest_copy = 0;
|
u64 largest_copy = 0;
|
||||||
const DAddr buffer_start = buffer.cpu_addr_cached;
|
const DAddr buffer_start = buffer.cpu_addr_cached;
|
||||||
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
|
const auto add_upload = [&](DAddr start, DAddr end) {
|
||||||
|
if (start == end) return;
|
||||||
|
const u64 range_size = end - start;
|
||||||
upload_copies.push_back(BufferCopy{
|
upload_copies.push_back(BufferCopy{
|
||||||
.src_offset = total_size_bytes,
|
.src_offset = total_size_bytes,
|
||||||
.dst_offset = device_addr_out - buffer_start,
|
.dst_offset = start - buffer_start,
|
||||||
.size = range_size,
|
.size = range_size,
|
||||||
});
|
});
|
||||||
total_size_bytes += range_size;
|
total_size_bytes += range_size;
|
||||||
largest_copy = (std::max)(largest_copy, range_size);
|
largest_copy = (std::max)(largest_copy, range_size);
|
||||||
|
};
|
||||||
|
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
|
||||||
|
DAddr upload_start = device_addr_out;
|
||||||
|
gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, [&](DAddr gpu_start, DAddr gpu_end) {
|
||||||
|
add_upload(upload_start, gpu_start);
|
||||||
|
upload_start = gpu_end;
|
||||||
|
});
|
||||||
|
add_upload(upload_start, device_addr_out + range_size);
|
||||||
});
|
});
|
||||||
if (total_size_bytes == 0) {
|
if (total_size_bytes == 0) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1788,9 +1798,6 @@ void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
|
|||||||
if (immediate_buffer.empty()) {
|
if (immediate_buffer.empty()) {
|
||||||
immediate_buffer = ImmediateBuffer(largest_copy);
|
immediate_buffer = ImmediateBuffer(largest_copy);
|
||||||
}
|
}
|
||||||
if (Settings::values.enable_gpu_buffer_readback.GetValue()) {
|
|
||||||
DownloadBufferMemory(buffer, device_addr, copy.size);
|
|
||||||
}
|
|
||||||
device_memory.ReadBlockUnsafe(device_addr, immediate_buffer.data(), copy.size);
|
device_memory.ReadBlockUnsafe(device_addr, immediate_buffer.data(), copy.size);
|
||||||
upload_span = immediate_buffer.subspan(0, copy.size);
|
upload_span = immediate_buffer.subspan(0, copy.size);
|
||||||
}
|
}
|
||||||
@@ -1809,9 +1816,6 @@ void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
|
|||||||
for (BufferCopy& copy : copies) {
|
for (BufferCopy& copy : copies) {
|
||||||
u8* const src_pointer = staging_pointer.data() + copy.src_offset;
|
u8* const src_pointer = staging_pointer.data() + copy.src_offset;
|
||||||
const DAddr device_addr = buffer.CpuAddr() + copy.dst_offset;
|
const DAddr device_addr = buffer.CpuAddr() + copy.dst_offset;
|
||||||
if (Settings::values.enable_gpu_buffer_readback.GetValue()) {
|
|
||||||
DownloadBufferMemory(buffer, device_addr, copy.size);
|
|
||||||
}
|
|
||||||
device_memory.ReadBlockUnsafe(device_addr, src_pointer, copy.size);
|
device_memory.ReadBlockUnsafe(device_addr, src_pointer, copy.size);
|
||||||
// Apply the staging offset
|
// Apply the staging offset
|
||||||
copy.src_offset += upload_staging.offset;
|
copy.src_offset += upload_staging.offset;
|
||||||
|
|||||||
@@ -430,11 +430,11 @@ private:
|
|||||||
|
|
||||||
void UpdateComputeTextureBuffers();
|
void UpdateComputeTextureBuffers();
|
||||||
|
|
||||||
void MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size);
|
void MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size, bool needs_sync = false);
|
||||||
|
|
||||||
[[nodiscard]] BufferId FindBuffer(DAddr device_addr, u32 size, bool sparse_compatible);
|
[[nodiscard]] BufferId FindBuffer(DAddr device_addr, u32 size, bool sparse_compatible);
|
||||||
|
|
||||||
void WaitForGpuFenceIfNeeded(Buffer& buffer);
|
void SynchronizeBufferWrites(Buffer& buffer);
|
||||||
|
|
||||||
[[nodiscard]] OverlapResult ResolveOverlaps(DAddr device_addr, u32 wanted_size);
|
[[nodiscard]] OverlapResult ResolveOverlaps(DAddr device_addr, u32 wanted_size);
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
|
|
||||||
constexpr u32 MacroRegistersStart = 0xE00;
|
constexpr u32 MacroRegistersStart = 0xE00;
|
||||||
[[maybe_unused]] constexpr u32 ComputeInline = 0x6D;
|
constexpr u32 ComputeInline = 0x6D;
|
||||||
|
|
||||||
DmaPusher::DmaPusher(Core::System& system_, MemoryManager& memory_manager_, Control::ChannelState& channel_state_)
|
DmaPusher::DmaPusher(Core::System& system_, MemoryManager& memory_manager_, Control::ChannelState& channel_state_)
|
||||||
: system{system_}
|
: system{system_}
|
||||||
@@ -73,11 +73,16 @@ bool DmaPusher::Step() {
|
|||||||
synced = false;
|
synced = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.size > 0 && dma_state.method >= MacroRegistersStart && subchannels[dma_state.subchannel]) {
|
|
||||||
subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, header.size * sizeof(u32));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header.size > 0) {
|
if (header.size > 0) {
|
||||||
|
if (subchannels[dma_state.subchannel] && dma_state.method_count) {
|
||||||
|
const auto engine = subchannel_type[dma_state.subchannel];
|
||||||
|
const bool kepler_payload = engine == Engines::EngineTypes::KeplerCompute && dma_state.method == ComputeInline && dma_state.non_incrementing;
|
||||||
|
const bool macro_payload = engine == Engines::EngineTypes::Maxwell3D && dma_state.method >= MacroRegistersStart;
|
||||||
|
if (kepler_payload || macro_payload) {
|
||||||
|
const size_t words = std::min<size_t>(dma_state.method_count, header.size);
|
||||||
|
subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, words * sizeof(u32));
|
||||||
|
}
|
||||||
|
}
|
||||||
const bool use_safe = Settings::IsDMALevelDefault() ? Settings::IsGPULevelHigh() : Settings::IsDMALevelSafe();
|
const bool use_safe = Settings::IsDMALevelDefault() ? Settings::IsGPULevelHigh() : Settings::IsDMALevelSafe();
|
||||||
if (use_safe) {
|
if (use_safe) {
|
||||||
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::SafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
|
Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::SafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers);
|
||||||
|
|||||||
@@ -49,13 +49,16 @@ void KeplerCompute::CallMethod(Core::System& system, u32 method, u32 method_argu
|
|||||||
case KEPLER_COMPUTE_REG_INDEX(exec_upload): {
|
case KEPLER_COMPUTE_REG_INDEX(exec_upload): {
|
||||||
UploadInfo info{.upload_address = upload_address,
|
UploadInfo info{.upload_address = upload_address,
|
||||||
.exec_address = upload_state.ExecTargetAddress(),
|
.exec_address = upload_state.ExecTargetAddress(),
|
||||||
.copy_size = upload_state.GetUploadSize()};
|
.copy_size = upload_state.GetUploadSize(),
|
||||||
|
.was_dirty = upload_dirty};
|
||||||
uploads.push_back(info);
|
uploads.push_back(info);
|
||||||
upload_state.ProcessExec(regs.exec_upload.linear != 0);
|
upload_state.ProcessExec(regs.exec_upload.linear != 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case KEPLER_COMPUTE_REG_INDEX(data_upload): {
|
case KEPLER_COMPUTE_REG_INDEX(data_upload): {
|
||||||
upload_address = current_dma_segment;
|
upload_address = current_dma_segment;
|
||||||
|
upload_dirty = current_dirty;
|
||||||
|
current_dirty = false;
|
||||||
upload_state.ProcessData(method_argument, is_last_call);
|
upload_state.ProcessData(method_argument, is_last_call);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -64,9 +67,11 @@ void KeplerCompute::CallMethod(Core::System& system, u32 method, u32 method_argu
|
|||||||
|
|
||||||
for (auto& data : uploads) {
|
for (auto& data : uploads) {
|
||||||
const GPUVAddr offset = data.exec_address - launch_desc_loc;
|
const GPUVAddr offset = data.exec_address - launch_desc_loc;
|
||||||
if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x) &&
|
if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x)) {
|
||||||
memory_manager.IsMemoryDirty(data.upload_address, data.copy_size)) {
|
const bool source_dirty = memory_manager.IsMemoryDirty(data.upload_address, data.copy_size);
|
||||||
indirect_compute = {data.upload_address};
|
if (data.was_dirty || source_dirty) {
|
||||||
|
indirect_compute = {data.upload_address};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uploads.clear();
|
uploads.clear();
|
||||||
@@ -83,6 +88,8 @@ void KeplerCompute::CallMultiMethod(Core::System& system, u32 method, const u32*
|
|||||||
switch (method) {
|
switch (method) {
|
||||||
case KEPLER_COMPUTE_REG_INDEX(data_upload):
|
case KEPLER_COMPUTE_REG_INDEX(data_upload):
|
||||||
upload_address = current_dma_segment;
|
upload_address = current_dma_segment;
|
||||||
|
upload_dirty = current_dirty;
|
||||||
|
current_dirty = false;
|
||||||
upload_state.ProcessData(base_start, amount);
|
upload_state.ProcessData(base_start, amount);
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -226,11 +226,13 @@ private:
|
|||||||
VideoCore::RasterizerInterface* rasterizer = nullptr;
|
VideoCore::RasterizerInterface* rasterizer = nullptr;
|
||||||
Upload::State upload_state;
|
Upload::State upload_state;
|
||||||
GPUVAddr upload_address;
|
GPUVAddr upload_address;
|
||||||
|
bool upload_dirty{};
|
||||||
|
|
||||||
struct UploadInfo {
|
struct UploadInfo {
|
||||||
GPUVAddr upload_address;
|
GPUVAddr upload_address;
|
||||||
GPUVAddr exec_address;
|
GPUVAddr exec_address;
|
||||||
u32 copy_size;
|
u32 copy_size;
|
||||||
|
bool was_dirty;
|
||||||
};
|
};
|
||||||
std::vector<UploadInfo> uploads;
|
std::vector<UploadInfo> uploads;
|
||||||
std::optional<GPUVAddr> indirect_compute{};
|
std::optional<GPUVAddr> indirect_compute{};
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SignalFence(std::function<void()>&& func) {
|
void SignalFence(std::function<void()>&& func) {
|
||||||
const bool delay_fence = Settings::IsGPUFenceBehaviorDefault() ? Settings::IsGPULevelHigh() : Settings::IsGPUFenceBehaviorBalanced() || Settings::IsGPUFenceBehaviorAccurate() || Settings::IsGPUFenceBehaviorStrict();
|
const bool delay_fence = Settings::IsGPUFenceBehaviorDefault() ? Settings::IsGPULevelHigh() : Settings::IsGPUFenceBehaviorBalanced() || Settings::IsGPUFenceBehaviorAccurate();
|
||||||
const bool should_flush = ShouldFlush();
|
const bool should_flush = ShouldFlush();
|
||||||
if constexpr (!can_async_check) {
|
if constexpr (!can_async_check) {
|
||||||
TryReleasePendingFences<false>();
|
TryReleasePendingFences<false>();
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ MemoryManager::MemoryManager(Core::System& system_, MaxwellDeviceMemoryManager&
|
|||||||
page_table_mask = page_table_size - 1;
|
page_table_mask = page_table_size - 1;
|
||||||
big_page_table_mask = big_page_table_size - 1;
|
big_page_table_mask = big_page_table_size - 1;
|
||||||
|
|
||||||
|
big_page_table_dev.ResizeAndClear(big_page_table_size);
|
||||||
big_entries.resize(big_page_table_size / 32, 0);
|
big_entries.resize(big_page_table_size / 32, 0);
|
||||||
big_page_table_dev.resize(big_page_table_size);
|
|
||||||
big_page_continuous.resize(big_page_table_size / continuous_bits, 0);
|
big_page_continuous.resize(big_page_table_size / continuous_bits, 0);
|
||||||
entries.resize(page_table_size / 32, 0);
|
entries.resize(page_table_size / 32, 0);
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ GPUVAddr MemoryManager::BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] DAddr
|
|||||||
const DAddr current_dev_addr = dev_addr + offset;
|
const DAddr current_dev_addr = dev_addr + offset;
|
||||||
const auto index = PageEntryIndex(current_gpu_addr, true);
|
const auto index = PageEntryIndex(current_gpu_addr, true);
|
||||||
const u32 sub_value = static_cast<u32>(current_dev_addr >> cpu_page_bits);
|
const u32 sub_value = static_cast<u32>(current_dev_addr >> cpu_page_bits);
|
||||||
big_page_table_dev[index] = sub_value;
|
big_page_table_dev.Set(index, sub_value);
|
||||||
const bool is_continuous = ([&] {
|
const bool is_continuous = ([&] {
|
||||||
uintptr_t base_ptr{
|
uintptr_t base_ptr{
|
||||||
reinterpret_cast<uintptr_t>(memory.GetPointer<u8>(current_dev_addr))};
|
reinterpret_cast<uintptr_t>(memory.GetPointer<u8>(current_dev_addr))};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
#include "common/multi_level_page_table.h"
|
#include "common/multi_level_page_table.h"
|
||||||
#include "common/range_map.h"
|
#include "common/range_map.h"
|
||||||
#include "common/scratch_buffer.h"
|
#include "common/scratch_buffer.h"
|
||||||
#include "common/virtual_buffer.h"
|
#include "common/sparse_large_vector.h"
|
||||||
#include "video_core/invalidation_accumulator.h"
|
#include "video_core/invalidation_accumulator.h"
|
||||||
#include "video_core/cache_types.h"
|
#include "video_core/cache_types.h"
|
||||||
#include "video_core/host1x/gpu_device_memory_manager.h"
|
#include "video_core/host1x/gpu_device_memory_manager.h"
|
||||||
@@ -214,7 +214,7 @@ private:
|
|||||||
|
|
||||||
Common::MultiLevelPageTable<u32> page_table;
|
Common::MultiLevelPageTable<u32> page_table;
|
||||||
Common::RangeMap<GPUVAddr, PTEKind> kind_map;
|
Common::RangeMap<GPUVAddr, PTEKind> kind_map;
|
||||||
Common::VirtualBuffer<u32> big_page_table_dev;
|
Common::SparseLargeVector<u32> big_page_table_dev;
|
||||||
|
|
||||||
std::vector<u64> big_page_continuous;
|
std::vector<u64> big_page_continuous;
|
||||||
boost::container::small_vector<std::pair<DAddr, std::size_t>, 32> page_stash{};
|
boost::container::small_vector<std::pair<DAddr, std::size_t>, 32> page_stash{};
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ void QueryCacheBase<Traits>::CounterReport(GPUVAddr addr, QueryType counter_type
|
|||||||
};
|
};
|
||||||
u8* pointer = impl->device_memory.template GetPointer<u8>(cpu_addr);
|
u8* pointer = impl->device_memory.template GetPointer<u8>(cpu_addr);
|
||||||
u8* pointer_timestamp = impl->device_memory.template GetPointer<u8>(cpu_addr + 8);
|
u8* pointer_timestamp = impl->device_memory.template GetPointer<u8>(cpu_addr + 8);
|
||||||
bool is_synced = (Settings::IsGPUFenceBehaviorDefault() ? !Settings::IsGPULevelHigh() : !Settings::IsGPUFenceBehaviorBalanced() && !Settings::IsGPUFenceBehaviorAccurate() && !Settings::IsGPUFenceBehaviorStrict()) && is_fence;
|
bool is_synced = (Settings::IsGPUFenceBehaviorDefault() ? !Settings::IsGPULevelHigh() : !Settings::IsGPUFenceBehaviorBalanced() && !Settings::IsGPUFenceBehaviorAccurate()) && is_fence;
|
||||||
std::function<void()> operation([this, is_synced, streamer, query_base = query, query_location,
|
std::function<void()> operation([this, is_synced, streamer, query_base = query, query_location,
|
||||||
pointer, pointer_timestamp] {
|
pointer, pointer_timestamp] {
|
||||||
if (True(query_base->flags & QueryFlagBits::IsInvalidated)) {
|
if (True(query_base->flags & QueryFlagBits::IsInvalidated)) {
|
||||||
|
|||||||
@@ -98,7 +98,10 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
|
|||||||
RefreshResources(device, framebuffer);
|
RefreshResources(device, framebuffer);
|
||||||
SetAntiAliasPass(device);
|
SetAntiAliasPass(device);
|
||||||
#ifdef HAS_RESHADE
|
#ifdef HAS_RESHADE
|
||||||
SetPostProcessPass(device);
|
const bool is_applet =
|
||||||
|
(framebuffer.layer_stack_mask & Service::Nvnflinger::LayerStackBit(
|
||||||
|
Service::Nvnflinger::LayerStackId::Recording)) == 0;
|
||||||
|
SetPostProcessPass(device, is_applet);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Finish any pending renderpass
|
// Finish any pending renderpass
|
||||||
@@ -228,7 +231,11 @@ void Layer::SetAntiAliasPass(const Device& device) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAS_RESHADE
|
#ifdef HAS_RESHADE
|
||||||
void Layer::SetPostProcessPass(const Device& device) {
|
void Layer::SetPostProcessPass(const Device& device, bool is_applet) {
|
||||||
|
if (is_applet) {
|
||||||
|
post_process.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const VkExtent2D render_area{
|
const VkExtent2D render_area{
|
||||||
.width = Settings::values.resolution_info.ScaleUp(raw_width),
|
.width = Settings::values.resolution_info.ScaleUp(raw_width),
|
||||||
.height = Settings::values.resolution_info.ScaleUp(raw_height),
|
.height = Settings::values.resolution_info.ScaleUp(raw_height),
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ private:
|
|||||||
void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer);
|
void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer);
|
||||||
void SetAntiAliasPass(const Device& device);
|
void SetAntiAliasPass(const Device& device);
|
||||||
#ifdef HAS_RESHADE
|
#ifdef HAS_RESHADE
|
||||||
void SetPostProcessPass(const Device& device);
|
void SetPostProcessPass(const Device& device, bool is_applet);
|
||||||
#endif
|
#endif
|
||||||
void ReleaseRawImages();
|
void ReleaseRawImages();
|
||||||
|
|
||||||
|
|||||||
@@ -419,15 +419,15 @@ void BufferCacheRuntime::TickFrame(Common::SlotVector<Buffer>& slot_buffers) noe
|
|||||||
}
|
}
|
||||||
|
|
||||||
u64 BufferCacheRuntime::CurrentTick() {
|
u64 BufferCacheRuntime::CurrentTick() {
|
||||||
return scheduler.GetMasterSemaphore().CurrentTick();
|
return scheduler.CurrentTick();
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 BufferCacheRuntime::KnownGpuTick() {
|
bool BufferCacheRuntime::IsFree(u64 tick) {
|
||||||
return scheduler.GetMasterSemaphore().KnownGpuTick();
|
return scheduler.IsFree(tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferCacheRuntime::Wait(u64 buffer_tick) {
|
void BufferCacheRuntime::Wait(u64 tick) {
|
||||||
scheduler.Wait(buffer_tick);
|
scheduler.Wait(tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferCacheRuntime::Finish() {
|
void BufferCacheRuntime::Finish() {
|
||||||
|
|||||||
@@ -112,9 +112,9 @@ public:
|
|||||||
|
|
||||||
u64 CurrentTick();
|
u64 CurrentTick();
|
||||||
|
|
||||||
u64 KnownGpuTick();
|
bool IsFree(u64 tick);
|
||||||
|
|
||||||
void Wait(u64 buffer_tick);
|
void Wait(u64 tick);
|
||||||
|
|
||||||
void Finish();
|
void Finish();
|
||||||
|
|
||||||
|
|||||||
+18
-68
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
// Static Qt on macOS doesn't use Vulkan
|
// Static Qt on macOS doesn't use Vulkan
|
||||||
// Other platforms do, and thus have conflicting VulkanMemoryAllocator symbols
|
// Other platforms do, and thus have conflicting VulkanMemoryAllocator symbols
|
||||||
|
#include "core/launch_params.h"
|
||||||
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
|
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
|
||||||
#undef VMA_IMPLEMENTATION
|
#undef VMA_IMPLEMENTATION
|
||||||
#endif
|
#endif
|
||||||
@@ -515,79 +516,28 @@ MainWindow::MainWindow(bool has_broken_vulkan)
|
|||||||
// to prevent the UI from blowing up.
|
// to prevent the UI from blowing up.
|
||||||
UpdateUITheme();
|
UpdateUITheme();
|
||||||
|
|
||||||
QStringList args = QApplication::arguments();
|
if (QStringList args = QApplication::arguments(); args.size() >= 2) {
|
||||||
|
QList<QByteArray> qba{};
|
||||||
|
for (auto const& e : args)
|
||||||
|
qba.push_back(e.toUtf8());
|
||||||
|
std::vector<char*> args_cstr{};
|
||||||
|
for (auto& e : qba)
|
||||||
|
args_cstr.push_back(e.data());
|
||||||
|
|
||||||
if (args.size() < 2) {
|
auto const lp = Core::ParseLaunchParams(*QtCommon::system, args_cstr.size(), args_cstr.data(), nullptr);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString game_path;
|
// Override fullscreen setting if gamepath or argument is provided
|
||||||
bool should_launch_qlaunch = false;
|
if (!lp.filepath.empty() || lp.fullscreen) {
|
||||||
bool should_launch_hlaunch = false;
|
ui->action_Fullscreen->setChecked(lp.fullscreen);
|
||||||
bool should_launch_setup = false;
|
|
||||||
bool has_gamepath = false;
|
|
||||||
bool is_fullscreen = false;
|
|
||||||
|
|
||||||
// Preserves drag/drop functionality
|
|
||||||
for (int i = 1; i < args.size(); ++i) {
|
|
||||||
if (args[i] == QStringLiteral("-f")) {
|
|
||||||
// Launch game in fullscreen mode
|
|
||||||
is_fullscreen = true;
|
|
||||||
} else if (args[i] == QStringLiteral("-u") && i < args.size() - 1) {
|
|
||||||
// Launch game with a specific user
|
|
||||||
int user_arg_idx = ++i;
|
|
||||||
bool argument_ok;
|
|
||||||
std::size_t selected_user = args[user_arg_idx].toUInt(&argument_ok);
|
|
||||||
if (!argument_ok) {
|
|
||||||
// try to look it up by username, only finds the first username that matches.
|
|
||||||
std::string const user_arg_str = args[user_arg_idx].toStdString();
|
|
||||||
auto const user_idx =
|
|
||||||
QtCommon::system->GetProfileManager().GetUserIndex(user_arg_str);
|
|
||||||
if (user_idx != std::nullopt) {
|
|
||||||
selected_user = user_idx.value();
|
|
||||||
} else {
|
|
||||||
LOG_ERROR(Frontend, "Invalid user argument '{}'", user_arg_str);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (QtCommon::system->GetProfileManager().UserExistsIndex(selected_user)) {
|
|
||||||
Settings::values.current_user = s32(selected_user);
|
|
||||||
user_flag_cmd_line = true;
|
|
||||||
} else {
|
|
||||||
LOG_ERROR(Frontend, "Selected user {} doesn't exist", selected_user);
|
|
||||||
}
|
|
||||||
} else if (args[i] == QStringLiteral("-g") && i < args.size() - 1) {
|
|
||||||
// Launch game at path
|
|
||||||
game_path = args[++i];
|
|
||||||
has_gamepath = true;
|
|
||||||
} else if (args[i] == QStringLiteral("-input-profile") && i < args.size() - 1) {
|
|
||||||
auto& players = Settings::values.players.GetValue();
|
|
||||||
players[0].profile_name = args[++i].toStdString();
|
|
||||||
} else if (args[i] == QStringLiteral("-qlaunch")) {
|
|
||||||
should_launch_qlaunch = true;
|
|
||||||
} else if (args[i] == QStringLiteral("-hlaunch")) {
|
|
||||||
should_launch_hlaunch = true;
|
|
||||||
} else if (args[i] == QStringLiteral("-setup")) {
|
|
||||||
should_launch_setup = true;
|
|
||||||
} else {
|
|
||||||
game_path = args[i];
|
|
||||||
has_gamepath = true;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Override fullscreen setting if gamepath or argument is provided
|
if (!lp.filepath.empty()) {
|
||||||
if (has_gamepath || is_fullscreen) {
|
BootGame(QString::fromStdString(lp.filepath), ApplicationAppletParameters());
|
||||||
ui->action_Fullscreen->setChecked(is_fullscreen);
|
} else if (lp.launch_setup) {
|
||||||
}
|
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Starter), std::nullopt);
|
||||||
|
} else if (lp.launch_qlaunch) {
|
||||||
if (should_launch_setup) {
|
|
||||||
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Starter), std::nullopt);
|
|
||||||
} else {
|
|
||||||
if (!game_path.isEmpty()) {
|
|
||||||
BootGame(game_path, ApplicationAppletParameters());
|
|
||||||
} else if (should_launch_qlaunch) {
|
|
||||||
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt);
|
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt);
|
||||||
} else if (should_launch_hlaunch) {
|
} else if (lp.launch_hlaunch) {
|
||||||
std::filesystem::path const sd_dir =
|
std::filesystem::path const sd_dir =
|
||||||
Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir);
|
Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir);
|
||||||
auto const hbl_path = (sd_dir / "atmosphere" / "hbl.nsp").string();
|
auto const hbl_path = (sd_dir / "atmosphere" / "hbl.nsp").string();
|
||||||
|
|||||||
@@ -42,9 +42,6 @@ target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common
|
|||||||
if (ENABLE_OPENGL)
|
if (ENABLE_OPENGL)
|
||||||
target_link_libraries(yuzu-cmd PRIVATE glad)
|
target_link_libraries(yuzu-cmd PRIVATE glad)
|
||||||
endif()
|
endif()
|
||||||
if (MSVC)
|
|
||||||
target_link_libraries(yuzu-cmd PRIVATE getopt)
|
|
||||||
endif()
|
|
||||||
target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||||
|
|
||||||
create_resource("../../dist/eden.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
|
create_resource("../../dist/eden.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
|
||||||
|
|||||||
+15
-189
@@ -10,6 +10,7 @@
|
|||||||
#include <regex>
|
#include <regex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "common/settings_enums.h"
|
#include "common/settings_enums.h"
|
||||||
|
#include "core/launch_params.h"
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -52,12 +53,6 @@
|
|||||||
#include "common/windows/timer_resolution.h"
|
#include "common/windows/timer_resolution.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#undef _UNICODE
|
|
||||||
#include <getopt.h>
|
|
||||||
#ifndef _MSC_VER
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
extern "C" {
|
extern "C" {
|
||||||
// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
|
// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
|
||||||
@@ -67,25 +62,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void PrintHelp(const char* argv0) {
|
|
||||||
std::cout << "Usage: " << argv0
|
|
||||||
<< " [options] <filename>\n"
|
|
||||||
"-c, --config Load the specified configuration file\n"
|
|
||||||
"-f, --fullscreen Start in fullscreen mode\n"
|
|
||||||
"-g, --game File path of the game to load\n"
|
|
||||||
"-h, --help Display this help and exit\n"
|
|
||||||
"-m, --multiplayer=nick:password@address:port"
|
|
||||||
" Nickname, password, address and port for multiplayer\n"
|
|
||||||
"-p, --program Pass following string as arguments to executable\n"
|
|
||||||
"-u, --user Select a specific user profile from 0 to 7\n"
|
|
||||||
"-d, --debug Run the GDB stub on a port from 1 to 65535\n"
|
|
||||||
"-v, --version Output version information and exit\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
static void PrintVersion() {
|
|
||||||
std::cout << "Eden " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void OnStateChanged(const Network::RoomMember::State& state) {
|
static void OnStateChanged(const Network::RoomMember::State& state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case Network::RoomMember::State::Idle:
|
case Network::RoomMember::State::Idle:
|
||||||
@@ -208,168 +184,18 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
|||||||
LOG_CRITICAL(Frontend, "Failed to get command line arguments");
|
LOG_CRITICAL(Frontend, "Failed to get command line arguments");
|
||||||
return SDL_APP_FAILURE;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
std::string filepath;
|
|
||||||
std::optional<std::string> config_path{};
|
|
||||||
std::string program_args;
|
|
||||||
std::optional<int> selected_user{};
|
|
||||||
std::optional<u16> override_gdb_port{};
|
|
||||||
bool use_multiplayer = false;
|
|
||||||
bool fullscreen = false;
|
|
||||||
bool force_null_render = false;
|
|
||||||
bool force_single_core = false;
|
|
||||||
std::string nickname{};
|
|
||||||
std::string password{};
|
|
||||||
std::string address{};
|
|
||||||
std::string input_profile{};
|
|
||||||
std::optional<std::string> log_filter{};
|
|
||||||
u16 port = Network::DefaultRoomPort;
|
|
||||||
|
|
||||||
static struct option long_options[] = {
|
|
||||||
// clang-format off
|
|
||||||
{"debug", no_argument, 0, 'd'},
|
|
||||||
{"config", required_argument, 0, 'c'},
|
|
||||||
{"fullscreen", no_argument, 0, 'f'},
|
|
||||||
{"help", no_argument, 0, 'h'},
|
|
||||||
{"game", required_argument, 0, 'g'},
|
|
||||||
{"multiplayer", required_argument, 0, 'm'},
|
|
||||||
{"program", optional_argument, 0, 'p'},
|
|
||||||
{"user", required_argument, 0, 'u'},
|
|
||||||
{"version", no_argument, 0, 'v'},
|
|
||||||
{"input-profile", no_argument, 0, 'i'},
|
|
||||||
{"null-render", no_argument, 0, 'n'},
|
|
||||||
{"singlecore", no_argument, 0, 's'},
|
|
||||||
{"filter", no_argument, 0, 'x'},
|
|
||||||
{0, 0, 0, 0},
|
|
||||||
// clang-format on
|
|
||||||
};
|
|
||||||
|
|
||||||
while (optind < argc) {
|
|
||||||
int arg = getopt_long(argc, argv, "g:fhvcip::c:u:d:", long_options, &option_index);
|
|
||||||
if (arg != -1) {
|
|
||||||
switch (char(arg)) {
|
|
||||||
case 'd':
|
|
||||||
override_gdb_port = uint16_t(atoi(optarg));
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
config_path = optarg;
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
fullscreen = true;
|
|
||||||
LOG_INFO(Frontend, "Starting in fullscreen mode...");
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
PrintHelp(argv[0]);
|
|
||||||
return SDL_APP_FAILURE;
|
|
||||||
case 'g':
|
|
||||||
filepath = std::string(optarg);
|
|
||||||
break;
|
|
||||||
case 'i': {
|
|
||||||
input_profile = std::string(optarg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'm': {
|
|
||||||
use_multiplayer = true;
|
|
||||||
const std::string str_arg(optarg);
|
|
||||||
// regex to check if the format is nickname:password@ip:port
|
|
||||||
// with optional :password
|
|
||||||
const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
|
|
||||||
if (!std::regex_match(str_arg, re)) {
|
|
||||||
std::cout << "Wrong format for option --multiplayer\n";
|
|
||||||
PrintHelp(argv[0]);
|
|
||||||
return SDL_APP_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::smatch match;
|
|
||||||
std::regex_search(str_arg, match, re);
|
|
||||||
ASSERT(match.size() == 5);
|
|
||||||
nickname = match[1];
|
|
||||||
password = match[2];
|
|
||||||
address = match[3];
|
|
||||||
if (!match[4].str().empty()) {
|
|
||||||
port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0));
|
|
||||||
}
|
|
||||||
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
|
|
||||||
if (!std::regex_match(nickname, nickname_re)) {
|
|
||||||
LOG_ERROR(Frontend, "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
|
|
||||||
return SDL_APP_FAILURE;
|
|
||||||
}
|
|
||||||
if (address.empty()) {
|
|
||||||
LOG_ERROR(Frontend, "Address to room must not be empty");
|
|
||||||
return SDL_APP_FAILURE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'p':
|
|
||||||
program_args = argv[optind];
|
|
||||||
++optind;
|
|
||||||
break;
|
|
||||||
case 'u':
|
|
||||||
selected_user = atoi(optarg);
|
|
||||||
break;
|
|
||||||
case 'v':
|
|
||||||
PrintVersion();
|
|
||||||
return SDL_APP_FAILURE;
|
|
||||||
case 'n':
|
|
||||||
force_null_render = true;
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
force_single_core = true;
|
|
||||||
break;
|
|
||||||
case 'x':
|
|
||||||
log_filter = argv[optind];
|
|
||||||
++optind;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
#ifdef _WIN32
|
|
||||||
filepath = Common::UTF16ToUTF8(argv_w[optind]);
|
|
||||||
#else
|
#else
|
||||||
filepath = argv[optind];
|
wchar_t **argv_w = nullptr;
|
||||||
#endif
|
#endif
|
||||||
optind++;
|
Core::LaunchParams lp = Core::ParseLaunchParams(state->system, argc, argv, argv_w);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SdlConfig config{config_path};
|
|
||||||
|
|
||||||
// apply the log_filter setting
|
|
||||||
// the logger was initialized before and doesn't pick up the filter on its own
|
|
||||||
Common::Log::Filter filter;
|
|
||||||
filter.ParseFilterString(log_filter.value_or(Settings::values.log_filter.GetValue()));
|
|
||||||
Common::Log::SetGlobalFilter(filter);
|
|
||||||
|
|
||||||
if (!program_args.empty()) {
|
|
||||||
Settings::values.program_args = program_args;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!input_profile.empty()) {
|
|
||||||
auto& players = Settings::values.players.GetValue();
|
|
||||||
players[0].profile_name = input_profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected_user.has_value()) {
|
|
||||||
Settings::values.current_user = std::clamp(*selected_user, 0, 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (override_gdb_port.has_value()) {
|
|
||||||
Settings::values.use_gdbstub = true;
|
|
||||||
Settings::values.gdbstub_port = *override_gdb_port;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (force_single_core) {
|
|
||||||
Settings::values.use_multi_core = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (force_null_render) {
|
|
||||||
Settings::values.renderer_backend = Settings::RendererBackend::Null;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
LocalFree(argv_w);
|
LocalFree(argv_w);
|
||||||
#endif
|
#endif
|
||||||
|
SdlConfig config{lp.config_path};
|
||||||
|
|
||||||
if (filepath.empty()) {
|
Core::ApplyLaunchParams(lp);
|
||||||
|
|
||||||
|
if (lp.filepath.empty()) {
|
||||||
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
|
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
|
||||||
return SDL_APP_FAILURE;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
@@ -386,14 +212,14 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
|||||||
case Settings::RendererBackend::OpenGL_GLSL:
|
case Settings::RendererBackend::OpenGL_GLSL:
|
||||||
case Settings::RendererBackend::OpenGL_GLASM:
|
case Settings::RendererBackend::OpenGL_GLASM:
|
||||||
case Settings::RendererBackend::OpenGL_SPIRV:
|
case Settings::RendererBackend::OpenGL_SPIRV:
|
||||||
state->emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, state->system, fullscreen);
|
state->emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, state->system, lp.fullscreen);
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
case Settings::RendererBackend::Vulkan:
|
case Settings::RendererBackend::Vulkan:
|
||||||
state->emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, state->system, fullscreen);
|
state->emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, state->system, lp.fullscreen);
|
||||||
break;
|
break;
|
||||||
case Settings::RendererBackend::Null:
|
case Settings::RendererBackend::Null:
|
||||||
state->emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, state->system, fullscreen);
|
state->emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, state->system, lp.fullscreen);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOG_CRITICAL(Frontend, "Invalid renderer backend");
|
LOG_CRITICAL(Frontend, "Invalid renderer backend");
|
||||||
@@ -413,12 +239,12 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
|||||||
Service::AM::FrontendAppletParameters load_parameters{
|
Service::AM::FrontendAppletParameters load_parameters{
|
||||||
.applet_id = Service::AM::AppletId::Application,
|
.applet_id = Service::AM::AppletId::Application,
|
||||||
};
|
};
|
||||||
const Core::SystemResultStatus load_result = state->system.Load(*state->emu_window, filepath, load_parameters);
|
const Core::SystemResultStatus load_result = state->system.Load(*state->emu_window, lp.filepath, load_parameters);
|
||||||
switch (load_result) {
|
switch (load_result) {
|
||||||
case Core::SystemResultStatus::Success:
|
case Core::SystemResultStatus::Success:
|
||||||
break; // Expected case
|
break; // Expected case
|
||||||
case Core::SystemResultStatus::ErrorGetLoader:
|
case Core::SystemResultStatus::ErrorGetLoader:
|
||||||
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath);
|
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", lp.filepath);
|
||||||
return SDL_APP_FAILURE;
|
return SDL_APP_FAILURE;
|
||||||
case Core::SystemResultStatus::ErrorLoader:
|
case Core::SystemResultStatus::ErrorLoader:
|
||||||
LOG_CRITICAL(Frontend, "Failed to load ROM!");
|
LOG_CRITICAL(Frontend, "Failed to load ROM!");
|
||||||
@@ -440,14 +266,14 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
|||||||
return SDL_APP_FAILURE;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (use_multiplayer) {
|
if (lp.use_multiplayer) {
|
||||||
if (auto member = Network::GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
member->BindOnChatMessageReceived(OnMessageReceived);
|
member->BindOnChatMessageReceived(OnMessageReceived);
|
||||||
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
|
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
|
||||||
member->BindOnStateChanged(OnStateChanged);
|
member->BindOnStateChanged(OnStateChanged);
|
||||||
member->BindOnError(OnNetworkError);
|
member->BindOnError(OnNetworkError);
|
||||||
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, nickname);
|
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", lp.address, lp.port, lp.nickname);
|
||||||
member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password);
|
member->Join(lp.nickname, lp.address.c_str(), lp.port, 0, Network::NoPreferredIP, lp.password);
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(Network, "Could not access RoomMember");
|
LOG_ERROR(Network, "Could not access RoomMember");
|
||||||
return SDL_APP_FAILURE;
|
return SDL_APP_FAILURE;
|
||||||
|
|||||||
Reference in New Issue
Block a user