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
OS: linux
TARGET: ${{ matrix.target }}
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
steps:
- uses: actions/checkout@v4
if: ${{ env.SHOULD_RUN == 'true' }}
with:
submodules: recursive
- name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' }}
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
@ -50,18 +53,19 @@ jobs:
restore-keys: |
${{ runner.os }}-${{ matrix.target }}-
- name: Build
if: ${{ env.SHOULD_RUN == 'true' }}
run: ./.ci/linux.sh
- name: Move AppImage to artifacts directory
if: ${{ contains(matrix.target, 'appimage') }}
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
run: |
mkdir -p artifacts
mv build/bundle/*.AppImage artifacts/
- name: Rename AppImage
if: ${{ matrix.target == 'appimage-wayland' }}
if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }}
run: |
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
- name: Upload
if: ${{ contains(matrix.target, 'appimage') }}
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
uses: actions/upload-artifact@v4
with:
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 displays = dm.displays
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 isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable
isNotDefaultOrPresentable &&
@ -59,6 +59,10 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
it.state != Display.STATE_OFF &&
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() {

View file

@ -255,7 +255,9 @@ class InputBindingSetting(
} else {
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
value = uiString
}

View file

@ -269,6 +269,8 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
system.GPU().ApplyPerProgramSettings(program_id);
std::unique_ptr<Frontend::GraphicsContext> cpu_context;
system.GPU().Renderer().Rasterizer()->LoadDefaultDiskResources(stop_run,
&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_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_axis_vertical">Eix Vertical</string>
<string name="controller_axis_horizontal">Eix Horitzontal</string>
<string name="direction_up">Amunt</string>
<string name="direction_down">Avall</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_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_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_down">Ned</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_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_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_swap">Byt skærme</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_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_axis_vertical">Eje Vertical</string>
<string name="controller_axis_horizontal">Eje Horizontal</string>
<string name="direction_up">Arriba</string>
<string name="direction_down">Abajo</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_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_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_swap">Intercambiar Pantallas</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_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_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_down">Dół</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_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_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_swap">Zamień ekrany</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_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_axis_vertical">Eixo vertical</string>
<string name="controller_axis_horizontal">Eixo horizontal</string>
<string name="direction_up">Cima</string>
<string name="direction_down">Baixo</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_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_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_swap">Trocar telas</string>
<string name="button_turbo">Turbo</string>

View file

@ -64,8 +64,6 @@
<string name="controller_triggers">Триггеры</string>
<string name="controller_trigger">Триггер</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_description">Нажмите или отклоните элемент управления.</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_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_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_down">Aşağı</string>
<string name="direction_left">Sol</string>

View file

@ -112,8 +112,6 @@
<string name="controller_dpad_axis_description">有些控制器可能无法将其方向键映射为轴。如果是这种情况,只能使用方向键(按键)部分。</string>
<string name="controller_dpad_button">十字键(按键)</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_down"></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_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_axis_vertical">Achse (Oben/Unten)</string>
<string name="controller_axis_horizontal">Achse (Links/Rechts)</string>
<string name="direction_up">Hoch</string>
<string name="direction_down">Runter</string>
<string name="direction_left">Links</string>

View file

@ -6,8 +6,6 @@
<string name="controller_c">C-Tikku</string>
<string name="controller_triggers">Liipaisimet</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) -->
<string name="generic_buttons">Nappulat</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_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_axis_vertical">Axe Haut/Bas</string>
<string name="controller_axis_horizontal">Axe Gauche/Droite</string>
<string name="direction_up">Haut</string>
<string name="direction_down">Bas</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_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_axis_vertical">Asse verticale</string>
<string name="controller_axis_horizontal">Asse orizzontale</string>
<string name="direction_up">Su</string>
<string name="direction_down">Giù</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_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_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_swap">Inverti schermi</string>
<string name="button_turbo">Turbo</string>

View file

@ -8,8 +8,6 @@
<string name="controller_c">C-Spak</string>
<string name="controller_triggers">Utløser</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_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>

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_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_axis_vertical">Op/neer-as</string>
<string name="controller_axis_horizontal">Links/rechts as</string>
<string name="direction_up">Op</string>
<string name="direction_down">Neer</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_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_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_down">Ner</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_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_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_swap">Byt skärm</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_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_axis_vertical">Up/Down Axis</string>
<string name="controller_axis_horizontal">Left/Right Axis</string>
<string name="controller_axis_vertical">Vertical Axis</string>
<string name="controller_axis_horizontal">Horizontal Axis</string>
<string name="direction_up">Up</string>
<string name="direction_down">Down</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_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_vertical_axis">Move your joystick down</string>
<string name="input_binding_description_horizontal_axis">Move your joystick right</string>
<string name="input_binding_description_vertical_axis">Press UP on your joystick.</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_b" translatable="false">B</string>
<string name="button_select" translatable="false">SELECT</string>

View file

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

View file

@ -18,6 +18,7 @@
#include "core/3ds.h"
#include "core/core.h"
#include "core/frontend/framebuffer_layout.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
@ -78,6 +79,10 @@ void EmuThread::run() {
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(
stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit LoadProgress(stage, value, total);

View file

@ -171,12 +171,21 @@ void GMainWindow::ShowCommandOutput(std::string title, std::string message) {
#endif
}
bool IsPrerelease() {
bool IsPrereleaseBuild() {
return ((strstr(Common::g_build_fullname, "alpha") != NULL) ||
(strstr(Common::g_build_fullname, "beta") != 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_)
: ui{std::make_unique<Ui::MainWindow>()}, system{system_}, movie{system.Movie()},
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) {
update_future = QtConcurrent::run([]() -> QString {
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) {
return QString::fromStdString(latest_release_tag.value());
}
@ -4057,7 +4067,7 @@ void GMainWindow::OnEmulatorUpdateAvailable() {
update_prompt.exec();
if (update_prompt.button(QMessageBox::Yes) == update_prompt.clickedButton()) {
std::string update_page_url;
if (IsPrerelease()) {
if (ShouldCheckForPrereleaseUpdates()) {
update_page_url = "https://github.com/azahar-emu/azahar/releases";
} else {
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.enable_gamemode);
ReadBasicSetting(UISettings::values.check_for_update_on_start);
ReadBasicSetting(UISettings::values.update_check_channel);
qt_config->endGroup();
}
@ -1139,7 +1140,7 @@ void QtConfig::SaveMiscellaneousValues() {
WriteBasicSetting(Settings::values.log_regex_filter);
WriteBasicSetting(Settings::values.enable_gamemode);
WriteBasicSetting(UISettings::values.check_for_update_on_start);
WriteBasicSetting(UISettings::values.update_check_channel);
qt_config->endGroup();
}

View file

@ -44,7 +44,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
ui->toggle_gamemode->setVisible(false);
#endif
#ifndef ENABLE_QT_UPDATE_CHECKER
ui->toggle_update_checker->setVisible(false);
ui->updates_group->setVisible(false);
#endif
SetupPerGameUI();
@ -92,6 +92,8 @@ void ConfigureGeneral::SetConfiguration() {
ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse.GetValue());
ui->toggle_update_checker->setChecked(
UISettings::values.check_for_update_on_start.GetValue());
ui->update_channel_combobox->setCurrentIndex(
UISettings::values.update_check_channel.GetValue());
#ifdef __unix__
ui->toggle_gamemode->setChecked(Settings::values.enable_gamemode.GetValue());
#endif
@ -179,6 +181,7 @@ void ConfigureGeneral::ApplyConfiguration() {
UISettings::values.mute_when_in_background = ui->toggle_background_mute->isChecked();
UISettings::values.hide_mouse = ui->toggle_hide_mouse->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__
Settings::values.enable_gamemode = ui->toggle_gamemode->isChecked();
#endif
@ -211,5 +214,5 @@ void ConfigureGeneral::SetupPerGameUI() {
ui->general_group->setVisible(false);
ui->button_reset_defaults->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">
<item>
<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>
<widget class="QGroupBox" name="general_group">
<property name="title">
@ -57,13 +94,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_update_checker">
<property name="text">
<string>Check for updates</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -315,7 +345,6 @@
<tabstop>toggle_check_exit</tabstop>
<tabstop>toggle_background_pause</tabstop>
<tabstop>toggle_hide_mouse</tabstop>
<tabstop>toggle_update_checker</tabstop>
<tabstop>button_reset_defaults</tabstop>
</tabstops>
<resources/>

View file

@ -234,7 +234,7 @@
<item>
<widget class="QCheckBox" name="toggle_async_present">
<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 name="text">
<string>Enable async presentation</string>

View file

@ -1113,13 +1113,21 @@ const QStringList GameList::supported_file_extensions = {
};
void GameList::RefreshGameDirectory() {
// Do not scan directories when the system is powered on, it will be
// repopulated on shutdown anyways.
if (Core::System::GetInstance().IsPoweredOn()) {
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) {
LOG_INFO(Frontend, "Change detected in the applications directory. Reloading game list.");
PopulateAsync(UISettings::values.game_dirs);

View file

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

View file

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

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;
system.GPU().Renderer().Rasterizer()->LoadDefaultDiskResources(
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.join();
const auto signal_entry =
CreateEntry(Class::Log, Level::Critical, "?", 0, "?",
fmt::vformat("Received signal {}", fmt::make_format_args(sig)));
const auto signal_entry = CreateEntry(
Class::Log, Level::Critical, "?", 0, "?",
fmt::vformat("Received signal {}", fmt::make_format_args(sig)), time_origin);
ForEachBackend([&signal_entry](Backend& backend) {
backend.EnableForStacktrace();
backend.Write(signal_entry);
@ -345,12 +345,13 @@ private:
abort();
}
line.pop_back(); // Remove newline
const auto frame_entry =
CreateEntry(Class::Log, Level::Critical, "?", 0, "?", std::move(line));
const auto frame_entry = CreateEntry(Class::Log, Level::Critical, "?", 0, "?",
std::move(line), time_origin);
ForEachBackend([&frame_entry](Backend& backend) { backend.Write(frame_entry); });
}
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) {
backend.Write(rip_entry);
backend.Flush();

View file

@ -515,7 +515,11 @@ struct Values {
SwitchableSetting<bool> use_hw_shader{true, "use_hw_shader"};
SwitchableSetting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"};
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"};
#endif
Setting<bool> use_shader_jit{true, "use_shader_jit"};
SwitchableSetting<u32, true> resolution_factor{1, 0, 10, "resolution_factor"};
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);
gpu->ReportLoadingProgramID(program_id);
// Restore any parameters that should be carried through a reset.
if (auto apt = Service::APT::GetModule(*this)) {
if (restore_deliver_arg.has_value()) {
@ -896,19 +894,26 @@ void System::serialize(Archive& ar, const unsigned int file_version) {
timing->UnlockEventQueue();
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
// serialized
auto gsp = service_manager->GetService<Service::GSP::GSP_GPU>("gsp::Gpu");
gpu->SetInterruptHandler(
[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();
if (thread_id != std::numeric_limits<u32>::max()) {
const auto thread = kernel->GetThreadByID(thread_id);
if (thread) {
const std::shared_ptr<Kernel::Process> process = thread->owner_process.lock();
if (process) {
gpu->ApplyPerProgramSettings(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;
auto& gpu = system.GPU();
gpu.ApplyPerProgramSettings(process->codeset->program_id);
gpu.GetRightEyeDisabler().SetEnabled(right_eye_disable_allow);
gpu.PicaCore().vs_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;
const auto low_title_Id = plgldr_context.user_load_parameters.low_title_Id;
if (plgldr_context.use_user_load_parameters &&
plgldr_context.user_load_parameters.low_title_Id ==
static_cast<u32>(process.codeset->program_id) &&
(low_title_Id == static_cast<u32>(process.codeset->program_id) ||
low_title_Id == 0 /* Should load for any title */) &&
plgldr_context.user_load_parameters.path[0]) {
std::string plugin_file = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
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) {
auto itr = handlers.find(context.CommandHeader().command_id.Value());
const FunctionInfoBase* info = itr == handlers.end() ? nullptr : &itr->second;
if (info == nullptr || !info->implemented) {
if (info == nullptr || info->handler_callback == nullptr) {
context.ReportUnimplemented();
return ReportUnimplementedFunction(context.CommandBuffer(), info);
}

View file

@ -82,7 +82,6 @@ private:
struct FunctionInfoBase {
u32 command_id;
bool implemented;
HandlerFnP<ServiceFrameworkBase> handler_callback;
const char* name;
};
@ -97,8 +96,6 @@ private:
void RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n);
void ReportUnimplementedFunction(u32* cmd_buf, const FunctionInfoBase* info);
void Empty(Kernel::HLERequestContext& ctx) {}
/// Identifier string used to connect to the service.
std::string service_name;
/// 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)
: FunctionInfoBase{
command_id, handler_callback != nullptr,
command_id,
// Type-erase member function pointer by casting it down to the base class.
handler_callback ? static_cast<HandlerFnP<ServiceFrameworkBase>>(handler_callback)
: &ServiceFrameworkBase::Empty,
name} {}
static_cast<HandlerFnP<ServiceFrameworkBase>>(handler_callback), name} {}
};
/**

View file

@ -311,7 +311,7 @@ GraphicsDebugger& 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(
Common::Hacks::HackType::ACCURATE_MULTIPLICATION, program_ID);
bool use_accurate_mul = Settings::values.shaders_accurate_mul.GetValue();

View file

@ -94,7 +94,7 @@ public:
return *right_eye_disabler;
}
void ReportLoadingProgramID(u64 program_ID);
void ApplyPerProgramSettings(u64 program_ID);
private:
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
// Refer to the license.txt file included.
#include "common/settings.h"
#include "common/vector_math.h"
#include "video_core/renderer_vulkan/vk_blit_helper.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_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 {
using Settings::TextureFilter;
using VideoCore::PixelFormat;
namespace {
@ -67,33 +55,8 @@ constexpr std::array<vk::DescriptorSetLayoutBinding, 2> TWO_TEXTURES_BINDINGS =
{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{
.stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
.stageFlags = vk::ShaderStageFlagBits::eVertex,
.offset = 0,
.size = sizeof(PushConstants),
};
@ -141,17 +104,12 @@ constexpr vk::PipelineDynamicStateCreateInfo PIPELINE_DYNAMIC_STATE_CREATE_INFO{
.dynamicStateCount = static_cast<u32>(DYNAMIC_STATES.size()),
.pDynamicStates = DYNAMIC_STATES.data(),
};
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{
constexpr vk::PipelineColorBlendStateCreateInfo PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO{
.logicOpEnable = VK_FALSE,
.attachmentCount = 1,
.pAttachments = &COLOR_BLEND_ATTACHMENT,
.logicOp = vk::LogicOp::eClear,
.attachmentCount = 0,
.pAttachments = nullptr,
.blendConstants = std::array{0.0f, 0.0f, 0.0f, 0.0f},
};
constexpr vk::PipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO{
.depthTestEnable = VK_TRUE,
@ -170,9 +128,9 @@ inline constexpr vk::SamplerCreateInfo SAMPLER_CREATE_INFO{
.magFilter = filter,
.minFilter = filter,
.mipmapMode = vk::SamplerMipmapMode::eNearest,
.addressModeU = vk::SamplerAddressMode::eClampToEdge,
.addressModeV = vk::SamplerAddressMode::eClampToEdge,
.addressModeW = vk::SamplerAddressMode::eClampToEdge,
.addressModeU = vk::SamplerAddressMode::eClampToBorder,
.addressModeV = vk::SamplerAddressMode::eClampToBorder,
.addressModeW = vk::SamplerAddressMode::eClampToBorder,
.mipLodBias = 0.0f,
.anisotropyEnable = VK_FALSE,
.maxAnisotropy = 0.0f,
@ -185,14 +143,12 @@ inline constexpr vk::SamplerCreateInfo SAMPLER_CREATE_INFO{
};
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{
.setLayoutCount = 1,
.pSetLayouts = set_layout,
.pushConstantRangeCount = 1,
.pPushConstantRanges =
(compute ? &COMPUTE_PUSH_CONSTANT_RANGE
: (filter ? &FILTER_PUSH_CONSTANT_RANGE : &PUSH_CONSTANT_RANGE)),
.pPushConstantRanges = (compute ? &COMPUTE_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_buffer_provider{instance, scheduler.GetMasterSemaphore(), COMPUTE_BUFFER_BINDINGS},
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{
device.createPipelineLayout(PipelineLayoutCreateInfo(&compute_provider.Layout(), true))},
compute_buffer_pipeline_layout{device.createPipelineLayout(
PipelineLayoutCreateInfo(&compute_buffer_provider.Layout(), true))},
two_textures_pipeline_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,
vk::ShaderStageFlagBits::eVertex, device)},
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)},
blit_depth_stencil_frag{Compile(HostShaders::VULKAN_BLIT_DEPTH_STENCIL_FRAG,
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)},
depth_to_buffer_pipeline{
MakeComputePipeline(depth_to_buffer_comp, compute_buffer_pipeline_layout)},
@ -290,18 +230,10 @@ BlitHelper::~BlitHelper() {
device.destroyPipelineLayout(compute_pipeline_layout);
device.destroyPipelineLayout(compute_buffer_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(d24s8_to_rgba8_comp);
device.destroyShaderModule(depth_to_buffer_comp);
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(d24s8_to_rgba8_pipeline);
device.destroyPipeline(depth_blit_pipeline);
@ -310,7 +242,7 @@ BlitHelper::~BlitHelper() {
}
void BindBlitState(vk::CommandBuffer cmdbuf, vk::PipelineLayout layout,
const VideoCore::TextureBlit& blit, const Surface& dest) {
const VideoCore::TextureBlit& blit) {
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),
@ -340,9 +272,8 @@ void BindBlitState(vk::CommandBuffer cmdbuf, vk::PipelineLayout layout,
};
cmdbuf.setViewport(0, viewport);
cmdbuf.setScissor(0, scissor);
cmdbuf.pushConstants(layout,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0,
sizeof(push_constants), &push_constants);
cmdbuf.pushConstants(layout, vk::ShaderStageFlagBits::eVertex, 0, sizeof(push_constants),
&push_constants);
}
bool BlitHelper::BlitDepthStencil(Surface& source, Surface& dest,
@ -369,12 +300,12 @@ bool BlitHelper::BlitDepthStencil(Surface& source, Surface& dest,
};
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;
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, depth_blit_pipeline);
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, descriptor_set, {});
BindBlitState(cmdbuf, layout, blit, dest);
BindBlitState(cmdbuf, layout, blit);
cmdbuf.draw(3, 1, 0, 0);
});
scheduler.MakeDirty(StateFlags::Pipeline);
@ -600,7 +531,7 @@ vk::Pipeline BlitHelper::MakeDepthStencilBlitPipeline() {
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pMultisampleState = &PIPELINE_MULTISAMPLE_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,
.layout = two_textures_pipeline_layout,
.renderPass = renderpass,
@ -616,280 +547,4 @@ vk::Pipeline BlitHelper::MakeDepthStencilBlitPipeline() {
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

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
// Refer to the license.txt file included.
#pragma once
#include "video_core/rasterizer_cache/pixel_format.h"
#include "video_core/renderer_vulkan/vk_resource_pool.h"
namespace VideoCore {
@ -28,7 +27,6 @@ public:
explicit BlitHelper(const Instance& instance, Scheduler& scheduler,
RenderManager& renderpass_cache, DescriptorUpdateQueue& update_queue);
~BlitHelper();
bool Filter(Surface& surface, const VideoCore::TextureBlit& blit);
bool BlitDepthStencil(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit);
@ -40,25 +38,6 @@ public:
private:
vk::Pipeline MakeComputePipeline(vk::ShaderModule shader, vk::PipelineLayout layout);
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:
const Instance& instance;
@ -72,23 +51,14 @@ private:
DescriptorHeap compute_provider;
DescriptorHeap compute_buffer_provider;
DescriptorHeap two_textures_provider;
DescriptorHeap single_texture_provider;
DescriptorHeap three_textures_provider;
vk::PipelineLayout compute_pipeline_layout;
vk::PipelineLayout compute_buffer_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 d24s8_to_rgba8_comp;
vk::ShaderModule depth_to_buffer_comp;
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 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
// Refer to the license.txt file included.
@ -103,14 +103,13 @@ vk::CommandBuffer CommandPool::Commit() {
return cmd_buffers[index];
}
constexpr u32 DESCRIPTOR_SET_BATCH = 64;
constexpr u32 DESCRIPTOR_MULTIPLIER = 4; // Increase capacity of each pool
constexpr u32 DESCRIPTOR_SET_BATCH = 32;
DescriptorHeap::DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore,
std::span<const vk::DescriptorSetLayoutBinding> bindings,
u32 descriptor_heap_count_)
: 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.
const vk::DescriptorSetLayoutCreateInfo layout_ci = {
.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
// 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/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/microprofile.h"
@ -714,7 +700,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
: SurfaceBase{params}, runtime{&runtime_}, instance{&runtime_.GetInstance()},
scheduler{&runtime_.GetScheduler()}, traits{instance->GetTraits(pixel_format)} {
if (pixel_format == VideoCore::PixelFormat::Invalid || !traits.transfer_support) {
if (pixel_format == VideoCore::PixelFormat::Invalid) {
return;
}
@ -734,25 +720,18 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
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();
handles[0] = MakeHandle(instance, width, height, levels, texture_type, format, usage, flags,
traits.aspect, need_format_list, DebugName(false));
handles[0] = MakeHandle(instance, width, height, levels, texture_type, format, traits.usage,
flags, traits.aspect, need_format_list, DebugName(false));
raw_images.emplace_back(handles[0].image);
if (res_scale != 1) {
handles[1] =
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);
}
runtime->renderpass_cache.EndRendering();
scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
const auto barriers = MakeInitBarriers(aspect, raw_images);
@ -809,49 +788,6 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
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() {
if (!handles[0].image_view) {
return;
@ -940,23 +876,14 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
runtime->upload_buffer.Commit(staging.size);
if (res_scale != 1) {
// Always ensure the scaled image exists
if (!handles[1].image) {
// This will create handles[1] and perform the initial scaling
ScaleUp(res_scale);
} else {
// Update the scaled version of the uploaded area
const VideoCore::TextureBlit blit = {
.src_level = upload.texture_level,
.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);
}
}
const VideoCore::TextureBlit blit = {
.src_level = upload.texture_level,
.dst_level = upload.texture_level,
.src_rect = upload.texture_rect,
.dst_rect = upload.texture_rect * res_scale,
};
BlitScale(blit, true);
}
}
@ -1322,6 +1249,11 @@ vk::ImageView Surface::ImageView(u32 index) const noexcept {
return image_view;
}
vk::ImageView Surface::FramebufferView() noexcept {
is_framebuffer = true;
return ImageView();
}
vk::ImageView Surface::DepthView() noexcept {
if (depth_view) {
return depth_view.get();
@ -1397,8 +1329,6 @@ vk::ImageView Surface::StorageView() noexcept {
}
vk::Framebuffer Surface::Framebuffer() noexcept {
is_framebuffer = true;
const u32 index = res_scale == 1 ? 0u : 1u;
if (framebuffers[index]) {
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 render_pass =
runtime->renderpass_cache.GetRenderpass(color_format, depth_format, false);
// Use AttachmentView() to get single mip level view for framebuffer
const auto attachments = std::array{AttachmentView()};
const auto attachments = std::array{ImageView()};
framebuffers[index] = MakeFramebuffer(instance->GetDevice(), render_pass, GetScaledWidth(),
GetScaledHeight(), attachments);
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) {
const FormatTraits& depth_traits = instance->GetTraits(pixel_format);
const bool is_depth_stencil = pixel_format == PixelFormat::D24S8;
@ -1440,16 +1353,9 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
return;
}
// Always use consistent source and destination images for proper scaling
// When upscaling: source = unscaled (0), destination = scaled (1)
// 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(),
scheduler->Record([src_image = Image(!up_scale), aspect = Aspect(),
filter = MakeFilter(pixel_format), dst_image = Image(up_scale),
blit](vk::CommandBuffer render_cmdbuf) {
// Adjust blitting parameters for filtered upscaling
const std::array source_offsets = {
vk::Offset3D{static_cast<s32>(blit.src_rect.left),
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 = {
vk::ImageMemoryBarrier{
.srcAccessMask = src_access,
.srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
.dstAccessMask = vk::AccessFlagBits::eTransferRead,
.oldLayout = vk::ImageLayout::eGeneral,
.newLayout = vk::ImageLayout::eTransferSrcOptimal,
@ -1493,7 +1399,10 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
.subresourceRange = MakeSubresourceRange(aspect, blit.src_level),
},
vk::ImageMemoryBarrier{
.srcAccessMask = dst_access,
.srcAccessMask = vk::AccessFlagBits::eShaderRead |
vk::AccessFlagBits::eDepthStencilAttachmentRead |
vk::AccessFlagBits::eColorAttachmentRead |
vk::AccessFlagBits::eTransferRead,
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
.oldLayout = vk::ImageLayout::eGeneral,
.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,
Surface* color, Surface* depth_stencil)
Surface* color, Surface* depth)
: 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();
if (shadow_rendering && !color) {
return;
@ -1559,41 +1468,27 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
}
images[index] = surface->Image();
aspects[index] = surface->Aspect();
// Use AttachmentView() for single-mip-level framebuffer attachment
image_views[index] = surface->AttachmentView();
image_views[index] = shadow_rendering ? surface->StorageView() : surface->FramebufferView();
};
boost::container::static_vector<vk::ImageView, 2> attachments;
if (!shadow_rendering) {
// Prepare the surfaces for use in framebuffer
if (color) {
prepare(0, color);
attachments.emplace_back(image_views[0]);
}
if (color) {
prepare(0, color);
attachments.emplace_back(image_views[0]);
}
if (depth_stencil) {
prepare(1, depth_stencil);
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);
}
if (depth) {
prepare(1, depth);
attachments.emplace_back(image_views[1]);
}
const vk::Device device = runtime.GetInstance().GetDevice();
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 =
renderpass_cache.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false);
// Don't create a framebuffer for shadow rendering
framebuffer.reset();
framebuffer = MakeFramebuffer(device, render_pass, color->GetScaledWidth(),
color->GetScaledHeight(), {});
} else {
render_pass = renderpass_cache.GetRenderpass(formats[0], formats[1], false);
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
// 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::SurfaceBase& surface,
const VideoCore::Material* materal);
explicit Surface(TextureRuntime& runtime, u32 width_, u32 height_,
VideoCore::PixelFormat format_);
~Surface();
Surface(const Surface&) = delete;
@ -130,24 +128,12 @@ public:
/// Returns the image view at index, otherwise the base view
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.
vk::ImageView CopyImageView() noexcept;
/// Returns the framebuffer view of the surface image
vk::ImageView FramebufferView() noexcept;
/// Returns the depth view of the surface image
vk::ImageView DepthView() noexcept;
@ -160,9 +146,6 @@ public:
/// Returns a framebuffer handle for rendering to this surface
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
void Upload(const VideoCore::BufferTextureCopy& upload, const VideoCore::StagingData& staging);
@ -248,12 +231,19 @@ public:
return res_scale;
}
u32 Width() const noexcept {
return width;
}
u32 Height() const noexcept {
return height;
}
private:
std::array<vk::Image, 2> images{};
std::array<vk::ImageView, 2> image_views{};
vk::UniqueFramebuffer framebuffer;
vk::RenderPass render_pass;
std::vector<vk::UniqueImageView> framebuffer_views;
std::array<vk::ImageAspectFlags, 2> aspects{};
std::array<VideoCore::PixelFormat, 2> formats{VideoCore::PixelFormat::Invalid,
VideoCore::PixelFormat::Invalid};