Compare commits

...

16 commits

Author SHA1 Message Date
OpenSauce04
3703dcec3e Updated translations via Transifex 2026-01-25 00:57:26 +00:00
OpenSauce04
64b7f7e5b1 Revert "core: Temporary workaround for MSVC compiler bug (#1505)"
This reverts commit b0fe4d190d.
2026-01-25 00:49:54 +00:00
OpenSauce04
fa3be2d9a5 externals: Updated SDL to 2.32.10 2026-01-25 00:44:51 +00:00
OpenSauce04
b900b635ad ci: Only build appimage-wayland for tagged jobs
This is to reduce stress on the CI/CD build queue and cache
2026-01-25 00:44:51 +00:00
David Griswold
3d954bc74d update language and use UP instead of DOWN on axis mapping 2026-01-25 00:27:39 +00:00
OpenSauce04
e10999fe81 plgldr: Fixed plugins that should load for all titles failing to load due to a missing check 2026-01-25 00:24:40 +00:00
PabloMK7
6020f48e06 Revert: video_core/renderer_vulkan: Add texture filtering (#1678) 2026-01-25 00:24:22 +00:00
Immersion95
c7dda3c444 qt: Mention in the tooltip that async presentation adds 1 frame of input lag (#1669)
Async Presentation (Vulkan) adds 1 frame of input lag - Mention in the tooltip
2026-01-25 00:24:05 +00:00
PabloMK7
e96e84e1b3 video_core: Fix custom textures when loading a savestate 2026-01-25 00:23:46 +00:00
PabloMK7
8c5161e88f core: Fix accurate multiplication loading (home menu and savestate) 2026-01-25 00:23:29 +00:00
David Griswold
15e06bd000 android: prioritize non-built-in displays (#1667) 2026-01-25 00:23:13 +00:00
PabloMK7
656ab12542 log: Fix compilation with gcc backtrace (#1668) 2026-01-25 00:22:57 +00:00
OpenSauce
933a9e5596 qt: Workaround for Qt directoryChanged event spam on macOS (#1665)
* qt: Workaround for Qt directoryChanged event spam on macOS

* Use steady_clock instead of system_clock
2026-01-21 21:35:17 +00:00
OpenSauce04
6fa51c2fe4 qt: Implement Update Channel setting 2026-01-21 21:34:40 +00:00
OpenSauce04
9dd0149ec7 android: Fix desynced default value for use_vsync setting 2026-01-21 21:30:55 +00:00
Marcin Serwin
b038cdaf86 citra_meta: search for Qt6::GuiPrivate before using it
Otherwise, the configuration on darwin fails for me with the following
error:

```
CMake Error at src/citra_meta/CMakeLists.txt:64 (target_link_libraries):
  Target "citra_meta" links to:

    Qt6::GuiPrivate

  but the target was not found.
```

Signed-off-by: Marcin Serwin <marcin@serwin.dev>
2026-01-21 21:29:54 +00:00
70 changed files with 6609 additions and 6572 deletions

View file

@ -38,11 +38,14 @@ jobs:
CCACHE_SLOPPINESS: time_macros CCACHE_SLOPPINESS: time_macros
OS: linux OS: linux
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
if: ${{ env.SHOULD_RUN == 'true' }}
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' }}
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
@ -50,18 +53,19 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-${{ matrix.target }}- ${{ runner.os }}-${{ matrix.target }}-
- name: Build - name: Build
if: ${{ env.SHOULD_RUN == 'true' }}
run: ./.ci/linux.sh run: ./.ci/linux.sh
- name: Move AppImage to artifacts directory - name: Move AppImage to artifacts directory
if: ${{ contains(matrix.target, 'appimage') }} if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
run: | run: |
mkdir -p artifacts mkdir -p artifacts
mv build/bundle/*.AppImage artifacts/ mv build/bundle/*.AppImage artifacts/
- name: Rename AppImage - name: Rename AppImage
if: ${{ matrix.target == 'appimage-wayland' }} if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }}
run: | run: |
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
- name: Upload - name: Upload
if: ${{ contains(matrix.target, 'appimage') }} if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

514
dist/languages/de.ts vendored

File diff suppressed because it is too large Load diff

512
dist/languages/el.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

512
dist/languages/fi.ts vendored

File diff suppressed because it is too large Load diff

514
dist/languages/fr.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

512
dist/languages/id.ts vendored

File diff suppressed because it is too large Load diff

514
dist/languages/it.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

512
dist/languages/nb.ts vendored

File diff suppressed because it is too large Load diff

512
dist/languages/nl.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

514
dist/languages/sv.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

2
externals/sdl2/SDL vendored

@ -1 +1 @@
Subproject commit 2359383fc187386204c3bb22de89655a494cd128 Subproject commit 5d249570393f7a37e037abf22cd6012a4cc56a71

View file

@ -50,7 +50,7 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
val currentDisplayId = context.display.displayId val currentDisplayId = context.display.displayId
val displays = dm.displays val displays = dm.displays
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION); val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
return displays.firstOrNull { val extDisplays = displays.filter {
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId } val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable
isNotDefaultOrPresentable && isNotDefaultOrPresentable &&
@ -59,6 +59,10 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
it.state != Display.STATE_OFF && it.state != Display.STATE_OFF &&
it.isValid it.isValid
} }
// if there is a display called Built-In Display or Built-In Screen, prioritize the OTHER screen
val selected = extDisplays.firstOrNull { ! it.name.contains("Built",true) }
?: extDisplays.firstOrNull()
return selected
} }
fun updateDisplay() { fun updateDisplay() {

View file

@ -255,7 +255,9 @@ class InputBindingSetting(
} else { } else {
buttonCode buttonCode
} }
writeAxisMapping(motionRange.axis, button, axisDir == '-') // use UP (-) to map vertical, but use RIGHT (+) to map horizontal
val inverted = if (isHorizontalOrientation()) axisDir == '-' else axisDir == '+'
writeAxisMapping(motionRange.axis, button, inverted)
val uiString = "${device.name}: Axis ${motionRange.axis}" + axisDir val uiString = "${device.name}: Axis ${motionRange.axis}" + axisDir
value = uiString value = uiString
} }

View file

@ -269,6 +269,8 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
system.GPU().ApplyPerProgramSettings(program_id);
std::unique_ptr<Frontend::GraphicsContext> cpu_context; std::unique_ptr<Frontend::GraphicsContext> cpu_context;
system.GPU().Renderer().Rasterizer()->LoadDefaultDiskResources(stop_run, system.GPU().Renderer().Rasterizer()->LoadDefaultDiskResources(stop_run,
&LoadDiskCacheProgress); &LoadDiskCacheProgress);

View file

@ -112,8 +112,6 @@
<string name="controller_dpad_axis_description">És possible que alguns controladors no puguen assignar el D-pad com un eix. Si aquest és el cas, utilitza la secció D-Pad (botons).</string> <string name="controller_dpad_axis_description">És possible que alguns controladors no puguen assignar el D-pad com un eix. Si aquest és el cas, utilitza la secció D-Pad (botons).</string>
<string name="controller_dpad_button">D-Pad (Botó)</string> <string name="controller_dpad_button">D-Pad (Botó)</string>
<string name="controller_dpad_button_description">Assigna només el D-pad a aquests si tens problemes amb les assignacions de botons del D-Pad (Eix).</string> <string name="controller_dpad_button_description">Assigna només el D-pad a aquests si tens problemes amb les assignacions de botons del D-Pad (Eix).</string>
<string name="controller_axis_vertical">Eix Vertical</string>
<string name="controller_axis_horizontal">Eix Horitzontal</string>
<string name="direction_up">Amunt</string> <string name="direction_up">Amunt</string>
<string name="direction_down">Avall</string> <string name="direction_down">Avall</string>
<string name="direction_left">Esquerra</string> <string name="direction_left">Esquerra</string>

View file

@ -119,8 +119,6 @@
<string name="controller_dpad_axis_description">Nogle kontrollere er muligvis ikke i stand til at tilknytte deres D-Pad som en akse. Hvis det er tilfældet, skal du bruge afsnittet D-Pad (knapper).</string> <string name="controller_dpad_axis_description">Nogle kontrollere er muligvis ikke i stand til at tilknytte deres D-Pad som en akse. Hvis det er tilfældet, skal du bruge afsnittet D-Pad (knapper).</string>
<string name="controller_dpad_button">D-Pad (knapper)</string> <string name="controller_dpad_button">D-Pad (knapper)</string>
<string name="controller_dpad_button_description">Udfyld kun disse D-Pad, hvis du har problemer med opsætningen af D-Pad (akser).</string> <string name="controller_dpad_button_description">Udfyld kun disse D-Pad, hvis du har problemer med opsætningen af D-Pad (akser).</string>
<string name="controller_axis_vertical">Op/ned akse</string>
<string name="controller_axis_horizontal">Venstre/højre akse</string>
<string name="direction_up">Op</string> <string name="direction_up">Op</string>
<string name="direction_down">Ned</string> <string name="direction_down">Ned</string>
<string name="direction_left">Venstre</string> <string name="direction_left">Venstre</string>
@ -129,8 +127,6 @@
<string name="input_dialog_description">Tryk på eller flyt et input.</string> <string name="input_dialog_description">Tryk på eller flyt et input.</string>
<string name="input_binding">Inputbinding</string> <string name="input_binding">Inputbinding</string>
<string name="input_binding_description">Tryk på eller flyt et input for at binde det til %1$s.</string> <string name="input_binding_description">Tryk på eller flyt et input for at binde det til %1$s.</string>
<string name="input_binding_description_vertical_axis">Bevæg joysticket ned</string>
<string name="input_binding_description_horizontal_axis">Bevæg joysticket til højre</string>
<string name="button_home">HOME</string> <string name="button_home">HOME</string>
<string name="button_swap">Byt skærme</string> <string name="button_swap">Byt skærme</string>
<string name="button_turbo">Turbo</string> <string name="button_turbo">Turbo</string>

View file

@ -116,8 +116,6 @@
<string name="controller_dpad_axis_description">Es posible que algunos controladores no puedan asignar su D-pad como un eje. Si ese es el caso, utilice la sección D-Pad (botones).</string> <string name="controller_dpad_axis_description">Es posible que algunos controladores no puedan asignar su D-pad como un eje. Si ese es el caso, utilice la sección D-Pad (botones).</string>
<string name="controller_dpad_button">D-Pad (Botón)</string> <string name="controller_dpad_button">D-Pad (Botón)</string>
<string name="controller_dpad_button_description">Asigne solo el D-pad a éstos si tiene problemas con las asignaciones de botones del D-Pad (Eje).</string> <string name="controller_dpad_button_description">Asigne solo el D-pad a éstos si tiene problemas con las asignaciones de botones del D-Pad (Eje).</string>
<string name="controller_axis_vertical">Eje Vertical</string>
<string name="controller_axis_horizontal">Eje Horizontal</string>
<string name="direction_up">Arriba</string> <string name="direction_up">Arriba</string>
<string name="direction_down">Abajo</string> <string name="direction_down">Abajo</string>
<string name="direction_left">Izquierda</string> <string name="direction_left">Izquierda</string>
@ -126,8 +124,6 @@
<string name="input_dialog_description">Pulsa o mueve un botón/palanca.</string> <string name="input_dialog_description">Pulsa o mueve un botón/palanca.</string>
<string name="input_binding">Asignación de botones</string> <string name="input_binding">Asignación de botones</string>
<string name="input_binding_description">Pulsa o mueve un botón para asignarlo a %1$s.</string> <string name="input_binding_description">Pulsa o mueve un botón para asignarlo a %1$s.</string>
<string name="input_binding_description_vertical_axis">Mueve el joystick abajo</string>
<string name="input_binding_description_horizontal_axis">Mueve el joystick a la derecha</string>
<string name="button_home">HOME</string> <string name="button_home">HOME</string>
<string name="button_swap">Intercambiar Pantallas</string> <string name="button_swap">Intercambiar Pantallas</string>
<string name="button_turbo">Turbo</string> <string name="button_turbo">Turbo</string>

View file

@ -119,8 +119,6 @@
<string name="controller_dpad_axis_description">Niektóre kontrolery mogą nie być w stanie zmapować swojego D-pada jako osi. W takim przypadku należy użyć sekcji D-Pad (przyciski).</string> <string name="controller_dpad_axis_description">Niektóre kontrolery mogą nie być w stanie zmapować swojego D-pada jako osi. W takim przypadku należy użyć sekcji D-Pad (przyciski).</string>
<string name="controller_dpad_button">Krzyżak (Przycisk)</string> <string name="controller_dpad_button">Krzyżak (Przycisk)</string>
<string name="controller_dpad_button_description">Zmapuj D-pad na te ustawienia tylko wtedy, gdy masz problemy z mapowaniem przycisków D-Pada (Oś).</string> <string name="controller_dpad_button_description">Zmapuj D-pad na te ustawienia tylko wtedy, gdy masz problemy z mapowaniem przycisków D-Pada (Oś).</string>
<string name="controller_axis_vertical">Oś góra/dół</string>
<string name="controller_axis_horizontal">Oś lewa/prawa</string>
<string name="direction_up">Góra</string> <string name="direction_up">Góra</string>
<string name="direction_down">Dół</string> <string name="direction_down">Dół</string>
<string name="direction_left">Lewo</string> <string name="direction_left">Lewo</string>
@ -129,8 +127,6 @@
<string name="input_dialog_description">Naciśnij lub przenieś wejście.</string> <string name="input_dialog_description">Naciśnij lub przenieś wejście.</string>
<string name="input_binding">Powiązanie wejścia</string> <string name="input_binding">Powiązanie wejścia</string>
<string name="input_binding_description">Naciśnij lub przesuń wejście, aby powiązać je z %1$s.</string> <string name="input_binding_description">Naciśnij lub przesuń wejście, aby powiązać je z %1$s.</string>
<string name="input_binding_description_vertical_axis">Przesuń analog w dół.</string>
<string name="input_binding_description_horizontal_axis">Przesuń analog w prawo.</string>
<string name="button_home">HOME</string> <string name="button_home">HOME</string>
<string name="button_swap">Zamień ekrany</string> <string name="button_swap">Zamień ekrany</string>
<string name="button_turbo">Turbo</string> <string name="button_turbo">Turbo</string>

View file

@ -119,8 +119,6 @@
<string name="controller_dpad_axis_description">Alguns controles podem não ser capazes de mapear os D-pads para um eixo. Se esse for o caso, use a seção de D-Pad (Botões).</string> <string name="controller_dpad_axis_description">Alguns controles podem não ser capazes de mapear os D-pads para um eixo. Se esse for o caso, use a seção de D-Pad (Botões).</string>
<string name="controller_dpad_button">D-Pad (Botão)</string> <string name="controller_dpad_button">D-Pad (Botão)</string>
<string name="controller_dpad_button_description">Só mapeie o D-pad para isso se você se você estiver encontrando problemas com o mapeamento de botão do D-Pad (Eixo).</string> <string name="controller_dpad_button_description">Só mapeie o D-pad para isso se você se você estiver encontrando problemas com o mapeamento de botão do D-Pad (Eixo).</string>
<string name="controller_axis_vertical">Eixo vertical</string>
<string name="controller_axis_horizontal">Eixo horizontal</string>
<string name="direction_up">Cima</string> <string name="direction_up">Cima</string>
<string name="direction_down">Baixo</string> <string name="direction_down">Baixo</string>
<string name="direction_left">Esquerda</string> <string name="direction_left">Esquerda</string>
@ -129,8 +127,6 @@
<string name="input_dialog_description">Pressione ou mova uma entrada.</string> <string name="input_dialog_description">Pressione ou mova uma entrada.</string>
<string name="input_binding">Mapeamento de controles</string> <string name="input_binding">Mapeamento de controles</string>
<string name="input_binding_description">Pressione ou mova um botão/alavanca para mapear para %1$s.</string> <string name="input_binding_description">Pressione ou mova um botão/alavanca para mapear para %1$s.</string>
<string name="input_binding_description_vertical_axis">Mova o seu joystick para baixo</string>
<string name="input_binding_description_horizontal_axis">Mova o seu joystick para a direita</string>
<string name="button_home">Menu Principal</string> <string name="button_home">Menu Principal</string>
<string name="button_swap">Trocar telas</string> <string name="button_swap">Trocar telas</string>
<string name="button_turbo">Turbo</string> <string name="button_turbo">Turbo</string>

View file

@ -64,8 +64,6 @@
<string name="controller_triggers">Триггеры</string> <string name="controller_triggers">Триггеры</string>
<string name="controller_trigger">Триггер</string> <string name="controller_trigger">Триггер</string>
<string name="controller_dpad">Крестовина</string> <string name="controller_dpad">Крестовина</string>
<string name="controller_axis_vertical">Ось вверх/вниз</string>
<string name="controller_axis_horizontal">Ось влево/вправо</string>
<string name="input_dialog_title">Привязка %1$s %2$s</string> <string name="input_dialog_title">Привязка %1$s %2$s</string>
<string name="input_dialog_description">Нажмите или отклоните элемент управления.</string> <string name="input_dialog_description">Нажмите или отклоните элемент управления.</string>
<string name="input_binding">Привязки ввода</string> <string name="input_binding">Привязки ввода</string>

View file

@ -111,8 +111,6 @@
<string name="controller_dpad_axis_description">Bazı oyun kolları D-pad\'lerini bir eksen olarak atayamayabilir. Durum buysa D-Pad (butonlar) bölümünü kullanın.</string> <string name="controller_dpad_axis_description">Bazı oyun kolları D-pad\'lerini bir eksen olarak atayamayabilir. Durum buysa D-Pad (butonlar) bölümünü kullanın.</string>
<string name="controller_dpad_button">D-pad (Buton)</string> <string name="controller_dpad_button">D-pad (Buton)</string>
<string name="controller_dpad_button_description">Sadece D-Pad (Eksen) atamasında sorun yaşıyorsanız D-Pad\'inizi bunlara atayın.</string> <string name="controller_dpad_button_description">Sadece D-Pad (Eksen) atamasında sorun yaşıyorsanız D-Pad\'inizi bunlara atayın.</string>
<string name="controller_axis_vertical">Yukarı/Aşağı Eksen</string>
<string name="controller_axis_horizontal">Sol/Sağ Eksen</string>
<string name="direction_up">Yukarı</string> <string name="direction_up">Yukarı</string>
<string name="direction_down">Aşağı</string> <string name="direction_down">Aşağı</string>
<string name="direction_left">Sol</string> <string name="direction_left">Sol</string>

View file

@ -112,8 +112,6 @@
<string name="controller_dpad_axis_description">有些控制器可能无法将其方向键映射为轴。如果是这种情况,只能使用方向键(按键)部分。</string> <string name="controller_dpad_axis_description">有些控制器可能无法将其方向键映射为轴。如果是这种情况,只能使用方向键(按键)部分。</string>
<string name="controller_dpad_button">十字键(按键)</string> <string name="controller_dpad_button">十字键(按键)</string>
<string name="controller_dpad_button_description">仅当您在方向键 (轴) 映射遇到问题时,才将方向键映射到这些按键。</string> <string name="controller_dpad_button_description">仅当您在方向键 (轴) 映射遇到问题时,才将方向键映射到这些按键。</string>
<string name="controller_axis_vertical">上/下轴</string>
<string name="controller_axis_horizontal">左/右轴</string>
<string name="direction_up"></string> <string name="direction_up"></string>
<string name="direction_down"></string> <string name="direction_down"></string>
<string name="direction_left"></string> <string name="direction_left"></string>

View file

@ -112,8 +112,6 @@
<string name="controller_dpad_axis_description">Einige Controller sind nicht in der Lage, das Steuerkreuz als Achse zuzuordnen. Wenn das der Fall ist, dann nutze den Abschnitt: „Steuerkreuz (Knöpfe)“.</string> <string name="controller_dpad_axis_description">Einige Controller sind nicht in der Lage, das Steuerkreuz als Achse zuzuordnen. Wenn das der Fall ist, dann nutze den Abschnitt: „Steuerkreuz (Knöpfe)“.</string>
<string name="controller_dpad_button">Steuerkreuz (Knöpfe)</string> <string name="controller_dpad_button">Steuerkreuz (Knöpfe)</string>
<string name="controller_dpad_button_description">Nutze diese Funktion nur, wenn du Probleme mit der Funktion „Steuerkreuz (Achse) hast“.</string> <string name="controller_dpad_button_description">Nutze diese Funktion nur, wenn du Probleme mit der Funktion „Steuerkreuz (Achse) hast“.</string>
<string name="controller_axis_vertical">Achse (Oben/Unten)</string>
<string name="controller_axis_horizontal">Achse (Links/Rechts)</string>
<string name="direction_up">Hoch</string> <string name="direction_up">Hoch</string>
<string name="direction_down">Runter</string> <string name="direction_down">Runter</string>
<string name="direction_left">Links</string> <string name="direction_left">Links</string>

View file

@ -6,8 +6,6 @@
<string name="controller_c">C-Tikku</string> <string name="controller_c">C-Tikku</string>
<string name="controller_triggers">Liipaisimet</string> <string name="controller_triggers">Liipaisimet</string>
<string name="controller_dpad">D-Pad</string> <string name="controller_dpad">D-Pad</string>
<string name="controller_axis_vertical">Ylä/ala-akseli</string>
<string name="controller_axis_horizontal">Vasen/oikea akseli</string>
<!-- Generic buttons (Shared with lots of stuff) --> <!-- Generic buttons (Shared with lots of stuff) -->
<string name="generic_buttons">Nappulat</string> <string name="generic_buttons">Nappulat</string>
<string name="init_clock">Järjestelmän kellotyyppi</string> <string name="init_clock">Järjestelmän kellotyyppi</string>

View file

@ -112,8 +112,6 @@
<string name="controller_dpad_axis_description">Certaines manettes ne sont pas en mesure d\'affecter leur manette + à un axe. Dans ce cas, utilisez la section manette + (boutons).</string> <string name="controller_dpad_axis_description">Certaines manettes ne sont pas en mesure d\'affecter leur manette + à un axe. Dans ce cas, utilisez la section manette + (boutons).</string>
<string name="controller_dpad_button">Manette + (bouton)</string> <string name="controller_dpad_button">Manette + (bouton)</string>
<string name="controller_dpad_button_description">N\'affectez la manette + à ces boutons que si vous rencontrez des problèmes avec l\'affectation des boutons de la manette + (axes).</string> <string name="controller_dpad_button_description">N\'affectez la manette + à ces boutons que si vous rencontrez des problèmes avec l\'affectation des boutons de la manette + (axes).</string>
<string name="controller_axis_vertical">Axe Haut/Bas</string>
<string name="controller_axis_horizontal">Axe Gauche/Droite</string>
<string name="direction_up">Haut</string> <string name="direction_up">Haut</string>
<string name="direction_down">Bas</string> <string name="direction_down">Bas</string>
<string name="direction_left">Gauche</string> <string name="direction_left">Gauche</string>

View file

@ -124,8 +124,6 @@ Divertiti usando l\'emulatore!</string>
<string name="controller_dpad_axis_description">Alcuni controller potrebbero non essere in grado di mappare i tasti direzionali come un asse. Se questo è il caso, usa la sezione Tasti direzionali (pulsante).</string> <string name="controller_dpad_axis_description">Alcuni controller potrebbero non essere in grado di mappare i tasti direzionali come un asse. Se questo è il caso, usa la sezione Tasti direzionali (pulsante).</string>
<string name="controller_dpad_button">Tasti direzionali (pulsante)</string> <string name="controller_dpad_button">Tasti direzionali (pulsante)</string>
<string name="controller_dpad_button_description">Mappa i tasti direzionali in questa sezione solo se riscontri problemi con le mappature della sezione Tasti direzionali (asse).</string> <string name="controller_dpad_button_description">Mappa i tasti direzionali in questa sezione solo se riscontri problemi con le mappature della sezione Tasti direzionali (asse).</string>
<string name="controller_axis_vertical">Asse verticale</string>
<string name="controller_axis_horizontal">Asse orizzontale</string>
<string name="direction_up">Su</string> <string name="direction_up">Su</string>
<string name="direction_down">Giù</string> <string name="direction_down">Giù</string>
<string name="direction_left">Sinistra</string> <string name="direction_left">Sinistra</string>
@ -134,8 +132,6 @@ Divertiti usando l\'emulatore!</string>
<string name="input_dialog_description">Premi o sposta un comando</string> <string name="input_dialog_description">Premi o sposta un comando</string>
<string name="input_binding">Assegnazione Input</string> <string name="input_binding">Assegnazione Input</string>
<string name="input_binding_description">Premi o muovi un comando per assegnarlo a %1$s.</string> <string name="input_binding_description">Premi o muovi un comando per assegnarlo a %1$s.</string>
<string name="input_binding_description_vertical_axis">Sposta il joystick verso il basso</string>
<string name="input_binding_description_horizontal_axis">Sposta il joystick verso destra</string>
<string name="button_home">Home</string> <string name="button_home">Home</string>
<string name="button_swap">Inverti schermi</string> <string name="button_swap">Inverti schermi</string>
<string name="button_turbo">Turbo</string> <string name="button_turbo">Turbo</string>

View file

@ -8,8 +8,6 @@
<string name="controller_c">C-Spak</string> <string name="controller_c">C-Spak</string>
<string name="controller_triggers">Utløser</string> <string name="controller_triggers">Utløser</string>
<string name="controller_dpad">Kontrollpluss</string> <string name="controller_dpad">Kontrollpluss</string>
<string name="controller_axis_vertical">Opp/Ned Akse</string>
<string name="controller_axis_horizontal">Venstre/Høyre Akse</string>
<string name="input_binding">Inngangsinnbinding</string> <string name="input_binding">Inngangsinnbinding</string>
<string name="input_binding_description">Trykk eller flytt en inngang for å binde den til %1$s.</string> <string name="input_binding_description">Trykk eller flytt en inngang for å binde den til %1$s.</string>
<string name="input_message_analog_only">Denne kontrollen må være bundet til en håndkontroller\'s analog spak eller kontrollpluss akse!</string> <string name="input_message_analog_only">Denne kontrollen må være bundet til en håndkontroller\'s analog spak eller kontrollpluss akse!</string>

View file

@ -83,8 +83,6 @@ Vink deze optie in de toekomst nogmaals aan om te zien of er ondersteuning is to
<string name="controller_dpad_axis_description">Sommige controllers kunnen hun D-pad mogelijk niet als as in kaart brengen. Als dat het geval is, gebruik dan de D-Pad (knoppen) sectie.</string> <string name="controller_dpad_axis_description">Sommige controllers kunnen hun D-pad mogelijk niet als as in kaart brengen. Als dat het geval is, gebruik dan de D-Pad (knoppen) sectie.</string>
<string name="controller_dpad_button">D-Pad (Knop)</string> <string name="controller_dpad_button">D-Pad (Knop)</string>
<string name="controller_dpad_button_description">Wijs het D-pad hier alleen aan toe als je problemen ondervindt met de toewijzing van de D-Pad (as)-knoppen.</string> <string name="controller_dpad_button_description">Wijs het D-pad hier alleen aan toe als je problemen ondervindt met de toewijzing van de D-Pad (as)-knoppen.</string>
<string name="controller_axis_vertical">Op/neer-as</string>
<string name="controller_axis_horizontal">Links/rechts as</string>
<string name="direction_up">Op</string> <string name="direction_up">Op</string>
<string name="direction_down">Neer</string> <string name="direction_down">Neer</string>
<string name="direction_left">Links</string> <string name="direction_left">Links</string>

View file

@ -119,8 +119,6 @@
<string name="controller_dpad_axis_description">Vissa styrenheter kanske inte kan mappa sina riktningsknappar som en axel. Om så är fallet, använd avsnittet Riktningsknappar (knappar).</string> <string name="controller_dpad_axis_description">Vissa styrenheter kanske inte kan mappa sina riktningsknappar som en axel. Om så är fallet, använd avsnittet Riktningsknappar (knappar).</string>
<string name="controller_dpad_button">Riktningsknappar (knapp)</string> <string name="controller_dpad_button">Riktningsknappar (knapp)</string>
<string name="controller_dpad_button_description">Mappa endast D-pad till dessa om du har problem med knappmappningarna för D-pad (axel).</string> <string name="controller_dpad_button_description">Mappa endast D-pad till dessa om du har problem med knappmappningarna för D-pad (axel).</string>
<string name="controller_axis_vertical">Axel upp/ner</string>
<string name="controller_axis_horizontal">Vänster/höger-axel</string>
<string name="direction_up">Upp</string> <string name="direction_up">Upp</string>
<string name="direction_down">Ner</string> <string name="direction_down">Ner</string>
<string name="direction_left">Vänster</string> <string name="direction_left">Vänster</string>
@ -129,8 +127,6 @@
<string name="input_dialog_description">Tryck på eller flytta en inmatning.</string> <string name="input_dialog_description">Tryck på eller flytta en inmatning.</string>
<string name="input_binding">Inmatningsbindning</string> <string name="input_binding">Inmatningsbindning</string>
<string name="input_binding_description">Tryck eller flytta en inmatning för att binda den till %1$s.</string> <string name="input_binding_description">Tryck eller flytta en inmatning för att binda den till %1$s.</string>
<string name="input_binding_description_vertical_axis">Rör din joystick neråt</string>
<string name="input_binding_description_horizontal_axis">Rör din joystick åt höger</string>
<string name="button_home">HOME</string> <string name="button_home">HOME</string>
<string name="button_swap">Byt skärm</string> <string name="button_swap">Byt skärm</string>
<string name="button_turbo">Turbo</string> <string name="button_turbo">Turbo</string>

View file

@ -130,8 +130,8 @@
<string name="controller_dpad_axis_description">Some controllers may not be able to map their D-pad as an axis. If that\'s the case, use the D-Pad (buttons) section.</string> <string name="controller_dpad_axis_description">Some controllers may not be able to map their D-pad as an axis. If that\'s the case, use the D-Pad (buttons) section.</string>
<string name="controller_dpad_button">D-Pad (Button)</string> <string name="controller_dpad_button">D-Pad (Button)</string>
<string name="controller_dpad_button_description">Only map the D-pad to these if you\'re facing issues with the D-Pad (Axis) button mappings.</string> <string name="controller_dpad_button_description">Only map the D-pad to these if you\'re facing issues with the D-Pad (Axis) button mappings.</string>
<string name="controller_axis_vertical">Up/Down Axis</string> <string name="controller_axis_vertical">Vertical Axis</string>
<string name="controller_axis_horizontal">Left/Right Axis</string> <string name="controller_axis_horizontal">Horizontal Axis</string>
<string name="direction_up">Up</string> <string name="direction_up">Up</string>
<string name="direction_down">Down</string> <string name="direction_down">Down</string>
<string name="direction_left">Left</string> <string name="direction_left">Left</string>
@ -140,8 +140,8 @@
<string name="input_dialog_description">Press or move an input.</string> <string name="input_dialog_description">Press or move an input.</string>
<string name="input_binding">Input Binding</string> <string name="input_binding">Input Binding</string>
<string name="input_binding_description">Press or move an input to bind it to %1$s.</string> <string name="input_binding_description">Press or move an input to bind it to %1$s.</string>
<string name="input_binding_description_vertical_axis">Move your joystick down</string> <string name="input_binding_description_vertical_axis">Press UP on your joystick.</string>
<string name="input_binding_description_horizontal_axis">Move your joystick right</string> <string name="input_binding_description_horizontal_axis">Press RIGHT on your joystick.</string>
<string name="button_a" translatable="false">A</string> <string name="button_a" translatable="false">A</string>
<string name="button_b" translatable="false">B</string> <string name="button_b" translatable="false">B</string>
<string name="button_select" translatable="false">SELECT</string> <string name="button_select" translatable="false">SELECT</string>

View file

@ -63,6 +63,7 @@ if (ENABLE_QT AND UNIX AND NOT APPLE)
endif() endif()
if (ENABLE_QT AND APPLE) if (ENABLE_QT AND APPLE)
find_package(Qt6 REQUIRED COMPONENTS GuiPrivate)
target_link_libraries(citra_meta PRIVATE Qt6::GuiPrivate) target_link_libraries(citra_meta PRIVATE Qt6::GuiPrivate)
endif() endif()

View file

@ -18,6 +18,7 @@
#include "core/3ds.h" #include "core/3ds.h"
#include "core/core.h" #include "core/core.h"
#include "core/frontend/framebuffer_layout.h" #include "core/frontend/framebuffer_layout.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h" #include "core/perf_stats.h"
#include "input_common/keyboard.h" #include "input_common/keyboard.h"
#include "input_common/main.h" #include "input_common/main.h"
@ -78,6 +79,10 @@ void EmuThread::run() {
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
u64 program_id{};
system.GetAppLoader().ReadProgramId(program_id);
system.GPU().ApplyPerProgramSettings(program_id);
system.GPU().Renderer().Rasterizer()->LoadDefaultDiskResources( system.GPU().Renderer().Rasterizer()->LoadDefaultDiskResources(
stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit LoadProgress(stage, value, total); emit LoadProgress(stage, value, total);

View file

@ -171,12 +171,21 @@ void GMainWindow::ShowCommandOutput(std::string title, std::string message) {
#endif #endif
} }
bool IsPrerelease() { bool IsPrereleaseBuild() {
return ((strstr(Common::g_build_fullname, "alpha") != NULL) || return ((strstr(Common::g_build_fullname, "alpha") != NULL) ||
(strstr(Common::g_build_fullname, "beta") != NULL) || (strstr(Common::g_build_fullname, "beta") != NULL) ||
(strstr(Common::g_build_fullname, "rc") != NULL)); (strstr(Common::g_build_fullname, "rc") != NULL));
} }
#ifdef ENABLE_QT_UPDATE_CHECKER
bool ShouldCheckForPrereleaseUpdates() {
const bool update_channel = UISettings::values.update_check_channel.GetValue();
const bool using_prerelease_channel =
(update_channel == UISettings::UpdateCheckChannels::PRERELEASE);
return (IsPrereleaseBuild() || using_prerelease_channel);
}
#endif
GMainWindow::GMainWindow(Core::System& system_) GMainWindow::GMainWindow(Core::System& system_)
: ui{std::make_unique<Ui::MainWindow>()}, system{system_}, movie{system.Movie()}, : ui{std::make_unique<Ui::MainWindow>()}, system{system_}, movie{system.Movie()},
user_data_migrator{this}, config{std::make_unique<QtConfig>()}, emu_thread{nullptr} { user_data_migrator{this}, config{std::make_unique<QtConfig>()}, emu_thread{nullptr} {
@ -406,7 +415,8 @@ GMainWindow::GMainWindow(Core::System& system_)
if (UISettings::values.check_for_update_on_start) { if (UISettings::values.check_for_update_on_start) {
update_future = QtConcurrent::run([]() -> QString { update_future = QtConcurrent::run([]() -> QString {
const std::optional<std::string> latest_release_tag = const std::optional<std::string> latest_release_tag =
UpdateChecker::GetLatestRelease(IsPrerelease()); UpdateChecker::GetLatestRelease(ShouldCheckForPrereleaseUpdates());
if (latest_release_tag && latest_release_tag.value() != Common::g_build_fullname) { if (latest_release_tag && latest_release_tag.value() != Common::g_build_fullname) {
return QString::fromStdString(latest_release_tag.value()); return QString::fromStdString(latest_release_tag.value());
} }
@ -4057,7 +4067,7 @@ void GMainWindow::OnEmulatorUpdateAvailable() {
update_prompt.exec(); update_prompt.exec();
if (update_prompt.button(QMessageBox::Yes) == update_prompt.clickedButton()) { if (update_prompt.button(QMessageBox::Yes) == update_prompt.clickedButton()) {
std::string update_page_url; std::string update_page_url;
if (IsPrerelease()) { if (ShouldCheckForPrereleaseUpdates()) {
update_page_url = "https://github.com/azahar-emu/azahar/releases"; update_page_url = "https://github.com/azahar-emu/azahar/releases";
} else { } else {
update_page_url = "https://azahar-emu.org/pages/download/"; update_page_url = "https://azahar-emu.org/pages/download/";

View file

@ -568,6 +568,7 @@ void QtConfig::ReadMiscellaneousValues() {
ReadBasicSetting(Settings::values.log_regex_filter); ReadBasicSetting(Settings::values.log_regex_filter);
ReadBasicSetting(Settings::values.enable_gamemode); ReadBasicSetting(Settings::values.enable_gamemode);
ReadBasicSetting(UISettings::values.check_for_update_on_start); ReadBasicSetting(UISettings::values.check_for_update_on_start);
ReadBasicSetting(UISettings::values.update_check_channel);
qt_config->endGroup(); qt_config->endGroup();
} }
@ -1139,7 +1140,7 @@ void QtConfig::SaveMiscellaneousValues() {
WriteBasicSetting(Settings::values.log_regex_filter); WriteBasicSetting(Settings::values.log_regex_filter);
WriteBasicSetting(Settings::values.enable_gamemode); WriteBasicSetting(Settings::values.enable_gamemode);
WriteBasicSetting(UISettings::values.check_for_update_on_start); WriteBasicSetting(UISettings::values.check_for_update_on_start);
WriteBasicSetting(UISettings::values.update_check_channel);
qt_config->endGroup(); qt_config->endGroup();
} }

View file

@ -44,7 +44,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
ui->toggle_gamemode->setVisible(false); ui->toggle_gamemode->setVisible(false);
#endif #endif
#ifndef ENABLE_QT_UPDATE_CHECKER #ifndef ENABLE_QT_UPDATE_CHECKER
ui->toggle_update_checker->setVisible(false); ui->updates_group->setVisible(false);
#endif #endif
SetupPerGameUI(); SetupPerGameUI();
@ -92,6 +92,8 @@ void ConfigureGeneral::SetConfiguration() {
ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse.GetValue()); ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse.GetValue());
ui->toggle_update_checker->setChecked( ui->toggle_update_checker->setChecked(
UISettings::values.check_for_update_on_start.GetValue()); UISettings::values.check_for_update_on_start.GetValue());
ui->update_channel_combobox->setCurrentIndex(
UISettings::values.update_check_channel.GetValue());
#ifdef __unix__ #ifdef __unix__
ui->toggle_gamemode->setChecked(Settings::values.enable_gamemode.GetValue()); ui->toggle_gamemode->setChecked(Settings::values.enable_gamemode.GetValue());
#endif #endif
@ -179,6 +181,7 @@ void ConfigureGeneral::ApplyConfiguration() {
UISettings::values.mute_when_in_background = ui->toggle_background_mute->isChecked(); UISettings::values.mute_when_in_background = ui->toggle_background_mute->isChecked();
UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked(); UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked();
UISettings::values.check_for_update_on_start = ui->toggle_update_checker->isChecked(); UISettings::values.check_for_update_on_start = ui->toggle_update_checker->isChecked();
UISettings::values.update_check_channel = ui->update_channel_combobox->currentIndex();
#ifdef __unix__ #ifdef __unix__
Settings::values.enable_gamemode = ui->toggle_gamemode->isChecked(); Settings::values.enable_gamemode = ui->toggle_gamemode->isChecked();
#endif #endif
@ -211,5 +214,5 @@ void ConfigureGeneral::SetupPerGameUI() {
ui->general_group->setVisible(false); ui->general_group->setVisible(false);
ui->button_reset_defaults->setVisible(false); ui->button_reset_defaults->setVisible(false);
ui->toggle_gamemode->setVisible(false); ui->toggle_gamemode->setVisible(false);
ui->toggle_update_checker->setVisible(false); ui->updates_group->setVisible(false);
} }

View file

@ -16,6 +16,43 @@
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="updates_group">
<property name="title">
<string>Updates</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QCheckBox" name="toggle_update_checker">
<property name="text">
<string>Check for updates</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="update_channel_label">
<property name="text">
<string>Update Channel</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="update_channel_combobox">
<item>
<property name="text">
<string>Stable</string>
</property>
</item>
<item>
<property name="text">
<string>Prerelease</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="general_group"> <widget class="QGroupBox" name="general_group">
<property name="title"> <property name="title">
@ -57,13 +94,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="toggle_update_checker">
<property name="text">
<string>Check for updates</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -315,7 +345,6 @@
<tabstop>toggle_check_exit</tabstop> <tabstop>toggle_check_exit</tabstop>
<tabstop>toggle_background_pause</tabstop> <tabstop>toggle_background_pause</tabstop>
<tabstop>toggle_hide_mouse</tabstop> <tabstop>toggle_hide_mouse</tabstop>
<tabstop>toggle_update_checker</tabstop>
<tabstop>button_reset_defaults</tabstop> <tabstop>button_reset_defaults</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>

View file

@ -234,7 +234,7 @@
<item> <item>
<widget class="QCheckBox" name="toggle_async_present"> <widget class="QCheckBox" name="toggle_async_present">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Perform presentation on separate threads. Improves performance when using Vulkan in most applications.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Perform presentation on separate threads. Improves performance when using Vulkan in most applications. Adds ~1 frame of input lag.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Enable async presentation</string> <string>Enable async presentation</string>

View file

@ -1113,13 +1113,21 @@ const QStringList GameList::supported_file_extensions = {
}; };
void GameList::RefreshGameDirectory() { void GameList::RefreshGameDirectory() {
// Do not scan directories when the system is powered on, it will be // Do not scan directories when the system is powered on, it will be
// repopulated on shutdown anyways. // repopulated on shutdown anyways.
if (Core::System::GetInstance().IsPoweredOn()) { if (Core::System::GetInstance().IsPoweredOn()) {
return; return;
} }
const auto time_now = std::chrono::steady_clock::now();
// Max of 1 refresh every 1 second.
if (time_last_refresh + std::chrono::seconds(1) > time_now) {
return;
}
time_last_refresh = time_now;
if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) { if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) {
LOG_INFO(Frontend, "Change detected in the applications directory. Reloading game list."); LOG_INFO(Frontend, "Change detected in the applications directory. Reloading game list.");
PopulateAsync(UISettings::values.game_dirs); PopulateAsync(UISettings::values.game_dirs);

View file

@ -151,6 +151,8 @@ private:
friend class GameListSearchField; friend class GameListSearchField;
const PlayTime::PlayTimeManager& play_time_manager; const PlayTime::PlayTimeManager& play_time_manager;
std::chrono::time_point<std::chrono::steady_clock> time_last_refresh;
}; };
class GameListPlaceholder : public QWidget { class GameListPlaceholder : public QWidget {

View file

@ -59,6 +59,12 @@ enum class GameListText : s32 {
ListEnd, ///< Keep this at the end of the enum. ListEnd, ///< Keep this at the end of the enum.
}; };
class UpdateCheckChannels {
public:
static constexpr int STABLE = 0;
static constexpr int PRERELEASE = 1;
};
struct Values { struct Values {
QByteArray geometry; QByteArray geometry;
QByteArray state; QByteArray state;
@ -84,6 +90,8 @@ struct Values {
Settings::Setting<bool> mute_when_in_background{false, "muteWhenInBackground"}; Settings::Setting<bool> mute_when_in_background{false, "muteWhenInBackground"};
Settings::Setting<bool> hide_mouse{false, "hideInactiveMouse"}; Settings::Setting<bool> hide_mouse{false, "hideInactiveMouse"};
Settings::Setting<bool> check_for_update_on_start{true, "check_for_update_on_start"}; Settings::Setting<bool> check_for_update_on_start{true, "check_for_update_on_start"};
Settings::Setting<int> update_check_channel{UpdateCheckChannels::STABLE,
"update_check_channel"};
Settings::Setting<std::string> inserted_cartridge{"", "inserted_cartridge"}; Settings::Setting<std::string> inserted_cartridge{"", "inserted_cartridge"};

View file

@ -474,6 +474,10 @@ int LaunchSdlFrontend(int argc, char** argv) {
} }
}); });
u64 program_id{};
system.GetAppLoader().ReadProgramId(program_id);
system.GPU().ApplyPerProgramSettings(program_id);
std::atomic_bool stop_run; std::atomic_bool stop_run;
system.GPU().Renderer().Rasterizer()->LoadDefaultDiskResources( system.GPU().Renderer().Rasterizer()->LoadDefaultDiskResources(
stop_run, [](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { stop_run, [](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {

View file

@ -330,9 +330,9 @@ private:
} }
backend_thread.request_stop(); backend_thread.request_stop();
backend_thread.join(); backend_thread.join();
const auto signal_entry = const auto signal_entry = CreateEntry(
CreateEntry(Class::Log, Level::Critical, "?", 0, "?", Class::Log, Level::Critical, "?", 0, "?",
fmt::vformat("Received signal {}", fmt::make_format_args(sig))); fmt::vformat("Received signal {}", fmt::make_format_args(sig)), time_origin);
ForEachBackend([&signal_entry](Backend& backend) { ForEachBackend([&signal_entry](Backend& backend) {
backend.EnableForStacktrace(); backend.EnableForStacktrace();
backend.Write(signal_entry); backend.Write(signal_entry);
@ -345,12 +345,13 @@ private:
abort(); abort();
} }
line.pop_back(); // Remove newline line.pop_back(); // Remove newline
const auto frame_entry = const auto frame_entry = CreateEntry(Class::Log, Level::Critical, "?", 0, "?",
CreateEntry(Class::Log, Level::Critical, "?", 0, "?", std::move(line)); std::move(line), time_origin);
ForEachBackend([&frame_entry](Backend& backend) { backend.Write(frame_entry); }); ForEachBackend([&frame_entry](Backend& backend) { backend.Write(frame_entry); });
} }
using namespace std::literals; using namespace std::literals;
const auto rip_entry = CreateEntry(Class::Log, Level::Critical, "?", 0, "?", "RIP"s); const auto rip_entry =
CreateEntry(Class::Log, Level::Critical, "?", 0, "?", "RIP"s, time_origin);
ForEachBackend([&rip_entry](Backend& backend) { ForEachBackend([&rip_entry](Backend& backend) {
backend.Write(rip_entry); backend.Write(rip_entry);
backend.Flush(); backend.Flush();

View file

@ -515,7 +515,11 @@ struct Values {
SwitchableSetting<bool> use_hw_shader{true, "use_hw_shader"}; SwitchableSetting<bool> use_hw_shader{true, "use_hw_shader"};
SwitchableSetting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"}; SwitchableSetting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"};
SwitchableSetting<bool> shaders_accurate_mul{true, "shaders_accurate_mul"}; SwitchableSetting<bool> shaders_accurate_mul{true, "shaders_accurate_mul"};
#ifdef ANDROID // TODO: Fuck this -OS
SwitchableSetting<bool> use_vsync{false, "use_vsync"};
#else
SwitchableSetting<bool> use_vsync{true, "use_vsync"}; SwitchableSetting<bool> use_vsync{true, "use_vsync"};
#endif
Setting<bool> use_shader_jit{true, "use_shader_jit"}; Setting<bool> use_shader_jit{true, "use_shader_jit"};
SwitchableSetting<u32, true> resolution_factor{1, 0, 10, "resolution_factor"}; SwitchableSetting<u32, true> resolution_factor{1, 0, 10, "resolution_factor"};
SwitchableSetting<double, true> frame_limit{100, 0, 1000, "frame_limit"}; SwitchableSetting<double, true> frame_limit{100, 0, 1000, "frame_limit"};

View file

@ -403,8 +403,6 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
kernel->UpdateCPUAndMemoryState(program_id, app_mem_mode, app_n3ds_hw_capabilities); kernel->UpdateCPUAndMemoryState(program_id, app_mem_mode, app_n3ds_hw_capabilities);
gpu->ReportLoadingProgramID(program_id);
// Restore any parameters that should be carried through a reset. // Restore any parameters that should be carried through a reset.
if (auto apt = Service::APT::GetModule(*this)) { if (auto apt = Service::APT::GetModule(*this)) {
if (restore_deliver_arg.has_value()) { if (restore_deliver_arg.has_value()) {
@ -896,19 +894,26 @@ void System::serialize(Archive& ar, const unsigned int file_version) {
timing->UnlockEventQueue(); timing->UnlockEventQueue();
cheat_engine.Connect(cheats_pid); cheat_engine.Connect(cheats_pid);
if (Settings::values.custom_textures) {
custom_tex_manager->FindCustomTextures();
}
// Re-register gpu callback, because gsp service changed after service_manager got // Re-register gpu callback, because gsp service changed after service_manager got
// serialized // serialized
auto gsp = service_manager->GetService<Service::GSP::GSP_GPU>("gsp::Gpu"); auto gsp = service_manager->GetService<Service::GSP::GSP_GPU>("gsp::Gpu");
gpu->SetInterruptHandler( gpu->SetInterruptHandler(
[gsp](Service::GSP::InterruptId interrupt_id) { gsp->SignalInterrupt(interrupt_id); }); [gsp](Service::GSP::InterruptId interrupt_id) { gsp->SignalInterrupt(interrupt_id); });
// Switch the shader cache to the title running when the savestate was created // Apply per program settings and switch the shader cache to the title running when the
// savestate was created.
// TODO(PabloMK7): Find better way to obtain the program ID.
const u32 thread_id = gsp->GetActiveClientThreadId(); const u32 thread_id = gsp->GetActiveClientThreadId();
if (thread_id != std::numeric_limits<u32>::max()) { if (thread_id != std::numeric_limits<u32>::max()) {
const auto thread = kernel->GetThreadByID(thread_id); const auto thread = kernel->GetThreadByID(thread_id);
if (thread) { if (thread) {
const std::shared_ptr<Kernel::Process> process = thread->owner_process.lock(); const std::shared_ptr<Kernel::Process> process = thread->owner_process.lock();
if (process) { if (process) {
gpu->ApplyPerProgramSettings(process->codeset->program_id);
gpu->Renderer().Rasterizer()->SwitchDiskResources(process->codeset->program_id); gpu->Renderer().Rasterizer()->SwitchDiskResources(process->codeset->program_id);
} }
} }

View file

@ -693,6 +693,7 @@ Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx,
Common::Hacks::HackAllowMode::DISALLOW) != Common::Hacks::HackAllowMode::DISALLOW; Common::Hacks::HackAllowMode::DISALLOW) != Common::Hacks::HackAllowMode::DISALLOW;
auto& gpu = system.GPU(); auto& gpu = system.GPU();
gpu.ApplyPerProgramSettings(process->codeset->program_id);
gpu.GetRightEyeDisabler().SetEnabled(right_eye_disable_allow); gpu.GetRightEyeDisabler().SetEnabled(right_eye_disable_allow);
gpu.PicaCore().vs_setup.requires_fixup = requires_shader_fixup; gpu.PicaCore().vs_setup.requires_fixup = requires_shader_fixup;
gpu.PicaCore().gs_setup.requires_fixup = requires_shader_fixup; gpu.PicaCore().gs_setup.requires_fixup = requires_shader_fixup;

View file

@ -115,9 +115,10 @@ void PLG_LDR::OnProcessRun(Kernel::Process& process, Kernel::KernelSystem& kerne
} }
} }
FileSys::Plugin3GXLoader plugin_loader; FileSys::Plugin3GXLoader plugin_loader;
const auto low_title_Id = plgldr_context.user_load_parameters.low_title_Id;
if (plgldr_context.use_user_load_parameters && if (plgldr_context.use_user_load_parameters &&
plgldr_context.user_load_parameters.low_title_Id == (low_title_Id == static_cast<u32>(process.codeset->program_id) ||
static_cast<u32>(process.codeset->program_id) && low_title_Id == 0 /* Should load for any title */) &&
plgldr_context.user_load_parameters.path[0]) { plgldr_context.user_load_parameters.path[0]) {
std::string plugin_file = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + std::string plugin_file = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
std::string(plgldr_context.user_load_parameters.path + 1); std::string(plgldr_context.user_load_parameters.path + 1);

View file

@ -178,7 +178,7 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(u32* cmd_buf, const Funct
void ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context) { void ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context) {
auto itr = handlers.find(context.CommandHeader().command_id.Value()); auto itr = handlers.find(context.CommandHeader().command_id.Value());
const FunctionInfoBase* info = itr == handlers.end() ? nullptr : &itr->second; const FunctionInfoBase* info = itr == handlers.end() ? nullptr : &itr->second;
if (info == nullptr || !info->implemented) { if (info == nullptr || info->handler_callback == nullptr) {
context.ReportUnimplemented(); context.ReportUnimplemented();
return ReportUnimplementedFunction(context.CommandBuffer(), info); return ReportUnimplementedFunction(context.CommandBuffer(), info);
} }

View file

@ -82,7 +82,6 @@ private:
struct FunctionInfoBase { struct FunctionInfoBase {
u32 command_id; u32 command_id;
bool implemented;
HandlerFnP<ServiceFrameworkBase> handler_callback; HandlerFnP<ServiceFrameworkBase> handler_callback;
const char* name; const char* name;
}; };
@ -97,8 +96,6 @@ private:
void RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n); void RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n);
void ReportUnimplementedFunction(u32* cmd_buf, const FunctionInfoBase* info); void ReportUnimplementedFunction(u32* cmd_buf, const FunctionInfoBase* info);
void Empty(Kernel::HLERequestContext& ctx) {}
/// Identifier string used to connect to the service. /// Identifier string used to connect to the service.
std::string service_name; std::string service_name;
/// Maximum number of concurrent sessions that this service can handle. /// Maximum number of concurrent sessions that this service can handle.
@ -137,11 +134,9 @@ protected:
*/ */
constexpr FunctionInfo(u32 command_id, HandlerFnP<Self> handler_callback, const char* name) constexpr FunctionInfo(u32 command_id, HandlerFnP<Self> handler_callback, const char* name)
: FunctionInfoBase{ : FunctionInfoBase{
command_id, handler_callback != nullptr, command_id,
// Type-erase member function pointer by casting it down to the base class. // Type-erase member function pointer by casting it down to the base class.
handler_callback ? static_cast<HandlerFnP<ServiceFrameworkBase>>(handler_callback) static_cast<HandlerFnP<ServiceFrameworkBase>>(handler_callback), name} {}
: &ServiceFrameworkBase::Empty,
name} {}
}; };
/** /**

View file

@ -311,7 +311,7 @@ GraphicsDebugger& GPU::Debugger() {
return impl->gpu_debugger; return impl->gpu_debugger;
} }
void GPU::ReportLoadingProgramID(u64 program_ID) { void GPU::ApplyPerProgramSettings(u64 program_ID) {
auto hack = Common::Hacks::hack_manager.GetHack( auto hack = Common::Hacks::hack_manager.GetHack(
Common::Hacks::HackType::ACCURATE_MULTIPLICATION, program_ID); Common::Hacks::HackType::ACCURATE_MULTIPLICATION, program_ID);
bool use_accurate_mul = Settings::values.shaders_accurate_mul.GetValue(); bool use_accurate_mul = Settings::values.shaders_accurate_mul.GetValue();

View file

@ -94,7 +94,7 @@ public:
return *right_eye_disabler; return *right_eye_disabler;
} }
void ReportLoadingProgramID(u64 program_ID); void ApplyPerProgramSettings(u64 program_ID);
private: private:
void SubmitCmdList(u32 index); void SubmitCmdList(u32 index);

View file

@ -1,8 +1,7 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "common/settings.h"
#include "common/vector_math.h" #include "common/vector_math.h"
#include "video_core/renderer_vulkan/vk_blit_helper.h" #include "video_core/renderer_vulkan/vk_blit_helper.h"
#include "video_core/renderer_vulkan/vk_descriptor_update_queue.h" #include "video_core/renderer_vulkan/vk_descriptor_update_queue.h"
@ -17,19 +16,8 @@
#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag.h" #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag.h"
#include "video_core/host_shaders/vulkan_depth_to_buffer_comp.h" #include "video_core/host_shaders/vulkan_depth_to_buffer_comp.h"
// Texture filtering shader includes
#include "video_core/host_shaders/texture_filtering/bicubic_frag.h"
#include "video_core/host_shaders/texture_filtering/mmpx_frag.h"
#include "video_core/host_shaders/texture_filtering/refine_frag.h"
#include "video_core/host_shaders/texture_filtering/scale_force_frag.h"
#include "video_core/host_shaders/texture_filtering/x_gradient_frag.h"
#include "video_core/host_shaders/texture_filtering/xbrz_freescale_frag.h"
#include "video_core/host_shaders/texture_filtering/y_gradient_frag.h"
#include "vk_blit_helper.h"
namespace Vulkan { namespace Vulkan {
using Settings::TextureFilter;
using VideoCore::PixelFormat; using VideoCore::PixelFormat;
namespace { namespace {
@ -67,33 +55,8 @@ constexpr std::array<vk::DescriptorSetLayoutBinding, 2> TWO_TEXTURES_BINDINGS =
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, {1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
}}; }};
// Texture filtering descriptor set bindings
constexpr std::array<vk::DescriptorSetLayoutBinding, 1> SINGLE_TEXTURE_BINDINGS = {{
{0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
}};
constexpr std::array<vk::DescriptorSetLayoutBinding, 3> THREE_TEXTURES_BINDINGS = {{
{0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
{2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
}};
// Note: Removed FILTER_UTILITY_BINDINGS as texture filtering doesn't need shadow buffers
// Push constant structure for texture filtering
struct FilterPushConstants {
std::array<float, 2> tex_scale;
std::array<float, 2> tex_offset;
float res_scale; // For xBRZ filter
};
inline constexpr vk::PushConstantRange FILTER_PUSH_CONSTANT_RANGE{
.stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
.offset = 0,
.size = sizeof(FilterPushConstants),
};
inline constexpr vk::PushConstantRange PUSH_CONSTANT_RANGE{ inline constexpr vk::PushConstantRange PUSH_CONSTANT_RANGE{
.stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, .stageFlags = vk::ShaderStageFlagBits::eVertex,
.offset = 0, .offset = 0,
.size = sizeof(PushConstants), .size = sizeof(PushConstants),
}; };
@ -141,17 +104,12 @@ constexpr vk::PipelineDynamicStateCreateInfo PIPELINE_DYNAMIC_STATE_CREATE_INFO{
.dynamicStateCount = static_cast<u32>(DYNAMIC_STATES.size()), .dynamicStateCount = static_cast<u32>(DYNAMIC_STATES.size()),
.pDynamicStates = DYNAMIC_STATES.data(), .pDynamicStates = DYNAMIC_STATES.data(),
}; };
constexpr vk::PipelineColorBlendStateCreateInfo PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO{
constexpr vk::PipelineColorBlendAttachmentState COLOR_BLEND_ATTACHMENT{
.blendEnable = VK_FALSE,
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA,
};
constexpr vk::PipelineColorBlendStateCreateInfo PIPELINE_COLOR_BLEND_STATE_CREATE_INFO{
.logicOpEnable = VK_FALSE, .logicOpEnable = VK_FALSE,
.attachmentCount = 1, .logicOp = vk::LogicOp::eClear,
.pAttachments = &COLOR_BLEND_ATTACHMENT, .attachmentCount = 0,
.pAttachments = nullptr,
.blendConstants = std::array{0.0f, 0.0f, 0.0f, 0.0f},
}; };
constexpr vk::PipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO{ constexpr vk::PipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO{
.depthTestEnable = VK_TRUE, .depthTestEnable = VK_TRUE,
@ -170,9 +128,9 @@ inline constexpr vk::SamplerCreateInfo SAMPLER_CREATE_INFO{
.magFilter = filter, .magFilter = filter,
.minFilter = filter, .minFilter = filter,
.mipmapMode = vk::SamplerMipmapMode::eNearest, .mipmapMode = vk::SamplerMipmapMode::eNearest,
.addressModeU = vk::SamplerAddressMode::eClampToEdge, .addressModeU = vk::SamplerAddressMode::eClampToBorder,
.addressModeV = vk::SamplerAddressMode::eClampToEdge, .addressModeV = vk::SamplerAddressMode::eClampToBorder,
.addressModeW = vk::SamplerAddressMode::eClampToEdge, .addressModeW = vk::SamplerAddressMode::eClampToBorder,
.mipLodBias = 0.0f, .mipLodBias = 0.0f,
.anisotropyEnable = VK_FALSE, .anisotropyEnable = VK_FALSE,
.maxAnisotropy = 0.0f, .maxAnisotropy = 0.0f,
@ -185,14 +143,12 @@ inline constexpr vk::SamplerCreateInfo SAMPLER_CREATE_INFO{
}; };
constexpr vk::PipelineLayoutCreateInfo PipelineLayoutCreateInfo( constexpr vk::PipelineLayoutCreateInfo PipelineLayoutCreateInfo(
const vk::DescriptorSetLayout* set_layout, bool compute = false, bool filter = false) { const vk::DescriptorSetLayout* set_layout, bool compute = false) {
return vk::PipelineLayoutCreateInfo{ return vk::PipelineLayoutCreateInfo{
.setLayoutCount = 1, .setLayoutCount = 1,
.pSetLayouts = set_layout, .pSetLayouts = set_layout,
.pushConstantRangeCount = 1, .pushConstantRangeCount = 1,
.pPushConstantRanges = .pPushConstantRanges = (compute ? &COMPUTE_PUSH_CONSTANT_RANGE : &PUSH_CONSTANT_RANGE),
(compute ? &COMPUTE_PUSH_CONSTANT_RANGE
: (filter ? &FILTER_PUSH_CONSTANT_RANGE : &PUSH_CONSTANT_RANGE)),
}; };
} }
@ -229,20 +185,12 @@ BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_,
compute_provider{instance, scheduler.GetMasterSemaphore(), COMPUTE_BINDINGS}, compute_provider{instance, scheduler.GetMasterSemaphore(), COMPUTE_BINDINGS},
compute_buffer_provider{instance, scheduler.GetMasterSemaphore(), COMPUTE_BUFFER_BINDINGS}, compute_buffer_provider{instance, scheduler.GetMasterSemaphore(), COMPUTE_BUFFER_BINDINGS},
two_textures_provider{instance, scheduler.GetMasterSemaphore(), TWO_TEXTURES_BINDINGS, 16}, two_textures_provider{instance, scheduler.GetMasterSemaphore(), TWO_TEXTURES_BINDINGS, 16},
single_texture_provider{instance, scheduler.GetMasterSemaphore(), SINGLE_TEXTURE_BINDINGS,
16},
three_textures_provider{instance, scheduler.GetMasterSemaphore(), THREE_TEXTURES_BINDINGS,
16},
compute_pipeline_layout{ compute_pipeline_layout{
device.createPipelineLayout(PipelineLayoutCreateInfo(&compute_provider.Layout(), true))}, device.createPipelineLayout(PipelineLayoutCreateInfo(&compute_provider.Layout(), true))},
compute_buffer_pipeline_layout{device.createPipelineLayout( compute_buffer_pipeline_layout{device.createPipelineLayout(
PipelineLayoutCreateInfo(&compute_buffer_provider.Layout(), true))}, PipelineLayoutCreateInfo(&compute_buffer_provider.Layout(), true))},
two_textures_pipeline_layout{ two_textures_pipeline_layout{
device.createPipelineLayout(PipelineLayoutCreateInfo(&two_textures_provider.Layout()))}, device.createPipelineLayout(PipelineLayoutCreateInfo(&two_textures_provider.Layout()))},
single_texture_pipeline_layout{device.createPipelineLayout(
PipelineLayoutCreateInfo(&single_texture_provider.Layout(), false, true))},
three_textures_pipeline_layout{device.createPipelineLayout(
PipelineLayoutCreateInfo(&three_textures_provider.Layout(), false, true))},
full_screen_vert{Compile(HostShaders::FULL_SCREEN_TRIANGLE_VERT, full_screen_vert{Compile(HostShaders::FULL_SCREEN_TRIANGLE_VERT,
vk::ShaderStageFlagBits::eVertex, device)}, vk::ShaderStageFlagBits::eVertex, device)},
d24s8_to_rgba8_comp{Compile(HostShaders::VULKAN_D24S8_TO_RGBA8_COMP, d24s8_to_rgba8_comp{Compile(HostShaders::VULKAN_D24S8_TO_RGBA8_COMP,
@ -251,14 +199,6 @@ BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_,
vk::ShaderStageFlagBits::eCompute, device)}, vk::ShaderStageFlagBits::eCompute, device)},
blit_depth_stencil_frag{Compile(HostShaders::VULKAN_BLIT_DEPTH_STENCIL_FRAG, blit_depth_stencil_frag{Compile(HostShaders::VULKAN_BLIT_DEPTH_STENCIL_FRAG,
vk::ShaderStageFlagBits::eFragment, device)}, vk::ShaderStageFlagBits::eFragment, device)},
// Texture filtering shader modules
bicubic_frag{Compile(HostShaders::BICUBIC_FRAG, vk::ShaderStageFlagBits::eFragment, device)},
scale_force_frag{
Compile(HostShaders::SCALE_FORCE_FRAG, vk::ShaderStageFlagBits::eFragment, device)},
xbrz_frag{
Compile(HostShaders::XBRZ_FREESCALE_FRAG, vk::ShaderStageFlagBits::eFragment, device)},
mmpx_frag{Compile(HostShaders::MMPX_FRAG, vk::ShaderStageFlagBits::eFragment, device)},
refine_frag{Compile(HostShaders::REFINE_FRAG, vk::ShaderStageFlagBits::eFragment, device)},
d24s8_to_rgba8_pipeline{MakeComputePipeline(d24s8_to_rgba8_comp, compute_pipeline_layout)}, d24s8_to_rgba8_pipeline{MakeComputePipeline(d24s8_to_rgba8_comp, compute_pipeline_layout)},
depth_to_buffer_pipeline{ depth_to_buffer_pipeline{
MakeComputePipeline(depth_to_buffer_comp, compute_buffer_pipeline_layout)}, MakeComputePipeline(depth_to_buffer_comp, compute_buffer_pipeline_layout)},
@ -290,18 +230,10 @@ BlitHelper::~BlitHelper() {
device.destroyPipelineLayout(compute_pipeline_layout); device.destroyPipelineLayout(compute_pipeline_layout);
device.destroyPipelineLayout(compute_buffer_pipeline_layout); device.destroyPipelineLayout(compute_buffer_pipeline_layout);
device.destroyPipelineLayout(two_textures_pipeline_layout); device.destroyPipelineLayout(two_textures_pipeline_layout);
device.destroyPipelineLayout(single_texture_pipeline_layout);
device.destroyPipelineLayout(three_textures_pipeline_layout);
device.destroyShaderModule(full_screen_vert); device.destroyShaderModule(full_screen_vert);
device.destroyShaderModule(d24s8_to_rgba8_comp); device.destroyShaderModule(d24s8_to_rgba8_comp);
device.destroyShaderModule(depth_to_buffer_comp); device.destroyShaderModule(depth_to_buffer_comp);
device.destroyShaderModule(blit_depth_stencil_frag); device.destroyShaderModule(blit_depth_stencil_frag);
// Destroy texture filtering shader modules
device.destroyShaderModule(bicubic_frag);
device.destroyShaderModule(scale_force_frag);
device.destroyShaderModule(xbrz_frag);
device.destroyShaderModule(mmpx_frag);
device.destroyShaderModule(refine_frag);
device.destroyPipeline(depth_to_buffer_pipeline); device.destroyPipeline(depth_to_buffer_pipeline);
device.destroyPipeline(d24s8_to_rgba8_pipeline); device.destroyPipeline(d24s8_to_rgba8_pipeline);
device.destroyPipeline(depth_blit_pipeline); device.destroyPipeline(depth_blit_pipeline);
@ -310,7 +242,7 @@ BlitHelper::~BlitHelper() {
} }
void BindBlitState(vk::CommandBuffer cmdbuf, vk::PipelineLayout layout, void BindBlitState(vk::CommandBuffer cmdbuf, vk::PipelineLayout layout,
const VideoCore::TextureBlit& blit, const Surface& dest) { const VideoCore::TextureBlit& blit) {
const vk::Offset2D offset{ const vk::Offset2D offset{
.x = std::min<s32>(blit.dst_rect.left, blit.dst_rect.right), .x = std::min<s32>(blit.dst_rect.left, blit.dst_rect.right),
.y = std::min<s32>(blit.dst_rect.bottom, blit.dst_rect.top), .y = std::min<s32>(blit.dst_rect.bottom, blit.dst_rect.top),
@ -340,9 +272,8 @@ void BindBlitState(vk::CommandBuffer cmdbuf, vk::PipelineLayout layout,
}; };
cmdbuf.setViewport(0, viewport); cmdbuf.setViewport(0, viewport);
cmdbuf.setScissor(0, scissor); cmdbuf.setScissor(0, scissor);
cmdbuf.pushConstants(layout, cmdbuf.pushConstants(layout, vk::ShaderStageFlagBits::eVertex, 0, sizeof(push_constants),
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0, &push_constants);
sizeof(push_constants), &push_constants);
} }
bool BlitHelper::BlitDepthStencil(Surface& source, Surface& dest, bool BlitHelper::BlitDepthStencil(Surface& source, Surface& dest,
@ -369,12 +300,12 @@ bool BlitHelper::BlitDepthStencil(Surface& source, Surface& dest,
}; };
renderpass_cache.BeginRendering(depth_pass); renderpass_cache.BeginRendering(depth_pass);
scheduler.Record([blit, descriptor_set, &dest, this](vk::CommandBuffer cmdbuf) { scheduler.Record([blit, descriptor_set, this](vk::CommandBuffer cmdbuf) {
const vk::PipelineLayout layout = two_textures_pipeline_layout; const vk::PipelineLayout layout = two_textures_pipeline_layout;
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, depth_blit_pipeline); cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, depth_blit_pipeline);
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, descriptor_set, {}); cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, descriptor_set, {});
BindBlitState(cmdbuf, layout, blit, dest); BindBlitState(cmdbuf, layout, blit);
cmdbuf.draw(3, 1, 0, 0); cmdbuf.draw(3, 1, 0, 0);
}); });
scheduler.MakeDirty(StateFlags::Pipeline); scheduler.MakeDirty(StateFlags::Pipeline);
@ -600,7 +531,7 @@ vk::Pipeline BlitHelper::MakeDepthStencilBlitPipeline() {
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.layout = two_textures_pipeline_layout, .layout = two_textures_pipeline_layout,
.renderPass = renderpass, .renderPass = renderpass,
@ -616,280 +547,4 @@ vk::Pipeline BlitHelper::MakeDepthStencilBlitPipeline() {
return VK_NULL_HANDLE; return VK_NULL_HANDLE;
} }
bool BlitHelper::Filter(Surface& surface, const VideoCore::TextureBlit& blit) {
const auto filter = Settings::values.texture_filter.GetValue();
const bool is_depth =
surface.type == VideoCore::SurfaceType::Depth ||
surface.type == VideoCore::SurfaceType::DepthStencil; // Skip filtering for depth textures
// and when no filter is selected
if (filter == Settings::TextureFilter::NoFilter || is_depth) {
return false;
} // Only filter base mipmap level
if (blit.src_level != 0) {
return true;
}
switch (filter) {
case TextureFilter::Anime4K:
FilterAnime4K(surface, blit);
break;
case TextureFilter::Bicubic:
FilterBicubic(surface, blit);
break;
case TextureFilter::ScaleForce:
FilterScaleForce(surface, blit);
break;
case TextureFilter::xBRZ:
FilterXbrz(surface, blit);
break;
case TextureFilter::MMPX:
FilterMMPX(surface, blit);
break;
default:
LOG_ERROR(Render_Vulkan, "Unknown texture filter {}", filter);
return false;
}
return true;
}
void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& blit) {
const bool is_depth = surface.type == VideoCore::SurfaceType::Depth ||
surface.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : surface.pixel_format;
auto pipeline = MakeFilterPipeline(refine_frag, three_textures_pipeline_layout, color_format);
FilterPassThreeTextures(surface, surface, surface, surface, pipeline,
three_textures_pipeline_layout, blit);
}
void BlitHelper::FilterBicubic(Surface& surface, const VideoCore::TextureBlit& blit) {
const bool is_depth = surface.type == VideoCore::SurfaceType::Depth ||
surface.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : surface.pixel_format;
auto pipeline = MakeFilterPipeline(bicubic_frag, single_texture_pipeline_layout, color_format);
FilterPass(surface, surface, pipeline, single_texture_pipeline_layout, blit);
}
void BlitHelper::FilterScaleForce(Surface& surface, const VideoCore::TextureBlit& blit) {
const bool is_depth = surface.type == VideoCore::SurfaceType::Depth ||
surface.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : surface.pixel_format;
auto pipeline =
MakeFilterPipeline(scale_force_frag, single_texture_pipeline_layout, color_format);
FilterPass(surface, surface, pipeline, single_texture_pipeline_layout, blit);
}
void BlitHelper::FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit) {
const bool is_depth = surface.type == VideoCore::SurfaceType::Depth ||
surface.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : surface.pixel_format;
auto pipeline = MakeFilterPipeline(xbrz_frag, single_texture_pipeline_layout, color_format);
FilterPass(surface, surface, pipeline, single_texture_pipeline_layout, blit);
}
void BlitHelper::FilterMMPX(Surface& surface, const VideoCore::TextureBlit& blit) {
const bool is_depth = surface.type == VideoCore::SurfaceType::Depth ||
surface.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : surface.pixel_format;
auto pipeline = MakeFilterPipeline(mmpx_frag, single_texture_pipeline_layout, color_format);
FilterPass(surface, surface, pipeline, single_texture_pipeline_layout, blit);
}
vk::Pipeline BlitHelper::MakeFilterPipeline(vk::ShaderModule fragment_shader,
vk::PipelineLayout layout,
VideoCore::PixelFormat color_format) {
const std::array stages = MakeStages(full_screen_vert, fragment_shader);
// Use the provided color format for render pass compatibility
const auto renderpass =
renderpass_cache.GetRenderpass(color_format, VideoCore::PixelFormat::Invalid, false);
vk::GraphicsPipelineCreateInfo pipeline_info = {
.stageCount = static_cast<u32>(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pDepthStencilState = nullptr,
.pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.layout = layout,
.renderPass = renderpass,
};
if (const auto result = device.createGraphicsPipeline({}, pipeline_info);
result.result == vk::Result::eSuccess) {
return result.value;
} else {
LOG_CRITICAL(Render_Vulkan, "Filter pipeline creation failed!");
UNREACHABLE();
}
}
void BlitHelper::FilterPass(Surface& source, Surface& dest, vk::Pipeline pipeline,
vk::PipelineLayout layout, const VideoCore::TextureBlit& blit) {
const auto texture_descriptor_set = single_texture_provider.Commit();
update_queue.AddImageSampler(texture_descriptor_set, 0, 0, source.ImageView(0), linear_sampler,
vk::ImageLayout::eGeneral);
const bool is_depth = dest.type == VideoCore::SurfaceType::Depth ||
dest.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : dest.pixel_format;
const auto depth_format = is_depth ? dest.pixel_format : VideoCore::PixelFormat::Invalid;
const auto renderpass = renderpass_cache.GetRenderpass(color_format, depth_format, false);
const RenderPass render_pass = {
.framebuffer = dest.Framebuffer(),
.render_pass = renderpass,
.render_area =
{
.offset = {0, 0},
.extent = {dest.GetScaledWidth(), dest.GetScaledHeight()},
},
};
renderpass_cache.BeginRendering(render_pass);
const float src_scale = static_cast<float>(source.GetResScale());
// Calculate normalized texture coordinates like OpenGL does
const auto src_extent = source.RealExtent(false); // Get unscaled texture extent
const float tex_scale_x =
static_cast<float>(blit.src_rect.GetWidth()) / static_cast<float>(src_extent.width);
const float tex_scale_y =
static_cast<float>(blit.src_rect.GetHeight()) / static_cast<float>(src_extent.height);
const float tex_offset_x =
static_cast<float>(blit.src_rect.left) / static_cast<float>(src_extent.width);
const float tex_offset_y =
static_cast<float>(blit.src_rect.bottom) / static_cast<float>(src_extent.height);
scheduler.Record([pipeline, layout, texture_descriptor_set, blit, tex_scale_x, tex_scale_y,
tex_offset_x, tex_offset_y, src_scale](vk::CommandBuffer cmdbuf) {
const FilterPushConstants push_constants{.tex_scale = {tex_scale_x, tex_scale_y},
.tex_offset = {tex_offset_x, tex_offset_y},
.res_scale = src_scale};
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
// Bind single texture descriptor set
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0,
texture_descriptor_set, {});
cmdbuf.pushConstants(layout, FILTER_PUSH_CONSTANT_RANGE.stageFlags,
FILTER_PUSH_CONSTANT_RANGE.offset, FILTER_PUSH_CONSTANT_RANGE.size,
&push_constants);
// Set up viewport and scissor for filtering (don't use BindBlitState as it overwrites push
// constants)
const vk::Offset2D offset{
.x = std::min<s32>(blit.dst_rect.left, blit.dst_rect.right),
.y = std::min<s32>(blit.dst_rect.bottom, blit.dst_rect.top),
};
const vk::Extent2D extent{
.width = blit.dst_rect.GetWidth(),
.height = blit.dst_rect.GetHeight(),
};
const vk::Viewport viewport{
.x = static_cast<float>(offset.x),
.y = static_cast<float>(offset.y),
.width = static_cast<float>(extent.width),
.height = static_cast<float>(extent.height),
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
const vk::Rect2D scissor{
.offset = offset,
.extent = extent,
};
cmdbuf.setViewport(0, viewport);
cmdbuf.setScissor(0, scissor);
cmdbuf.draw(3, 1, 0, 0);
});
scheduler.MakeDirty(StateFlags::Pipeline);
}
void BlitHelper::FilterPassThreeTextures(Surface& source1, Surface& source2, Surface& source3,
Surface& dest, vk::Pipeline pipeline,
vk::PipelineLayout layout,
const VideoCore::TextureBlit& blit) {
const auto texture_descriptor_set = three_textures_provider.Commit();
update_queue.AddImageSampler(texture_descriptor_set, 0, 0, source1.ImageView(0), linear_sampler,
vk::ImageLayout::eGeneral);
update_queue.AddImageSampler(texture_descriptor_set, 1, 0, source2.ImageView(0), linear_sampler,
vk::ImageLayout::eGeneral);
update_queue.AddImageSampler(texture_descriptor_set, 2, 0, source3.ImageView(0), linear_sampler,
vk::ImageLayout::eGeneral);
const bool is_depth = dest.type == VideoCore::SurfaceType::Depth ||
dest.type == VideoCore::SurfaceType::DepthStencil;
const auto color_format = is_depth ? VideoCore::PixelFormat::Invalid : dest.pixel_format;
const auto depth_format = is_depth ? dest.pixel_format : VideoCore::PixelFormat::Invalid;
const auto renderpass = renderpass_cache.GetRenderpass(color_format, depth_format, false);
const RenderPass render_pass = {
.framebuffer = dest.Framebuffer(),
.render_pass = renderpass,
.render_area =
{
.offset = {0, 0},
.extent = {dest.GetScaledWidth(), dest.GetScaledHeight()},
},
};
renderpass_cache.BeginRendering(render_pass);
const float src_scale = static_cast<float>(source1.GetResScale());
// Calculate normalized texture coordinates like OpenGL does
const auto src_extent = source1.RealExtent(false); // Get unscaled texture extent
const float tex_scale_x =
static_cast<float>(blit.src_rect.GetWidth()) / static_cast<float>(src_extent.width);
const float tex_scale_y =
static_cast<float>(blit.src_rect.GetHeight()) / static_cast<float>(src_extent.height);
const float tex_offset_x =
static_cast<float>(blit.src_rect.left) / static_cast<float>(src_extent.width);
const float tex_offset_y =
static_cast<float>(blit.src_rect.bottom) / static_cast<float>(src_extent.height);
scheduler.Record([pipeline, layout, texture_descriptor_set, blit, tex_scale_x, tex_scale_y,
tex_offset_x, tex_offset_y, src_scale](vk::CommandBuffer cmdbuf) {
const FilterPushConstants push_constants{.tex_scale = {tex_scale_x, tex_scale_y},
.tex_offset = {tex_offset_x, tex_offset_y},
.res_scale = src_scale};
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
// Bind single texture descriptor set
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0,
texture_descriptor_set, {});
cmdbuf.pushConstants(layout, FILTER_PUSH_CONSTANT_RANGE.stageFlags,
FILTER_PUSH_CONSTANT_RANGE.offset, FILTER_PUSH_CONSTANT_RANGE.size,
&push_constants);
// Set up viewport and scissor using safe viewport like working filters
const vk::Offset2D offset{
.x = std::min<s32>(blit.dst_rect.left, blit.dst_rect.right),
.y = std::min<s32>(blit.dst_rect.bottom, blit.dst_rect.top),
};
const vk::Extent2D extent{
.width = blit.dst_rect.GetWidth(),
.height = blit.dst_rect.GetHeight(),
};
const vk::Viewport viewport{
.x = static_cast<float>(offset.x),
.y = static_cast<float>(offset.y),
.width = static_cast<float>(extent.width),
.height = static_cast<float>(extent.height),
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
const vk::Rect2D scissor{
.offset = offset,
.extent = extent,
};
cmdbuf.setViewport(0, viewport);
cmdbuf.setScissor(0, scissor);
cmdbuf.draw(3, 1, 0, 0);
});
scheduler.MakeDirty(StateFlags::Pipeline);
}
} // namespace Vulkan } // namespace Vulkan

View file

@ -1,10 +1,9 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include "video_core/rasterizer_cache/pixel_format.h"
#include "video_core/renderer_vulkan/vk_resource_pool.h" #include "video_core/renderer_vulkan/vk_resource_pool.h"
namespace VideoCore { namespace VideoCore {
@ -28,7 +27,6 @@ public:
explicit BlitHelper(const Instance& instance, Scheduler& scheduler, explicit BlitHelper(const Instance& instance, Scheduler& scheduler,
RenderManager& renderpass_cache, DescriptorUpdateQueue& update_queue); RenderManager& renderpass_cache, DescriptorUpdateQueue& update_queue);
~BlitHelper(); ~BlitHelper();
bool Filter(Surface& surface, const VideoCore::TextureBlit& blit);
bool BlitDepthStencil(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit); bool BlitDepthStencil(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit);
@ -40,25 +38,6 @@ public:
private: private:
vk::Pipeline MakeComputePipeline(vk::ShaderModule shader, vk::PipelineLayout layout); vk::Pipeline MakeComputePipeline(vk::ShaderModule shader, vk::PipelineLayout layout);
vk::Pipeline MakeDepthStencilBlitPipeline(); vk::Pipeline MakeDepthStencilBlitPipeline();
vk::Pipeline MakeFilterPipeline(
vk::ShaderModule fragment_shader, vk::PipelineLayout layout,
VideoCore::PixelFormat color_format = VideoCore::PixelFormat::RGBA8);
void FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& blit);
void FilterBicubic(Surface& surface, const VideoCore::TextureBlit& blit);
void FilterScaleForce(Surface& surface, const VideoCore::TextureBlit& blit);
void FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit);
void FilterMMPX(Surface& surface, const VideoCore::TextureBlit& blit);
void FilterPass(Surface& source, Surface& dest, vk::Pipeline pipeline,
vk::PipelineLayout layout, const VideoCore::TextureBlit& blit);
void FilterPassThreeTextures(Surface& source1, Surface& source2, Surface& source3,
Surface& dest, vk::Pipeline pipeline, vk::PipelineLayout layout,
const VideoCore::TextureBlit& blit);
void FilterPassYGradient(Surface& source, Surface& dest, vk::Pipeline pipeline,
vk::PipelineLayout layout, const VideoCore::TextureBlit& blit);
private: private:
const Instance& instance; const Instance& instance;
@ -72,23 +51,14 @@ private:
DescriptorHeap compute_provider; DescriptorHeap compute_provider;
DescriptorHeap compute_buffer_provider; DescriptorHeap compute_buffer_provider;
DescriptorHeap two_textures_provider; DescriptorHeap two_textures_provider;
DescriptorHeap single_texture_provider;
DescriptorHeap three_textures_provider;
vk::PipelineLayout compute_pipeline_layout; vk::PipelineLayout compute_pipeline_layout;
vk::PipelineLayout compute_buffer_pipeline_layout; vk::PipelineLayout compute_buffer_pipeline_layout;
vk::PipelineLayout two_textures_pipeline_layout; vk::PipelineLayout two_textures_pipeline_layout;
vk::PipelineLayout single_texture_pipeline_layout;
vk::PipelineLayout three_textures_pipeline_layout;
vk::ShaderModule full_screen_vert; vk::ShaderModule full_screen_vert;
vk::ShaderModule d24s8_to_rgba8_comp; vk::ShaderModule d24s8_to_rgba8_comp;
vk::ShaderModule depth_to_buffer_comp; vk::ShaderModule depth_to_buffer_comp;
vk::ShaderModule blit_depth_stencil_frag; vk::ShaderModule blit_depth_stencil_frag;
vk::ShaderModule bicubic_frag;
vk::ShaderModule scale_force_frag;
vk::ShaderModule xbrz_frag;
vk::ShaderModule mmpx_frag;
vk::ShaderModule refine_frag;
vk::Pipeline d24s8_to_rgba8_pipeline; vk::Pipeline d24s8_to_rgba8_pipeline;
vk::Pipeline depth_to_buffer_pipeline; vk::Pipeline depth_to_buffer_pipeline;

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -103,14 +103,13 @@ vk::CommandBuffer CommandPool::Commit() {
return cmd_buffers[index]; return cmd_buffers[index];
} }
constexpr u32 DESCRIPTOR_SET_BATCH = 64; constexpr u32 DESCRIPTOR_SET_BATCH = 32;
constexpr u32 DESCRIPTOR_MULTIPLIER = 4; // Increase capacity of each pool
DescriptorHeap::DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore, DescriptorHeap::DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore,
std::span<const vk::DescriptorSetLayoutBinding> bindings, std::span<const vk::DescriptorSetLayoutBinding> bindings,
u32 descriptor_heap_count_) u32 descriptor_heap_count_)
: ResourcePool{master_semaphore, DESCRIPTOR_SET_BATCH}, device{instance.GetDevice()}, : ResourcePool{master_semaphore, DESCRIPTOR_SET_BATCH}, device{instance.GetDevice()},
descriptor_heap_count{descriptor_heap_count_ * DESCRIPTOR_MULTIPLIER} { // Increase pool size descriptor_heap_count{descriptor_heap_count_} {
// Create descriptor set layout. // Create descriptor set layout.
const vk::DescriptorSetLayoutCreateInfo layout_ci = { const vk::DescriptorSetLayoutCreateInfo layout_ci = {
.bindingCount = static_cast<u32>(bindings.size()), .bindingCount = static_cast<u32>(bindings.size()),

View file

@ -1,23 +1,9 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "video_core/renderer_vulkan/vk_texture_runtime.h"
#include <cmath>
#include <limits>
#include <span>
#include <string>
#include <boost/container/small_vector.hpp> #include <boost/container/small_vector.hpp>
#include <boost/container/static_vector.hpp> #include <boost/container/static_vector.hpp>
#include <vulkan/vulkan.hpp>
#include "video_core/custom_textures/custom_tex_manager.h"
#include "video_core/rasterizer_cache/pixel_format.h"
#include "video_core/rasterizer_cache/surface_params.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_vulkan/vk_blit_helper.h"
#include "video_core/renderer_vulkan/vk_descriptor_update_queue.h"
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
#include "common/literals.h" #include "common/literals.h"
#include "common/microprofile.h" #include "common/microprofile.h"
@ -714,7 +700,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
: SurfaceBase{params}, runtime{&runtime_}, instance{&runtime_.GetInstance()}, : SurfaceBase{params}, runtime{&runtime_}, instance{&runtime_.GetInstance()},
scheduler{&runtime_.GetScheduler()}, traits{instance->GetTraits(pixel_format)} { scheduler{&runtime_.GetScheduler()}, traits{instance->GetTraits(pixel_format)} {
if (pixel_format == VideoCore::PixelFormat::Invalid || !traits.transfer_support) { if (pixel_format == VideoCore::PixelFormat::Invalid) {
return; return;
} }
@ -734,25 +720,18 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
flags |= vk::ImageCreateFlagBits::eMutableFormat; flags |= vk::ImageCreateFlagBits::eMutableFormat;
} }
// Ensure color formats have the color attachment bit set for framebuffers
auto usage = traits.usage;
const bool is_color =
(traits.aspect & vk::ImageAspectFlagBits::eColor) != vk::ImageAspectFlags{};
if (is_color) {
usage |= vk::ImageUsageFlagBits::eColorAttachment;
}
const bool need_format_list = is_mutable && instance->IsImageFormatListSupported(); const bool need_format_list = is_mutable && instance->IsImageFormatListSupported();
handles[0] = MakeHandle(instance, width, height, levels, texture_type, format, usage, flags, handles[0] = MakeHandle(instance, width, height, levels, texture_type, format, traits.usage,
traits.aspect, need_format_list, DebugName(false)); flags, traits.aspect, need_format_list, DebugName(false));
raw_images.emplace_back(handles[0].image); raw_images.emplace_back(handles[0].image);
if (res_scale != 1) { if (res_scale != 1) {
handles[1] = handles[1] =
MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type, format, MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type, format,
usage, flags, traits.aspect, need_format_list, DebugName(true)); traits.usage, flags, traits.aspect, need_format_list, DebugName(true));
raw_images.emplace_back(handles[1].image); raw_images.emplace_back(handles[1].image);
} }
runtime->renderpass_cache.EndRendering(); runtime->renderpass_cache.EndRendering();
scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
const auto barriers = MakeInitBarriers(aspect, raw_images); const auto barriers = MakeInitBarriers(aspect, raw_images);
@ -809,49 +788,6 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
material = mat; material = mat;
} }
Surface::Surface(TextureRuntime& runtime_, u32 width_, u32 height_, VideoCore::PixelFormat format_)
: SurfaceBase{{
.width = width_,
.height = height_,
.pixel_format = format_,
.type = VideoCore::SurfaceType::Texture,
}},
runtime{&runtime_}, instance{&runtime_.GetInstance()}, scheduler{&runtime_.GetScheduler()},
traits{instance->GetTraits(format_)} {
// Create texture with requested size and format
const vk::ImageUsageFlags usage =
vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled;
handles[0] = MakeHandle(instance, width_, height_, 1, VideoCore::TextureType::Texture2D,
traits.native, usage, {}, traits.aspect, false, "Temporary Surface");
// Create image view
const vk::ImageViewCreateInfo view_info = {
.image = handles[0].image,
.viewType = vk::ImageViewType::e2D,
.format = traits.native,
.subresourceRange{
.aspectMask = traits.aspect,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
handles[0].image_view = instance->GetDevice().createImageViewUnique(view_info);
runtime->renderpass_cache.EndRendering();
scheduler->Record(
[raw_images = std::array{Image()}, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
const auto barriers = MakeInitBarriers(aspect, raw_images);
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTopOfPipe,
vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
});
}
Surface::~Surface() { Surface::~Surface() {
if (!handles[0].image_view) { if (!handles[0].image_view) {
return; return;
@ -940,23 +876,14 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
runtime->upload_buffer.Commit(staging.size); runtime->upload_buffer.Commit(staging.size);
if (res_scale != 1) { if (res_scale != 1) {
// Always ensure the scaled image exists const VideoCore::TextureBlit blit = {
if (!handles[1].image) { .src_level = upload.texture_level,
// This will create handles[1] and perform the initial scaling .dst_level = upload.texture_level,
ScaleUp(res_scale); .src_rect = upload.texture_rect,
} else { .dst_rect = upload.texture_rect * res_scale,
// Update the scaled version of the uploaded area };
const VideoCore::TextureBlit blit = {
.src_level = upload.texture_level, BlitScale(blit, true);
.dst_level = upload.texture_level,
.src_rect = upload.texture_rect,
.dst_rect = upload.texture_rect * res_scale,
};
// Only apply texture filtering when upscaling, matching OpenGL behavior
if (res_scale != 1 && !runtime->blit_helper.Filter(*this, blit)) {
BlitScale(blit, true);
}
}
} }
} }
@ -1322,6 +1249,11 @@ vk::ImageView Surface::ImageView(u32 index) const noexcept {
return image_view; return image_view;
} }
vk::ImageView Surface::FramebufferView() noexcept {
is_framebuffer = true;
return ImageView();
}
vk::ImageView Surface::DepthView() noexcept { vk::ImageView Surface::DepthView() noexcept {
if (depth_view) { if (depth_view) {
return depth_view.get(); return depth_view.get();
@ -1397,8 +1329,6 @@ vk::ImageView Surface::StorageView() noexcept {
} }
vk::Framebuffer Surface::Framebuffer() noexcept { vk::Framebuffer Surface::Framebuffer() noexcept {
is_framebuffer = true;
const u32 index = res_scale == 1 ? 0u : 1u; const u32 index = res_scale == 1 ? 0u : 1u;
if (framebuffers[index]) { if (framebuffers[index]) {
return framebuffers[index].get(); return framebuffers[index].get();
@ -1409,29 +1339,12 @@ vk::Framebuffer Surface::Framebuffer() noexcept {
const auto depth_format = is_depth ? pixel_format : PixelFormat::Invalid; const auto depth_format = is_depth ? pixel_format : PixelFormat::Invalid;
const auto render_pass = const auto render_pass =
runtime->renderpass_cache.GetRenderpass(color_format, depth_format, false); runtime->renderpass_cache.GetRenderpass(color_format, depth_format, false);
// Use AttachmentView() to get single mip level view for framebuffer const auto attachments = std::array{ImageView()};
const auto attachments = std::array{AttachmentView()};
framebuffers[index] = MakeFramebuffer(instance->GetDevice(), render_pass, GetScaledWidth(), framebuffers[index] = MakeFramebuffer(instance->GetDevice(), render_pass, GetScaledWidth(),
GetScaledHeight(), attachments); GetScaledHeight(), attachments);
return framebuffers[index].get(); return framebuffers[index].get();
} }
vk::ImageView Surface::AttachmentView() noexcept {
const vk::ImageViewCreateInfo view_info = {
.image = Image(),
.viewType = vk::ImageViewType::e2D,
.format = traits.native,
.subresourceRange{
.aspectMask = traits.aspect,
.baseMipLevel = 0,
.levelCount = 1, // Single mip level for framebuffer
.baseArrayLayer = 0,
.layerCount = 1,
},
};
return instance->GetDevice().createImageViewUnique(view_info).release();
}
void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) { void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
const FormatTraits& depth_traits = instance->GetTraits(pixel_format); const FormatTraits& depth_traits = instance->GetTraits(pixel_format);
const bool is_depth_stencil = pixel_format == PixelFormat::D24S8; const bool is_depth_stencil = pixel_format == PixelFormat::D24S8;
@ -1440,16 +1353,9 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
return; return;
} }
// Always use consistent source and destination images for proper scaling scheduler->Record([src_image = Image(!up_scale), aspect = Aspect(),
// When upscaling: source = unscaled (0), destination = scaled (1) filter = MakeFilter(pixel_format), dst_image = Image(up_scale),
// When downscaling: source = scaled (1), destination = unscaled (0)
const vk::Image src_image = up_scale ? Image(0) : Image(1);
const vk::Image dst_image = up_scale ? Image(1) : Image(0);
scheduler->Record([src_image, aspect = Aspect(), filter = MakeFilter(pixel_format), dst_image,
src_access = AccessFlags(), dst_access = AccessFlags(),
blit](vk::CommandBuffer render_cmdbuf) { blit](vk::CommandBuffer render_cmdbuf) {
// Adjust blitting parameters for filtered upscaling
const std::array source_offsets = { const std::array source_offsets = {
vk::Offset3D{static_cast<s32>(blit.src_rect.left), vk::Offset3D{static_cast<s32>(blit.src_rect.left),
static_cast<s32>(blit.src_rect.bottom), 0}, static_cast<s32>(blit.src_rect.bottom), 0},
@ -1483,7 +1389,7 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
const std::array read_barriers = { const std::array read_barriers = {
vk::ImageMemoryBarrier{ vk::ImageMemoryBarrier{
.srcAccessMask = src_access, .srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
.dstAccessMask = vk::AccessFlagBits::eTransferRead, .dstAccessMask = vk::AccessFlagBits::eTransferRead,
.oldLayout = vk::ImageLayout::eGeneral, .oldLayout = vk::ImageLayout::eGeneral,
.newLayout = vk::ImageLayout::eTransferSrcOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal,
@ -1493,7 +1399,10 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
.subresourceRange = MakeSubresourceRange(aspect, blit.src_level), .subresourceRange = MakeSubresourceRange(aspect, blit.src_level),
}, },
vk::ImageMemoryBarrier{ vk::ImageMemoryBarrier{
.srcAccessMask = dst_access, .srcAccessMask = vk::AccessFlagBits::eShaderRead |
vk::AccessFlagBits::eDepthStencilAttachmentRead |
vk::AccessFlagBits::eColorAttachmentRead |
vk::AccessFlagBits::eTransferRead,
.dstAccessMask = vk::AccessFlagBits::eTransferWrite, .dstAccessMask = vk::AccessFlagBits::eTransferWrite,
.oldLayout = vk::ImageLayout::eGeneral, .oldLayout = vk::ImageLayout::eGeneral,
.newLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eTransferDstOptimal,
@ -1540,9 +1449,9 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
} }
Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferParams& params, Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferParams& params,
Surface* color, Surface* depth_stencil) Surface* color, Surface* depth)
: VideoCore::FramebufferParams{params}, : VideoCore::FramebufferParams{params},
res_scale{color ? color->res_scale : (depth_stencil ? depth_stencil->res_scale : 1u)} { res_scale{color ? color->res_scale : (depth ? depth->res_scale : 1u)} {
auto& renderpass_cache = runtime.GetRenderpassCache(); auto& renderpass_cache = runtime.GetRenderpassCache();
if (shadow_rendering && !color) { if (shadow_rendering && !color) {
return; return;
@ -1559,41 +1468,27 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
} }
images[index] = surface->Image(); images[index] = surface->Image();
aspects[index] = surface->Aspect(); aspects[index] = surface->Aspect();
// Use AttachmentView() for single-mip-level framebuffer attachment image_views[index] = shadow_rendering ? surface->StorageView() : surface->FramebufferView();
image_views[index] = surface->AttachmentView();
}; };
boost::container::static_vector<vk::ImageView, 2> attachments; boost::container::static_vector<vk::ImageView, 2> attachments;
if (!shadow_rendering) { if (color) {
// Prepare the surfaces for use in framebuffer prepare(0, color);
if (color) { attachments.emplace_back(image_views[0]);
prepare(0, color); }
attachments.emplace_back(image_views[0]);
}
if (depth_stencil) { if (depth) {
prepare(1, depth_stencil); prepare(1, depth);
attachments.emplace_back(image_views[1]); attachments.emplace_back(image_views[1]);
}
} else {
// For shadow rendering, just collect surface info without adding attachments
if (color) {
prepare(0, color);
}
if (depth_stencil) {
prepare(1, depth_stencil);
}
} }
const vk::Device device = runtime.GetInstance().GetDevice(); const vk::Device device = runtime.GetInstance().GetDevice();
if (shadow_rendering) { if (shadow_rendering) {
// For shadow rendering, we don't need a framebuffer with attachments
// Just create a dummy render pass for the rendering pipeline
render_pass = render_pass =
renderpass_cache.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false); renderpass_cache.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false);
// Don't create a framebuffer for shadow rendering framebuffer = MakeFramebuffer(device, render_pass, color->GetScaledWidth(),
framebuffer.reset(); color->GetScaledHeight(), {});
} else { } else {
render_pass = renderpass_cache.GetRenderpass(formats[0], formats[1], false); render_pass = renderpass_cache.GetRenderpass(formats[0], formats[1], false);
framebuffer = MakeFramebuffer(device, render_pass, width, height, attachments); framebuffer = MakeFramebuffer(device, render_pass, width, height, attachments);

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Azahar Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -110,8 +110,6 @@ public:
explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceParams& params); explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceParams& params);
explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceBase& surface, explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceBase& surface,
const VideoCore::Material* materal); const VideoCore::Material* materal);
explicit Surface(TextureRuntime& runtime, u32 width_, u32 height_,
VideoCore::PixelFormat format_);
~Surface(); ~Surface();
Surface(const Surface&) = delete; Surface(const Surface&) = delete;
@ -130,24 +128,12 @@ public:
/// Returns the image view at index, otherwise the base view /// Returns the image view at index, otherwise the base view
vk::ImageView ImageView(u32 index = 1) const noexcept; vk::ImageView ImageView(u32 index = 1) const noexcept;
/// Returns width of the surface
u32 GetWidth() const noexcept {
return width;
}
/// Returns height of the surface
u32 GetHeight() const noexcept {
return height;
}
/// Returns resolution scale of the surface
u32 GetResScale() const noexcept {
return res_scale;
}
/// Returns a copy of the upscaled image handle, used for feedback loops. /// Returns a copy of the upscaled image handle, used for feedback loops.
vk::ImageView CopyImageView() noexcept; vk::ImageView CopyImageView() noexcept;
/// Returns the framebuffer view of the surface image
vk::ImageView FramebufferView() noexcept;
/// Returns the depth view of the surface image /// Returns the depth view of the surface image
vk::ImageView DepthView() noexcept; vk::ImageView DepthView() noexcept;
@ -160,9 +146,6 @@ public:
/// Returns a framebuffer handle for rendering to this surface /// Returns a framebuffer handle for rendering to this surface
vk::Framebuffer Framebuffer() noexcept; vk::Framebuffer Framebuffer() noexcept;
/// Returns a single-mip-level view suitable for framebuffer attachments
vk::ImageView AttachmentView() noexcept;
/// Uploads pixel data in staging to a rectangle region of the surface texture /// Uploads pixel data in staging to a rectangle region of the surface texture
void Upload(const VideoCore::BufferTextureCopy& upload, const VideoCore::StagingData& staging); void Upload(const VideoCore::BufferTextureCopy& upload, const VideoCore::StagingData& staging);
@ -248,12 +231,19 @@ public:
return res_scale; return res_scale;
} }
u32 Width() const noexcept {
return width;
}
u32 Height() const noexcept {
return height;
}
private: private:
std::array<vk::Image, 2> images{}; std::array<vk::Image, 2> images{};
std::array<vk::ImageView, 2> image_views{}; std::array<vk::ImageView, 2> image_views{};
vk::UniqueFramebuffer framebuffer; vk::UniqueFramebuffer framebuffer;
vk::RenderPass render_pass; vk::RenderPass render_pass;
std::vector<vk::UniqueImageView> framebuffer_views;
std::array<vk::ImageAspectFlags, 2> aspects{}; std::array<vk::ImageAspectFlags, 2> aspects{};
std::array<VideoCore::PixelFormat, 2> formats{VideoCore::PixelFormat::Invalid, std::array<VideoCore::PixelFormat, 2> formats{VideoCore::PixelFormat::Invalid,
VideoCore::PixelFormat::Invalid}; VideoCore::PixelFormat::Invalid};