mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-04 11:23:12 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b3eb7e47fd | |||
| 6a7c069824 | |||
| 86df49bbc9 | |||
| 4e59b71c59 |
@@ -52,7 +52,6 @@ if (YUZU_STATIC_ROOM)
|
||||
# disable e v e r y t h i n g
|
||||
set(ENABLE_QT OFF)
|
||||
set(YUZU_CMD OFF)
|
||||
set(ENABLE_CUBEB OFF)
|
||||
set(ENABLE_UPDATE_CHECKER OFF)
|
||||
set(USE_DISCORD_PRESENCE OFF)
|
||||
set(BUILD_TESTING OFF)
|
||||
@@ -84,8 +83,6 @@ option(ENABLE_WERROR "Enable -Werror diagnostics" ON)
|
||||
# Lossless Scaling frame generation. Only Android.
|
||||
cmake_dependent_option(ENABLE_LSFG "Enable Lossless Scaling frame generation" ON "ANDROID" OFF)
|
||||
|
||||
option(ENABLE_RESHADE "Enable ReShade FX post-processing effects" ON)
|
||||
|
||||
# non-linux bundled qt are static
|
||||
if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX))
|
||||
set(YUZU_STATIC_BUILD ON)
|
||||
@@ -216,8 +213,6 @@ if(MSVC)
|
||||
endif()
|
||||
|
||||
# TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
set(EXT_DEFAULT OFF)
|
||||
if (MSVC OR ANDROID)
|
||||
set(EXT_DEFAULT ON)
|
||||
@@ -576,10 +571,6 @@ if (NOT YUZU_STATIC_ROOM)
|
||||
find_package(DiscordRPC)
|
||||
endif()
|
||||
|
||||
if (ENABLE_CUBEB)
|
||||
find_package(cubeb)
|
||||
endif()
|
||||
|
||||
if (YUZU_TESTS OR DYNARMIC_TESTS)
|
||||
find_package(Catch2)
|
||||
endif()
|
||||
|
||||
@@ -54,18 +54,6 @@
|
||||
"repo": "arun11299/cpp-jwt",
|
||||
"version": "7f24eb4c32"
|
||||
},
|
||||
"cubeb": {
|
||||
"find_args": "CONFIG",
|
||||
"hash": "8a4bcb2f83ba590f52c66626e895304a73eb61928dbc57777e1822e55378e3568366f17f9da4b80036cc2ef4ea9723c32abf6e7d9bbe00fb03654f0991596ab0",
|
||||
"options": [
|
||||
"USE_SANITIZERS OFF",
|
||||
"BUILD_TESTS OFF",
|
||||
"BUILD_TOOLS OFF",
|
||||
"BUNDLE_SPEEX ON"
|
||||
],
|
||||
"repo": "mozilla/cubeb",
|
||||
"version": "fa02160712"
|
||||
},
|
||||
"discord-rpc": {
|
||||
"find_args": "MODULE",
|
||||
"hash": "8213c43dcb0f7d479f5861091d111ed12fbdec1e62e6d729d65a4bc181d82f48a35d5fd3cd5c291f2393ac7c9681eabc6b76609755f55376284c8a8d67e148f3",
|
||||
@@ -246,11 +234,6 @@
|
||||
"repo": "stachenov/quazip",
|
||||
"version": "2e95c9001b"
|
||||
},
|
||||
"reshade": {
|
||||
"hash": "a1bd3fcf135fb6d018c1831ae45a8942d9777d0418b55e1189921e2ab775d1b53b0a9808924e09ef1c3e68ea11d69b3bcbebc7f4b59de14f6deec2dc24531d5e",
|
||||
"repo": "crosire/reshade",
|
||||
"version": "v6.7.3"
|
||||
},
|
||||
"sdl3": {
|
||||
"hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3",
|
||||
"min_version": "3.2.10",
|
||||
|
||||
Vendored
-62
@@ -1,62 +0,0 @@
|
||||
texture EdenBackBufferTex : COLOR;
|
||||
sampler EdenBackBuffer { Texture = EdenBackBufferTex; };
|
||||
|
||||
uniform float Saturation <
|
||||
ui_type = "slider";
|
||||
ui_label = "Saturation";
|
||||
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
||||
> = 1.0;
|
||||
|
||||
uniform float Brightness <
|
||||
ui_type = "slider";
|
||||
ui_label = "Brightness";
|
||||
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
||||
> = 1.0;
|
||||
|
||||
uniform float Contrast <
|
||||
ui_type = "slider";
|
||||
ui_label = "Contrast";
|
||||
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
||||
> = 1.0;
|
||||
|
||||
uniform float Gamma <
|
||||
ui_type = "slider";
|
||||
ui_label = "Gamma";
|
||||
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
||||
> = 1.0;
|
||||
|
||||
void VS_Eden(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||
{
|
||||
uv = float2(0.0, 0.0);
|
||||
if (id == 2)
|
||||
{
|
||||
uv.x = 2.0;
|
||||
}
|
||||
if (id == 1)
|
||||
{
|
||||
uv.y = 2.0;
|
||||
}
|
||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||
}
|
||||
|
||||
float4 PS_ColorGrade(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float3 rgb = tex2D(EdenBackBuffer, uv).rgb;
|
||||
|
||||
float luma = dot(rgb, float3(0.2126, 0.7152, 0.0722));
|
||||
rgb = lerp(float3(luma, luma, luma), rgb, Saturation);
|
||||
rgb *= Brightness;
|
||||
rgb = (rgb - 0.5) * Contrast + 0.5;
|
||||
rgb = pow(max(rgb, 0.0), 1.0 / max(Gamma, 0.0001));
|
||||
|
||||
return float4(saturate(rgb), 1.0);
|
||||
}
|
||||
|
||||
technique EdenColorGrade
|
||||
{
|
||||
pass
|
||||
{
|
||||
VertexShader = VS_Eden;
|
||||
PixelShader = PS_ColorGrade;
|
||||
}
|
||||
}
|
||||
Vendored
-45
@@ -1,45 +0,0 @@
|
||||
texture EdenBackBufferTex : COLOR;
|
||||
sampler EdenBackBuffer { Texture = EdenBackBufferTex; };
|
||||
|
||||
uniform float Amount <
|
||||
ui_type = "slider";
|
||||
ui_label = "Amount";
|
||||
ui_min = 0.0; ui_max = 3.0; ui_step = 0.01;
|
||||
> = 0.6;
|
||||
|
||||
void VS_Eden(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||
{
|
||||
uv = float2(0.0, 0.0);
|
||||
if (id == 2)
|
||||
{
|
||||
uv.x = 2.0;
|
||||
}
|
||||
if (id == 1)
|
||||
{
|
||||
uv.y = 2.0;
|
||||
}
|
||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||
}
|
||||
|
||||
float4 PS_Sharpen(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
||||
|
||||
float3 centre = tex2D(EdenBackBuffer, uv).rgb;
|
||||
float3 blur = tex2D(EdenBackBuffer, uv + float2(-texel.x, 0.0)).rgb;
|
||||
blur += tex2D(EdenBackBuffer, uv + float2(texel.x, 0.0)).rgb;
|
||||
blur += tex2D(EdenBackBuffer, uv + float2(0.0, -texel.y)).rgb;
|
||||
blur += tex2D(EdenBackBuffer, uv + float2(0.0, texel.y)).rgb;
|
||||
blur *= 0.25;
|
||||
|
||||
return float4(centre + (centre - blur) * Amount, 1.0);
|
||||
}
|
||||
|
||||
technique EdenSharpen
|
||||
{
|
||||
pass
|
||||
{
|
||||
VertexShader = VS_Eden;
|
||||
PixelShader = PS_Sharpen;
|
||||
}
|
||||
}
|
||||
Vendored
-50
@@ -1,50 +0,0 @@
|
||||
texture EdenBackBufferTex : COLOR;
|
||||
sampler EdenBackBuffer { Texture = EdenBackBufferTex; };
|
||||
|
||||
uniform float Strength <
|
||||
ui_type = "slider";
|
||||
ui_label = "Strength";
|
||||
ui_tooltip = "How dark the corners become.";
|
||||
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
||||
> = 0.6;
|
||||
|
||||
uniform float Aspect <
|
||||
ui_type = "slider";
|
||||
ui_label = "Aspect";
|
||||
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
||||
> = 1.0;
|
||||
|
||||
void VS_Eden(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
||||
{
|
||||
uv = float2(0.0, 0.0);
|
||||
if (id == 2)
|
||||
{
|
||||
uv.x = 2.0;
|
||||
}
|
||||
if (id == 1)
|
||||
{
|
||||
uv.y = 2.0;
|
||||
}
|
||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||
}
|
||||
|
||||
float4 PS_Vignette(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
{
|
||||
float2 diff = uv - 0.5;
|
||||
diff.x *= Aspect;
|
||||
diff.y /= max(Aspect, 0.0001);
|
||||
|
||||
float falloff = 1.0 - min(1.0, Strength * dot(diff, diff) * 2.0);
|
||||
float3 rgb = tex2D(EdenBackBuffer, uv).rgb;
|
||||
|
||||
return float4(rgb * falloff, 1.0);
|
||||
}
|
||||
|
||||
technique EdenVignette
|
||||
{
|
||||
pass
|
||||
{
|
||||
VertexShader = VS_Eden;
|
||||
PixelShader = PS_Vignette;
|
||||
}
|
||||
}
|
||||
@@ -100,8 +100,6 @@ For reasons unberknownst to any human being, `glslangValidator` will crash upon
|
||||
|
||||
For this reason this patch is NOT applied to default on all platforms (for obvious reasons) - instead this is a HaikuOS specific patch, apply with `git apply <absolute path to patch>` after cloning SPIRV-Tools then `make -C build` and add the resulting binary (in `build/StandAlone/glslang`) into PATH.
|
||||
|
||||
`cubeb_devel` will also not work, either disable cubeb or uninstall it.
|
||||
|
||||
Still will not run flawlessly until `mesa-24` is available. Modify CMakeCache.txt with the `.so` of libGL and libGLESv2 by doing the incredibly difficult task of copy pasting them (`cp /boot/system/lib/libGL.so .`)
|
||||
|
||||
If you have `quazip1_qt6_devel`, uninstall it. It may call `Core5Compat` on CMake which is wrongly packaged.
|
||||
|
||||
+2
-3
@@ -73,7 +73,6 @@ Certain other dependencies will be fetched by CPM regardless. System packages *c
|
||||
|
||||
* [SimpleIni](https://github.com/brofield/simpleini)
|
||||
* [DiscordRPC](https://github.com/eden-emulator/discord-rpc)
|
||||
* [cubeb](https://github.com/mozilla/cubeb)
|
||||
* [libusb](https://github.com/libusb/libusb)
|
||||
* [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator)
|
||||
* [sirit](https://github.com/eden-emulator/sirit)
|
||||
@@ -122,7 +121,7 @@ sudo emerge -a \
|
||||
dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \
|
||||
dev-util/vulkan-utility-libraries dev-util/glslang \
|
||||
media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \
|
||||
media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \
|
||||
media-libs/VulkanMemoryAllocator media-libs/libsdl3 \
|
||||
net-libs/enet \
|
||||
sys-libs/zlib \
|
||||
dev-cpp/nlohmann_json dev-cpp/simpleini dev-cpp/cpp-httplib dev-cpp/cpp-jwt \
|
||||
@@ -356,7 +355,7 @@ pacman -Syuu --needed --noconfirm $packages
|
||||
<summary>HaikuOS</summary>
|
||||
|
||||
```sh
|
||||
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
|
||||
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel simpleini quazip_qt6_devel
|
||||
```
|
||||
|
||||
[Caveats](./Caveats.md#haikuos).
|
||||
|
||||
@@ -4,7 +4,7 @@ Modern game consoles require heavy power to be emulated appropriately. This is w
|
||||
|
||||
For example, take a disk write, instead of emulating a proper SD card we instead use the C++ standard library for I/O. Additionally we use the abstractions provided by the `fs` service to "lie" to programs about certain SD card properties. Notably this includes making up sizes for the fake SD card, giving "realistic" values or expected outputs for a given card, and so on. And instead of writing to an actual SD card, the emulator simply writes to a file.
|
||||
|
||||
This also means grand part of the emulator consists of just re-implementing firmware but using HLE primitives; for example audio doesn't go to an emulated audio device, but rather gets processed on the fly by a dedicated service and then passed to SDL3/cubeb/etc.
|
||||
This also means grand part of the emulator consists of just re-implementing firmware but using HLE primitives; for example audio doesn't go to an emulated audio device, but rather gets processed on the fly by a dedicated service and then passed to SDL3 or null backend.
|
||||
|
||||
As such, many of the systems implemented are not 100% accurate to the original software, but they're "good enough" to pass as being so. While we do strive to maintain high compatibility (especially with homebrew), there are realistic limitations to these approaches.
|
||||
|
||||
|
||||
@@ -35,8 +35,6 @@ These options control dependencies.
|
||||
|
||||
- `ENABLE_WEB_SERVICE` (ON) Enable multiplayer service
|
||||
- `ENABLE_WIFI_SCAN` (OFF) Enable WiFi scanning (requires iw on Linux) - experimental
|
||||
- `ENABLE_CUBEB` (ON) Enables the cubeb audio backend
|
||||
- This option is subject for removal.
|
||||
- `YUZU_TESTS` (ON) Compile tests - requires Catch2
|
||||
- `ENABLE_LTO` (OFF) Enable link-time optimization
|
||||
- Not recommended on Windows
|
||||
|
||||
Vendored
-63
@@ -106,36 +106,6 @@ endif()
|
||||
# SimpleIni
|
||||
AddJsonPackage(simpleini)
|
||||
|
||||
# Most linux distros don't package cubeb, so enable regardless of cpm settings
|
||||
if(ENABLE_CUBEB)
|
||||
AddJsonPackage(cubeb)
|
||||
|
||||
if (cubeb_ADDED)
|
||||
if (NOT MSVC)
|
||||
if (TARGET speex)
|
||||
target_compile_options(speex PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-compare>)
|
||||
endif()
|
||||
set_target_properties(cubeb PROPERTIES COMPILE_OPTIONS "")
|
||||
target_compile_options(cubeb INTERFACE
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-const-int-float-conversion>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shadow>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-missing-declarations>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-return-type>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-uninitialized>
|
||||
)
|
||||
else()
|
||||
target_compile_options(cubeb PRIVATE
|
||||
/wd4456
|
||||
/wd4458
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT TARGET cubeb::cubeb)
|
||||
add_library(cubeb::cubeb ALIAS cubeb)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT YUZU_USE_BUNDLED_SDL3)
|
||||
if (NOT WIN32)
|
||||
# Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers
|
||||
@@ -195,39 +165,6 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# reshadefx
|
||||
if (ENABLE_RESHADE)
|
||||
AddJsonPackage(NAME reshade DOWNLOAD_ONLY)
|
||||
|
||||
set(RESHADEFX_SHIM_DIR ${CMAKE_CURRENT_BINARY_DIR}/reshadefx_shim)
|
||||
file(WRITE ${RESHADEFX_SHIM_DIR}/spirv.hpp "#include <spirv/unified1/spirv.hpp>\n")
|
||||
file(WRITE ${RESHADEFX_SHIM_DIR}/GLSL.std.450.h "#include <spirv/unified1/GLSL.std.450.h>\n")
|
||||
|
||||
add_library(reshadefx STATIC
|
||||
${reshade_SOURCE_DIR}/source/effect_codegen_spirv.cpp
|
||||
${reshade_SOURCE_DIR}/source/effect_expression.cpp
|
||||
${reshade_SOURCE_DIR}/source/effect_lexer.cpp
|
||||
${reshade_SOURCE_DIR}/source/effect_parser_exp.cpp
|
||||
${reshade_SOURCE_DIR}/source/effect_parser_stmt.cpp
|
||||
${reshade_SOURCE_DIR}/source/effect_preprocessor.cpp
|
||||
${reshade_SOURCE_DIR}/source/effect_symbol_table.cpp
|
||||
)
|
||||
|
||||
target_include_directories(reshadefx SYSTEM PUBLIC ${reshade_SOURCE_DIR}/source)
|
||||
target_include_directories(reshadefx PRIVATE ${RESHADEFX_SHIM_DIR})
|
||||
target_link_libraries(reshadefx PUBLIC SPIRV-Headers::SPIRV-Headers)
|
||||
|
||||
if (NOT MSVC)
|
||||
target_compile_options(reshadefx PRIVATE -w)
|
||||
else()
|
||||
target_compile_options(reshadefx PRIVATE /w)
|
||||
endif()
|
||||
|
||||
if (NOT TARGET reshadefx::reshadefx)
|
||||
add_library(reshadefx::reshadefx ALIAS reshadefx)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Catch2
|
||||
if (YUZU_TESTS OR DYNARMIC_TESTS)
|
||||
AddJsonPackage(catch2)
|
||||
|
||||
@@ -15,7 +15,7 @@ pkgs.mkShellNoCC {
|
||||
enet libopus vulkan-headers vulkan-utility-libraries
|
||||
spirv-tools spirv-headers vulkan-loader unzip
|
||||
glslang python3 httplib cpp-jwt ffmpeg-headless
|
||||
libusb1 cubeb
|
||||
libusb1
|
||||
# eden
|
||||
qt6.qtbase qt6.qtmultimedia qt6.qtwayland qt6.qttools
|
||||
qt6.qtwebengine qt6.qt5compat
|
||||
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
abstract class FxUniformSetting(
|
||||
protected val index: Int,
|
||||
protected val uniform: NativePostProcessing.Uniform,
|
||||
protected val component: Int
|
||||
) : AbstractSetting {
|
||||
override val key: String
|
||||
get() = "fx_${index}_${uniform.name}_$component"
|
||||
|
||||
override val isRuntimeModifiable: Boolean
|
||||
get() = true
|
||||
|
||||
override val pairedSettingKey: String
|
||||
get() = ""
|
||||
|
||||
override val isSwitchable: Boolean
|
||||
get() = false
|
||||
|
||||
override val isSaveable: Boolean
|
||||
get() = true
|
||||
|
||||
override var global: Boolean
|
||||
get() = true
|
||||
set(_) {}
|
||||
|
||||
protected fun currentValue(): Float {
|
||||
if (NativePostProcessing.hasValue(index, uniform.name)) {
|
||||
return NativePostProcessing.getValue(index, uniform.name, component)
|
||||
}
|
||||
return uniform.defaultAt(component)
|
||||
}
|
||||
|
||||
protected fun commit(value: Float) {
|
||||
NativePostProcessing.setValue(index, uniform.name, component, value)
|
||||
NativePostProcessing.store()
|
||||
}
|
||||
|
||||
override fun reset() = commit(uniform.defaultAt(component))
|
||||
}
|
||||
|
||||
class FxUniformSliderSetting(
|
||||
index: Int,
|
||||
uniform: NativePostProcessing.Uniform,
|
||||
component: Int
|
||||
) : FxUniformSetting(index, uniform, component), AbstractIntSetting {
|
||||
override val defaultValue: Any
|
||||
get() = ((uniform.defaultAt(component) - uniform.min) / uniform.step).roundToInt()
|
||||
|
||||
override fun getInt(needsGlobal: Boolean): Int =
|
||||
((currentValue() - uniform.min) / uniform.step).roundToInt()
|
||||
|
||||
override fun setInt(value: Int) = commit(uniform.min + value * uniform.step)
|
||||
|
||||
override fun getValueAsString(needsGlobal: Boolean): String {
|
||||
if (uniform.kind == NativePostProcessing.KIND_FLOAT) {
|
||||
return String.format("%.3f", currentValue())
|
||||
}
|
||||
return currentValue().roundToInt().toString()
|
||||
}
|
||||
}
|
||||
|
||||
class FxUniformChoiceSetting(
|
||||
index: Int,
|
||||
uniform: NativePostProcessing.Uniform,
|
||||
component: Int
|
||||
) : FxUniformSetting(index, uniform, component), AbstractIntSetting {
|
||||
override val defaultValue: Any
|
||||
get() = uniform.defaultAt(component).roundToInt()
|
||||
|
||||
override fun getInt(needsGlobal: Boolean): Int = currentValue().roundToInt()
|
||||
|
||||
override fun setInt(value: Int) = commit(value.toFloat())
|
||||
|
||||
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
|
||||
}
|
||||
|
||||
class FxUniformBooleanSetting(
|
||||
index: Int,
|
||||
uniform: NativePostProcessing.Uniform,
|
||||
component: Int
|
||||
) : FxUniformSetting(index, uniform, component), AbstractBooleanSetting {
|
||||
override val defaultValue: Any
|
||||
get() = uniform.defaultAt(component) != 0f
|
||||
|
||||
override fun getBoolean(needsGlobal: Boolean): Boolean = currentValue() != 0f
|
||||
|
||||
override fun setBoolean(value: Boolean) {
|
||||
if (value) {
|
||||
commit(1f)
|
||||
return
|
||||
}
|
||||
commit(0f)
|
||||
}
|
||||
|
||||
override fun getValueAsString(needsGlobal: Boolean): String = getBoolean().toString()
|
||||
}
|
||||
@@ -12,7 +12,6 @@ object Settings {
|
||||
SECTION_SYSTEM(R.string.preferences_system),
|
||||
SECTION_RENDERER(R.string.preferences_graphics),
|
||||
SECTION_FRAME_GEN(R.string.frame_gen),
|
||||
SECTION_POST_PROCESSING(R.string.post_processing),
|
||||
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
|
||||
SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
|
||||
SECTION_SOC_OVERLAY(R.string.soc_overlay_options),
|
||||
|
||||
-1
@@ -13,7 +13,6 @@ enum class StringSetting(override val key: String) : AbstractStringSetting {
|
||||
DEVICE_NAME("device_name"),
|
||||
LOG_FILTER("log_filter"),
|
||||
PROGRAM_ARGS("program_args"),
|
||||
POST_SHADER_CHAIN("post_shader_chain"),
|
||||
|
||||
WEB_TOKEN("eden_token"),
|
||||
WEB_USERNAME("eden_username")
|
||||
|
||||
-218
@@ -18,9 +18,6 @@ import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.FxUniformBooleanSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.FxUniformChoiceSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.FxUniformSliderSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||
@@ -33,7 +30,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.FullscreenHelper
|
||||
import androidx.core.content.edit
|
||||
@@ -167,7 +163,6 @@ class SettingsFragmentPresenter(
|
||||
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||
MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl)
|
||||
MenuTag.SECTION_POST_PROCESSING -> addPostProcessingSettings(sl)
|
||||
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
|
||||
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
|
||||
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
|
||||
@@ -195,219 +190,6 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun addPostProcessingSettings(sl: ArrayList<SettingsItem>) {
|
||||
val usable = NativePostProcessing.catalog().filter { it.valid }
|
||||
|
||||
sl.apply {
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.post_processing_reload,
|
||||
descriptionId = R.string.post_processing_reload_description,
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.reload()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
|
||||
if (usable.isEmpty()) {
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.post_processing_empty,
|
||||
descriptionString = NativePostProcessing.getShaderDirectory(),
|
||||
isRunnable = false
|
||||
) {}
|
||||
)
|
||||
return@apply
|
||||
}
|
||||
|
||||
val labels = mutableListOf<String>()
|
||||
val files = mutableListOf<String>()
|
||||
val techniques = mutableListOf<String>()
|
||||
for (effect in usable) {
|
||||
for (technique in effect.techniques) {
|
||||
if (effect.techniques.size == 1) {
|
||||
labels.add(effect.name)
|
||||
} else {
|
||||
labels.add(effect.name + " \u00b7 " + technique)
|
||||
}
|
||||
files.add(effect.file)
|
||||
techniques.add(technique)
|
||||
}
|
||||
}
|
||||
|
||||
val chain = NativePostProcessing.chain()
|
||||
for (index in chain.indices) {
|
||||
val entry = chain[index]
|
||||
val effect = usable.firstOrNull { it.file == entry.file }
|
||||
|
||||
var header = entry.file
|
||||
if (effect != null) {
|
||||
header = effect.name
|
||||
}
|
||||
add(HeaderSetting(titleString = header))
|
||||
|
||||
add(
|
||||
IntSingleChoiceSetting(
|
||||
buildSlotSelector(index, entry, files, techniques),
|
||||
titleId = R.string.post_processing_effect,
|
||||
choices = labels.toTypedArray(),
|
||||
values = labels.indices.toList().toTypedArray()
|
||||
)
|
||||
)
|
||||
|
||||
if (effect != null) {
|
||||
for (uniform in effect.uniforms) {
|
||||
addUniform(this, index, uniform)
|
||||
}
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.post_processing_move_up,
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.move(index, -1)
|
||||
NativePostProcessing.store()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
}
|
||||
if (index < chain.size - 1) {
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.post_processing_move_down,
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.move(index, 1)
|
||||
NativePostProcessing.store()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
}
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.post_processing_reset,
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.resetValues(index)
|
||||
NativePostProcessing.store()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.post_processing_remove,
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.remove(index)
|
||||
NativePostProcessing.store()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.post_processing_add,
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.append(files[0], techniques[0])
|
||||
NativePostProcessing.store()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildSlotSelector(
|
||||
index: Int,
|
||||
entry: NativePostProcessing.ChainEntry,
|
||||
files: List<String>,
|
||||
techniques: List<String>
|
||||
): AbstractIntSetting = object : AbstractIntSetting {
|
||||
override val key = "fx_slot_$index"
|
||||
|
||||
override fun getInt(needsGlobal: Boolean): Int {
|
||||
for (i in files.indices) {
|
||||
if (files[i] == entry.file && techniques[i] == entry.technique) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
override fun setInt(value: Int) {
|
||||
NativePostProcessing.replace(index, files[value], techniques[value])
|
||||
NativePostProcessing.store()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
|
||||
override val defaultValue = 0
|
||||
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
|
||||
override fun reset() {}
|
||||
override val isRuntimeModifiable = true
|
||||
override val pairedSettingKey = ""
|
||||
override val isSwitchable = false
|
||||
override val isSaveable = true
|
||||
override var global: Boolean
|
||||
get() = true
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
private fun addUniform(
|
||||
sl: ArrayList<SettingsItem>,
|
||||
index: Int,
|
||||
uniform: NativePostProcessing.Uniform
|
||||
) {
|
||||
if (uniform.uiType == NativePostProcessing.UI_CHECKBOX ||
|
||||
uniform.kind == NativePostProcessing.KIND_BOOL
|
||||
) {
|
||||
sl.add(
|
||||
SwitchSetting(
|
||||
FxUniformBooleanSetting(index, uniform, 0),
|
||||
titleString = uniform.label,
|
||||
descriptionString = uniform.tooltip
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (uniform.items.isNotEmpty() &&
|
||||
(uniform.uiType == NativePostProcessing.UI_COMBO ||
|
||||
uniform.uiType == NativePostProcessing.UI_RADIO)
|
||||
) {
|
||||
sl.add(
|
||||
IntSingleChoiceSetting(
|
||||
FxUniformChoiceSetting(index, uniform, 0),
|
||||
titleString = uniform.label,
|
||||
descriptionString = uniform.tooltip,
|
||||
choices = uniform.items.toTypedArray(),
|
||||
values = uniform.items.indices.toList().toTypedArray()
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
for (component in 0 until uniform.components) {
|
||||
var title = uniform.label
|
||||
if (uniform.components > 1) {
|
||||
title = uniform.label + " [" + component + "]"
|
||||
}
|
||||
sl.add(
|
||||
SliderSetting(
|
||||
FxUniformSliderSetting(index, uniform, component),
|
||||
titleString = title,
|
||||
descriptionString = uniform.tooltip,
|
||||
min = 0,
|
||||
max = uniform.steps
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(
|
||||
|
||||
@@ -383,20 +383,6 @@ class GamePropertiesFragment : Fragment() {
|
||||
}
|
||||
)
|
||||
)
|
||||
add(
|
||||
SubmenuProperty(
|
||||
R.string.post_processing,
|
||||
R.string.post_processing_per_game_description,
|
||||
R.drawable.ic_post_processing,
|
||||
action = {
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
args.game,
|
||||
Settings.MenuTag.SECTION_POST_PROCESSING
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
if (GpuDriverHelper.isAdrenoGpu()) {
|
||||
add(
|
||||
|
||||
@@ -171,20 +171,6 @@ class HomeSettingsFragment : Fragment() {
|
||||
)
|
||||
)
|
||||
}
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.post_processing,
|
||||
R.string.post_processing_description,
|
||||
R.drawable.ic_post_processing,
|
||||
{
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
null,
|
||||
Settings.MenuTag.SECTION_POST_PROCESSING
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.lossless_scaling,
|
||||
|
||||
@@ -58,13 +58,6 @@ class LicensesFragment : Fragment() {
|
||||
R.string.license_fidelityfx_fsr_copyright,
|
||||
R.string.license_fidelityfx_fsr_text
|
||||
),
|
||||
License(
|
||||
R.string.license_cubeb,
|
||||
R.string.license_cubeb_description,
|
||||
R.string.license_cubeb_link,
|
||||
R.string.license_cubeb_copyright,
|
||||
R.string.license_cubeb_text
|
||||
),
|
||||
License(
|
||||
R.string.license_dynarmic,
|
||||
R.string.license_dynarmic_description,
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
object NativePostProcessing {
|
||||
const val KIND_BOOL = 0
|
||||
const val KIND_INT = 1
|
||||
const val KIND_FLOAT = 2
|
||||
|
||||
const val UI_HIDDEN = 0
|
||||
const val UI_SLIDER = 1
|
||||
const val UI_DRAG = 2
|
||||
const val UI_COMBO = 3
|
||||
const val UI_RADIO = 4
|
||||
const val UI_CHECKBOX = 5
|
||||
const val UI_COLOR = 6
|
||||
const val UI_INPUT_BOX = 7
|
||||
|
||||
external fun getCatalogJson(): String
|
||||
|
||||
external fun getChainJson(): String
|
||||
|
||||
external fun append(file: String, technique: String)
|
||||
|
||||
external fun replace(index: Int, file: String, technique: String)
|
||||
|
||||
external fun remove(index: Int)
|
||||
|
||||
external fun move(index: Int, delta: Int)
|
||||
|
||||
external fun resetValues(index: Int)
|
||||
|
||||
external fun getValue(index: Int, uniform: String, component: Int): Float
|
||||
|
||||
external fun hasValue(index: Int, uniform: String): Boolean
|
||||
|
||||
external fun setValue(index: Int, uniform: String, component: Int, value: Float)
|
||||
|
||||
external fun store()
|
||||
|
||||
external fun reload()
|
||||
|
||||
external fun getShaderDirectory(): String
|
||||
|
||||
data class Uniform(
|
||||
val name: String,
|
||||
val label: String,
|
||||
val tooltip: String,
|
||||
val category: String,
|
||||
val kind: Int,
|
||||
val uiType: Int,
|
||||
val components: Int,
|
||||
val min: Float,
|
||||
val max: Float,
|
||||
val step: Float,
|
||||
val items: List<String>,
|
||||
val defaults: List<Float>
|
||||
) {
|
||||
val steps: Int
|
||||
get() {
|
||||
val span = max - min
|
||||
if (step <= 0f) {
|
||||
return 1
|
||||
}
|
||||
val count = Math.round(span / step)
|
||||
if (count < 1) {
|
||||
return 1
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
fun defaultAt(component: Int): Float {
|
||||
if (component < defaults.size) {
|
||||
return defaults[component]
|
||||
}
|
||||
return 0f
|
||||
}
|
||||
}
|
||||
|
||||
data class Effect(
|
||||
val file: String,
|
||||
val name: String,
|
||||
val error: String,
|
||||
val techniques: List<String>,
|
||||
val uniforms: List<Uniform>
|
||||
) {
|
||||
val valid: Boolean
|
||||
get() = error.isEmpty() && techniques.isNotEmpty()
|
||||
}
|
||||
|
||||
data class ChainEntry(val file: String, val technique: String)
|
||||
|
||||
fun catalog(): List<Effect> {
|
||||
val out = mutableListOf<Effect>()
|
||||
val array = JSONArray(getCatalogJson())
|
||||
for (i in 0 until array.length()) {
|
||||
val obj = array.getJSONObject(i)
|
||||
out.add(
|
||||
Effect(
|
||||
file = obj.optString("file"),
|
||||
name = obj.optString("name"),
|
||||
error = obj.optString("error"),
|
||||
techniques = obj.optJSONArray("techniques").toStringList(),
|
||||
uniforms = obj.optJSONArray("uniforms").toUniformList()
|
||||
)
|
||||
)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
fun chain(): List<ChainEntry> {
|
||||
val out = mutableListOf<ChainEntry>()
|
||||
val array = JSONArray(getChainJson())
|
||||
for (i in 0 until array.length()) {
|
||||
val obj = array.getJSONObject(i)
|
||||
out.add(ChainEntry(obj.optString("file"), obj.optString("technique")))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
fun findEffect(file: String): Effect? = catalog().firstOrNull { it.file == file }
|
||||
|
||||
private fun JSONArray?.toStringList(): List<String> {
|
||||
if (this == null) {
|
||||
return emptyList()
|
||||
}
|
||||
val out = mutableListOf<String>()
|
||||
for (i in 0 until length()) {
|
||||
out.add(optString(i))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
private fun JSONArray?.toFloatList(): List<Float> {
|
||||
if (this == null) {
|
||||
return emptyList()
|
||||
}
|
||||
val out = mutableListOf<Float>()
|
||||
for (i in 0 until length()) {
|
||||
out.add(optDouble(i, 0.0).toFloat())
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
private fun JSONArray?.toUniformList(): List<Uniform> {
|
||||
if (this == null) {
|
||||
return emptyList()
|
||||
}
|
||||
val out = mutableListOf<Uniform>()
|
||||
for (i in 0 until length()) {
|
||||
val obj: JSONObject = optJSONObject(i) ?: continue
|
||||
out.add(
|
||||
Uniform(
|
||||
name = obj.optString("name"),
|
||||
label = obj.optString("label"),
|
||||
tooltip = obj.optString("tooltip"),
|
||||
category = obj.optString("category"),
|
||||
kind = obj.optInt("kind", KIND_FLOAT),
|
||||
uiType = obj.optInt("uiType", UI_HIDDEN),
|
||||
components = obj.optInt("components", 1),
|
||||
min = obj.optDouble("min", 0.0).toFloat(),
|
||||
max = obj.optDouble("max", 1.0).toFloat(),
|
||||
step = obj.optDouble("step", 0.01).toFloat(),
|
||||
items = obj.optJSONArray("items").toStringList(),
|
||||
defaults = obj.optJSONArray("defaults").toFloatList()
|
||||
)
|
||||
)
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ add_library(yuzu-android SHARED
|
||||
android_config.cpp
|
||||
android_config.h
|
||||
native_input.cpp
|
||||
native_post_processing.cpp
|
||||
)
|
||||
|
||||
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <jni.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "common/android/android_common.h"
|
||||
#ifdef HAS_RESHADE
|
||||
#include "video_core/post_processing/fx_chain.h"
|
||||
#include "video_core/post_processing/fx_effect.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
#ifdef HAS_RESHADE
|
||||
nlohmann::json SerializeUniform(const VideoCore::FxUniformDesc& uniform) {
|
||||
nlohmann::json out;
|
||||
out["name"] = uniform.name;
|
||||
out["label"] = uniform.label;
|
||||
out["tooltip"] = uniform.tooltip;
|
||||
out["category"] = uniform.category;
|
||||
out["kind"] = static_cast<int>(uniform.kind);
|
||||
out["uiType"] = static_cast<int>(uniform.ui_type);
|
||||
out["components"] = uniform.components;
|
||||
out["min"] = uniform.ui_min;
|
||||
out["max"] = uniform.ui_max;
|
||||
out["step"] = uniform.ui_step;
|
||||
out["items"] = uniform.items;
|
||||
|
||||
nlohmann::json defaults = nlohmann::json::array();
|
||||
for (u32 i = 0; i < uniform.components; ++i) {
|
||||
defaults.push_back(uniform.default_value[i]);
|
||||
}
|
||||
out["defaults"] = defaults;
|
||||
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getCatalogJson(JNIEnv* env,
|
||||
jobject obj) {
|
||||
nlohmann::json out = nlohmann::json::array();
|
||||
|
||||
#ifdef HAS_RESHADE
|
||||
for (const auto& effect : VideoCore::GetFxCatalog()) {
|
||||
nlohmann::json entry;
|
||||
entry["file"] = effect.file;
|
||||
entry["name"] = effect.name;
|
||||
entry["error"] = effect.error;
|
||||
entry["techniques"] = effect.techniques;
|
||||
|
||||
nlohmann::json uniforms = nlohmann::json::array();
|
||||
for (const auto& uniform : effect.uniforms) {
|
||||
uniforms.push_back(SerializeUniform(uniform));
|
||||
}
|
||||
entry["uniforms"] = uniforms;
|
||||
|
||||
out.push_back(entry);
|
||||
}
|
||||
#endif
|
||||
|
||||
return Common::Android::ToJString(env, out.dump());
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getChainJson(JNIEnv* env, jobject obj) {
|
||||
nlohmann::json out = nlohmann::json::array();
|
||||
|
||||
#ifdef HAS_RESHADE
|
||||
for (const auto& entry : VideoCore::FxChain::Instance().Entries()) {
|
||||
nlohmann::json item;
|
||||
item["file"] = entry.file;
|
||||
item["technique"] = entry.technique;
|
||||
|
||||
nlohmann::json values = nlohmann::json::object();
|
||||
for (const auto& [name, value] : entry.values) {
|
||||
values[name] = {value[0], value[1], value[2], value[3]};
|
||||
}
|
||||
item["values"] = values;
|
||||
|
||||
out.push_back(item);
|
||||
}
|
||||
#endif
|
||||
|
||||
return Common::Android::ToJString(env, out.dump());
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_append(JNIEnv* env, jobject obj,
|
||||
jstring jfile, jstring jtechnique) {
|
||||
#ifdef HAS_RESHADE
|
||||
VideoCore::FxChain::Instance().Append(Common::Android::GetJString(env, jfile),
|
||||
Common::Android::GetJString(env, jtechnique));
|
||||
#endif
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_replace(JNIEnv* env, jobject obj,
|
||||
jint index, jstring jfile,
|
||||
jstring jtechnique) {
|
||||
#ifdef HAS_RESHADE
|
||||
VideoCore::FxChain::Instance().Replace(static_cast<size_t>(index),
|
||||
Common::Android::GetJString(env, jfile),
|
||||
Common::Android::GetJString(env, jtechnique));
|
||||
#endif
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_remove(JNIEnv* env, jobject obj,
|
||||
jint index) {
|
||||
#ifdef HAS_RESHADE
|
||||
VideoCore::FxChain::Instance().Remove(static_cast<size_t>(index));
|
||||
#endif
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_move(JNIEnv* env, jobject obj, jint index,
|
||||
jint delta) {
|
||||
#ifdef HAS_RESHADE
|
||||
VideoCore::FxChain::Instance().Move(static_cast<size_t>(index), delta);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_resetValues(JNIEnv* env, jobject obj,
|
||||
jint index) {
|
||||
#ifdef HAS_RESHADE
|
||||
VideoCore::FxChain::Instance().ResetValues(static_cast<size_t>(index));
|
||||
#endif
|
||||
}
|
||||
|
||||
jfloat Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getValue(JNIEnv* env, jobject obj,
|
||||
jint index, jstring juniform,
|
||||
jint component) {
|
||||
#ifdef HAS_RESHADE
|
||||
if (component < 0 || component >= 4) {
|
||||
return 0.0f;
|
||||
}
|
||||
const auto value = VideoCore::FxChain::Instance().GetValue(
|
||||
static_cast<size_t>(index), Common::Android::GetJString(env, juniform));
|
||||
return value[static_cast<size_t>(component)];
|
||||
#else
|
||||
return 0.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_hasValue(JNIEnv* env, jobject obj,
|
||||
jint index,
|
||||
jstring juniform) {
|
||||
#ifdef HAS_RESHADE
|
||||
return static_cast<jboolean>(VideoCore::FxChain::Instance().HasValue(
|
||||
static_cast<size_t>(index), Common::Android::GetJString(env, juniform)));
|
||||
#else
|
||||
return static_cast<jboolean>(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_setValue(JNIEnv* env, jobject obj,
|
||||
jint index, jstring juniform,
|
||||
jint component, jfloat value) {
|
||||
#ifdef HAS_RESHADE
|
||||
if (component < 0 || component >= 4) {
|
||||
return;
|
||||
}
|
||||
const std::string uniform = Common::Android::GetJString(env, juniform);
|
||||
auto& chain = VideoCore::FxChain::Instance();
|
||||
|
||||
auto current = chain.GetValue(static_cast<size_t>(index), uniform);
|
||||
current[static_cast<size_t>(component)] = value;
|
||||
chain.SetValue(static_cast<size_t>(index), uniform, current);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_store(JNIEnv* env, jobject obj) {
|
||||
#ifdef HAS_RESHADE
|
||||
VideoCore::FxChain::Instance().StoreToSettings();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_reload(JNIEnv* env, jobject obj) {
|
||||
#ifdef HAS_RESHADE
|
||||
VideoCore::ReloadFxCatalog();
|
||||
VideoCore::FxChain::Instance().DropUnknownEntries();
|
||||
#endif
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getShaderDirectory(JNIEnv* env,
|
||||
jobject obj) {
|
||||
#ifdef HAS_RESHADE
|
||||
return Common::Android::ToJString(env, VideoCore::GetFxRootDirectory().string());
|
||||
#else
|
||||
return Common::Android::ToJString(env, "");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M8.5,3 L19.4,3 Q21,3 21,4.6 L21,16.5 L19.4,16.5 L19.4,4.6 L8.5,4.6 Z M5,7 L15,7 Q17,7 17,9 L17,19 Q17,21 15,21 L5,21 Q3,21 3,19 L3,9 Q3,7 5,7 Z M5.2,8.6 L14.8,8.6 Q15.4,8.6 15.4,9.2 L15.4,18.8 Q15.4,19.4 14.8,19.4 L5.2,19.4 Q4.6,19.4 4.6,18.8 L4.6,9.2 Q4.6,8.6 5.2,8.6 Z M10,10.9 A3.1,3.1 0 0 1 10,17.1 Z"/>
|
||||
</vector>
|
||||
@@ -799,9 +799,6 @@
|
||||
<string name="theme_mode_light">روشن</string>
|
||||
<string name="theme_mode_dark">تاریک</string>
|
||||
|
||||
<!-- Audio output engines -->
|
||||
<string name="cubeb">cubeb</string>
|
||||
|
||||
<!-- Anisotropic filtering options -->
|
||||
<string name="multiplier_x2">x2</string>
|
||||
<string name="multiplier_x4">x4</string>
|
||||
|
||||
@@ -466,13 +466,11 @@
|
||||
<string-array name="outputEngineEntries">
|
||||
<item>@string/auto</item>
|
||||
<item>@string/sdl3</item>
|
||||
<item>@string/cubeb</item>
|
||||
<item>@string/string_null</item>
|
||||
</string-array>
|
||||
<integer-array name="outputEngineValues">
|
||||
<item>0</item>
|
||||
<item>2</item>
|
||||
<item>1</item>
|
||||
<item>3</item>
|
||||
</integer-array>
|
||||
|
||||
|
||||
@@ -298,18 +298,6 @@
|
||||
<string name="gpu_driver_fetcher">GPU driver fetcher</string>
|
||||
<string name="gpu_driver_manager">GPU driver manager</string>
|
||||
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
||||
<string name="post_processing">Post-Processing Effects</string>
|
||||
<string name="post_processing_description">ReShade FX effects applied after rendering</string>
|
||||
<string name="post_processing_per_game_description">Configure the effect chain for this game</string>
|
||||
<string name="post_processing_effect">Effect</string>
|
||||
<string name="post_processing_add">Add effect</string>
|
||||
<string name="post_processing_remove">Remove</string>
|
||||
<string name="post_processing_move_up">Move up</string>
|
||||
<string name="post_processing_move_down">Move down</string>
|
||||
<string name="post_processing_reset">Reset to defaults</string>
|
||||
<string name="post_processing_reload">Reload from disk</string>
|
||||
<string name="post_processing_reload_description">Rescan the shader folder for .fx files</string>
|
||||
<string name="post_processing_empty">No effects found. Place .fx files in this folder:</string>
|
||||
<string name="frame_gen">Frame generation</string>
|
||||
<string name="frame_gen_per_game_description">Configure frame generation for this game</string>
|
||||
<string name="frame_gen_description">Insert interpolated frames between rendered ones using Lossless Scaling. Forces FIFO presentation while enabled.</string>
|
||||
@@ -1253,7 +1241,6 @@
|
||||
|
||||
<!-- Audio output engines -->
|
||||
<string name="sdl3" translatable="false">SDL3</string>
|
||||
<string name="cubeb" translatable="false">cubeb</string>
|
||||
|
||||
<!-- Anisotropic filtering options -->
|
||||
<string name="multiplier_x2" translatable="false">x2</string>
|
||||
@@ -1393,23 +1380,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
</string>
|
||||
<string name="license_cubeb" translatable="false">cubeb</string>
|
||||
<string name="license_cubeb_description" translatable="false">Cross platform audio library</string>
|
||||
<string name="license_cubeb_link" translatable="false">https://github.com/mozilla/cubeb</string>
|
||||
<string name="license_cubeb_copyright" translatable="false">Copyright © 2011 Mozilla Foundation</string>
|
||||
<string name="license_cubeb_text" translatable="false">
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.\n\n
|
||||
|
||||
THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
</string>
|
||||
<string name="license_dynarmic" translatable="false">Dynarmic</string>
|
||||
<string name="license_dynarmic_description" translatable="false">An ARM dynamic recompiler</string>
|
||||
|
||||
@@ -229,15 +229,6 @@ endif()
|
||||
target_include_directories(audio_core PRIVATE ${OPUS_INCLUDE_DIRS})
|
||||
target_link_libraries(audio_core PUBLIC common core Opus::opus)
|
||||
|
||||
if (ENABLE_CUBEB)
|
||||
target_sources(audio_core PRIVATE
|
||||
sink/cubeb_sink.cpp
|
||||
sink/cubeb_sink.h)
|
||||
|
||||
target_link_libraries(audio_core PRIVATE cubeb::cubeb)
|
||||
target_compile_definitions(audio_core PRIVATE HAVE_CUBEB=1)
|
||||
endif()
|
||||
|
||||
target_sources(audio_core PRIVATE
|
||||
sink/sdl3_sink.cpp
|
||||
sink/sdl3_sink.h)
|
||||
@@ -245,4 +236,8 @@ target_sources(audio_core PRIVATE
|
||||
target_link_libraries(audio_core PRIVATE SDL3::SDL3)
|
||||
target_compile_definitions(audio_core PRIVATE HAVE_SDL3)
|
||||
|
||||
if (ANDROID)
|
||||
target_link_libraries(audio_core PRIVATE OpenSLES)
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(audio_core)
|
||||
|
||||
@@ -1,450 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/sink/cubeb_sink.h"
|
||||
#include "audio_core/sink/sink_stream.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <objbase.h>
|
||||
#undef CreateEvent
|
||||
#endif
|
||||
|
||||
namespace AudioCore::Sink {
|
||||
/**
|
||||
* Cubeb sink stream, responsible for sinking samples to hardware.
|
||||
*/
|
||||
class CubebSinkStream final : public SinkStream {
|
||||
public:
|
||||
/**
|
||||
* Create a new sink stream.
|
||||
*
|
||||
* @param ctx_ - Cubeb context to create this stream with.
|
||||
* @param device_channels_ - Number of channels supported by the hardware.
|
||||
* @param system_channels_ - Number of channels the audio systems expect.
|
||||
* @param output_device - Cubeb output device id.
|
||||
* @param input_device - Cubeb input device id.
|
||||
* @param name_ - Name of this stream.
|
||||
* @param type_ - Type of this stream.
|
||||
* @param system_ - Core system.
|
||||
* @param event - Event used only for audio renderer, signalled on buffer consume.
|
||||
*/
|
||||
CubebSinkStream(cubeb* ctx_, u32 device_channels_, u32 system_channels_,
|
||||
cubeb_devid output_device, cubeb_devid input_device, const std::string& name_,
|
||||
StreamType type_, Core::System& system_)
|
||||
: SinkStream(system_, type_), ctx{ctx_} {
|
||||
#ifdef _WIN32
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
name = name_;
|
||||
device_channels = device_channels_;
|
||||
system_channels = system_channels_;
|
||||
|
||||
cubeb_stream_params params{};
|
||||
params.rate = TargetSampleRate;
|
||||
params.channels = device_channels;
|
||||
params.format = CUBEB_SAMPLE_S16LE;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
switch (params.channels) {
|
||||
case 1:
|
||||
params.layout = CUBEB_LAYOUT_MONO;
|
||||
break;
|
||||
case 2:
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
break;
|
||||
case 6:
|
||||
params.layout = CUBEB_LAYOUT_3F2_LFE;
|
||||
break;
|
||||
}
|
||||
|
||||
u32 minimum_latency{0};
|
||||
const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &minimum_latency);
|
||||
if (latency_error != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
|
||||
minimum_latency = TargetSampleCount * 2;
|
||||
}
|
||||
|
||||
minimum_latency = (std::max)(minimum_latency, TargetSampleCount * 2);
|
||||
|
||||
LOG_INFO(Service_Audio,
|
||||
"Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) "
|
||||
"latency {}",
|
||||
name, type, params.rate, params.channels, system_channels, minimum_latency);
|
||||
|
||||
auto init_error{0};
|
||||
if (type == StreamType::In) {
|
||||
init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device,
|
||||
¶ms, output_device, nullptr, minimum_latency,
|
||||
&CubebSinkStream::DataCallback,
|
||||
&CubebSinkStream::StateCallback, this);
|
||||
} else {
|
||||
init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device,
|
||||
nullptr, output_device, ¶ms, minimum_latency,
|
||||
&CubebSinkStream::DataCallback,
|
||||
&CubebSinkStream::StateCallback, this);
|
||||
}
|
||||
|
||||
if (init_error != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream, error: {}", init_error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the sink stream.
|
||||
*/
|
||||
~CubebSinkStream() override {
|
||||
LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name);
|
||||
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
Finalize();
|
||||
|
||||
#ifdef _WIN32
|
||||
CoUninitialize();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize the sink stream.
|
||||
*/
|
||||
void Finalize() override {
|
||||
Stop();
|
||||
cubeb_stream_destroy(stream_backend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the sink stream.
|
||||
*
|
||||
* @param resume - Set to true if this is resuming the stream a previously-active stream.
|
||||
* Default false.
|
||||
*/
|
||||
void Start(bool resume = false) override {
|
||||
if (!ctx || !paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
paused = false;
|
||||
if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the sink stream.
|
||||
*/
|
||||
void Stop() override {
|
||||
if (!ctx || paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
SignalPause();
|
||||
if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will
|
||||
* provide samples to be copied (audio in).
|
||||
*
|
||||
* @param stream - Cubeb-specific data about the stream.
|
||||
* @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
|
||||
* @param in_buff - Input buffer to be used if the stream is an input type.
|
||||
* @param out_buff - Output buffer to be used if the stream is an output type.
|
||||
* @param num_frames_ - Number of frames of audio in the buffers. Note: Not number of samples.
|
||||
*/
|
||||
static long DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data,
|
||||
[[maybe_unused]] const void* in_buff, void* out_buff,
|
||||
long num_frames_) {
|
||||
auto* impl = static_cast<CubebSinkStream*>(user_data);
|
||||
if (!impl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const std::size_t num_channels = impl->GetDeviceChannels();
|
||||
const std::size_t frame_size = num_channels;
|
||||
const std::size_t num_frames{static_cast<size_t>(num_frames_)};
|
||||
|
||||
if (impl->type == StreamType::In) {
|
||||
std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff),
|
||||
num_frames * frame_size};
|
||||
impl->ProcessAudioIn(input_buffer, num_frames);
|
||||
} else {
|
||||
std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size};
|
||||
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
|
||||
}
|
||||
|
||||
return num_frames_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cubeb callback for if a device state changes. Unused currently.
|
||||
*
|
||||
* @param stream - Cubeb-specific data about the stream.
|
||||
* @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
|
||||
* @param state - New state of the device.
|
||||
*/
|
||||
static void StateCallback(cubeb_stream*, void*, cubeb_state) {}
|
||||
|
||||
/// Main Cubeb context
|
||||
cubeb* ctx{};
|
||||
/// Cubeb stream backend
|
||||
cubeb_stream* stream_backend{};
|
||||
};
|
||||
|
||||
CubebSink::CubebSink(std::string_view target_device_name) {
|
||||
// Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
|
||||
#ifdef _WIN32
|
||||
com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
|
||||
if (cubeb_init(&ctx, "Eden", nullptr) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (target_device_name != auto_device_name && !target_device_name.empty()) {
|
||||
cubeb_device_collection collection;
|
||||
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
|
||||
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
|
||||
} else {
|
||||
const auto collection_end{collection.device + collection.count};
|
||||
const auto device{
|
||||
std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
|
||||
return info.friendly_name != nullptr &&
|
||||
target_device_name == std::string(info.friendly_name);
|
||||
})};
|
||||
if (device != collection_end) {
|
||||
output_device = device->devid;
|
||||
}
|
||||
cubeb_device_collection_destroy(ctx, &collection);
|
||||
}
|
||||
}
|
||||
|
||||
cubeb_get_max_channel_count(ctx, &device_channels);
|
||||
device_channels = device_channels >= 6U ? 6U : 2U;
|
||||
}
|
||||
|
||||
CubebSink::~CubebSink() {
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& sink_stream : sink_streams) {
|
||||
sink_stream.reset();
|
||||
}
|
||||
|
||||
cubeb_destroy(ctx);
|
||||
|
||||
#ifdef _WIN32
|
||||
if (SUCCEEDED(com_init_result)) {
|
||||
CoUninitialize();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels_,
|
||||
const std::string& name, StreamType type) {
|
||||
system_channels = system_channels_;
|
||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
|
||||
ctx, device_channels, system_channels, output_device, input_device, name, type, system));
|
||||
|
||||
return stream.get();
|
||||
}
|
||||
|
||||
void CubebSink::CloseStream(SinkStream* stream) {
|
||||
for (size_t i = 0; i < sink_streams.size(); i++) {
|
||||
if (sink_streams[i].get() == stream) {
|
||||
sink_streams[i].reset();
|
||||
sink_streams.erase(sink_streams.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CubebSink::CloseStreams() {
|
||||
sink_streams.clear();
|
||||
}
|
||||
|
||||
f32 CubebSink::GetDeviceVolume() const {
|
||||
if (sink_streams.empty()) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return sink_streams[0]->GetDeviceVolume();
|
||||
}
|
||||
|
||||
void CubebSink::SetDeviceVolume(f32 volume) {
|
||||
for (auto& stream : sink_streams) {
|
||||
stream->SetDeviceVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
void CubebSink::SetSystemVolume(f32 volume) {
|
||||
for (auto& stream : sink_streams) {
|
||||
stream->SetSystemVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> ListCubebSinkDevices(bool capture) {
|
||||
std::vector<std::string> device_list;
|
||||
cubeb* ctx;
|
||||
|
||||
#ifdef _WIN32
|
||||
auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
|
||||
if (cubeb_init(&ctx, "Eden Device Enumerator", nullptr) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
|
||||
return {};
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (SUCCEEDED(com_init_result)) {
|
||||
CoUninitialize();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto type{capture ? CUBEB_DEVICE_TYPE_INPUT : CUBEB_DEVICE_TYPE_OUTPUT};
|
||||
cubeb_device_collection collection;
|
||||
if (cubeb_enumerate_devices(ctx, type, &collection) != CUBEB_OK) {
|
||||
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
|
||||
} else {
|
||||
for (std::size_t i = 0; i < collection.count; i++) {
|
||||
const cubeb_device_info& device = collection.device[i];
|
||||
if (device.friendly_name && device.friendly_name[0] != '\0' &&
|
||||
device.state == CUBEB_DEVICE_STATE_ENABLED) {
|
||||
device_list.emplace_back(device.friendly_name);
|
||||
}
|
||||
}
|
||||
cubeb_device_collection_destroy(ctx, &collection);
|
||||
}
|
||||
|
||||
cubeb_destroy(ctx);
|
||||
return device_list;
|
||||
}
|
||||
|
||||
/* REVERSION TO 3833 - function GetCubebLatency REINTRODUCED FROM 3833 - DIABLO 3 FIX */
|
||||
u32 GetCubebLatency() {
|
||||
cubeb* ctx;
|
||||
|
||||
#ifdef _WIN32
|
||||
auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
|
||||
// Init cubeb
|
||||
if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
|
||||
// Return a large latency so we choose SDL instead.
|
||||
return 10000u;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (SUCCEEDED(com_init_result)) {
|
||||
CoUninitialize();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get min latency
|
||||
cubeb_stream_params params{};
|
||||
params.rate = TargetSampleRate;
|
||||
params.channels = 2;
|
||||
params.format = CUBEB_SAMPLE_S16LE;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
|
||||
u32 latency{0};
|
||||
const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &latency);
|
||||
if (latency_error != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
|
||||
latency = TargetSampleCount * 2;
|
||||
}
|
||||
latency = (std::max)(latency, TargetSampleCount * 2);
|
||||
cubeb_destroy(ctx);
|
||||
return latency;
|
||||
}
|
||||
|
||||
// REVERTED back to 3833 - Below namespace section and function IsCubebSuitable() removed, reverting to GetCubebLatency() above. - DIABLO 3 FIX
|
||||
/*
|
||||
namespace {
|
||||
static long TmpDataCallback(cubeb_stream*, void*, const void*, void*, long) {
|
||||
return TargetSampleCount;
|
||||
}
|
||||
static void TmpStateCallback(cubeb_stream*, void*, cubeb_state) {}
|
||||
} // namespace
|
||||
|
||||
bool IsCubebSuitable() {
|
||||
#if !defined(HAVE_CUBEB)
|
||||
return false;
|
||||
#else
|
||||
cubeb* ctx{nullptr};
|
||||
|
||||
#ifdef _WIN32
|
||||
auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
|
||||
// Init cubeb
|
||||
if (cubeb_init(&ctx, "Eden Latency Getter", nullptr) != CUBEB_OK) {
|
||||
LOG_ERROR(Audio_Sink, "Cubeb failed to init, it is not suitable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
SCOPE_EXIT {
|
||||
cubeb_destroy(ctx);
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
if (SUCCEEDED(com_init_result)) {
|
||||
CoUninitialize();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Get min latency
|
||||
cubeb_stream_params params{};
|
||||
params.rate = TargetSampleRate;
|
||||
params.channels = 2;
|
||||
params.format = CUBEB_SAMPLE_S16LE;
|
||||
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
|
||||
u32 latency{0};
|
||||
const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &latency);
|
||||
if (latency_error != CUBEB_OK) {
|
||||
LOG_ERROR(Audio_Sink, "Cubeb could not get min latency, it is not suitable.");
|
||||
return false;
|
||||
}
|
||||
latency = (std::max)(latency, TargetSampleCount * 2);
|
||||
|
||||
// Test opening a device with standard parameters
|
||||
cubeb_devid output_device{0};
|
||||
cubeb_devid input_device{0};
|
||||
std::string name{"Eden test"};
|
||||
cubeb_stream* stream{nullptr};
|
||||
|
||||
if (cubeb_stream_init(ctx, &stream, name.c_str(), input_device, nullptr, output_device, ¶ms,
|
||||
latency, &TmpDataCallback, &TmpStateCallback, nullptr) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Cubeb could not open a device, it is not suitable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
cubeb_stream_stop(stream);
|
||||
cubeb_stream_destroy(stream);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
|
||||
} // namespace AudioCore::Sink
|
||||
@@ -1,118 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cubeb/cubeb.h>
|
||||
|
||||
#include "audio_core/sink/sink.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace AudioCore::Sink {
|
||||
class SinkStream;
|
||||
|
||||
/**
|
||||
* Cubeb backend sink, holds multiple output streams and is responsible for sinking samples to
|
||||
* hardware. Used by Audio Render, Audio In and Audio Out.
|
||||
*/
|
||||
class CubebSink final : public Sink {
|
||||
public:
|
||||
explicit CubebSink(std::string_view device_id);
|
||||
~CubebSink() override;
|
||||
|
||||
/**
|
||||
* Create a new sink stream.
|
||||
*
|
||||
* @param system - Core system.
|
||||
* @param system_channels - Number of channels the audio system expects.
|
||||
* May differ from the device's channel count.
|
||||
* @param name - Name of this stream.
|
||||
* @param type - Type of this stream, render/in/out.
|
||||
*
|
||||
* @return A pointer to the created SinkStream
|
||||
*/
|
||||
SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
|
||||
const std::string& name, StreamType type) override;
|
||||
|
||||
/**
|
||||
* Close a given stream.
|
||||
*
|
||||
* @param stream - The stream to close.
|
||||
*/
|
||||
void CloseStream(SinkStream* stream) override;
|
||||
|
||||
/**
|
||||
* Close all streams.
|
||||
*/
|
||||
void CloseStreams() override;
|
||||
|
||||
/**
|
||||
* Get the device volume. Set from calls to the IAudioDevice service.
|
||||
*
|
||||
* @return Volume of the device.
|
||||
*/
|
||||
f32 GetDeviceVolume() const override;
|
||||
|
||||
/**
|
||||
* Set the device volume. Set from calls to the IAudioDevice service.
|
||||
*
|
||||
* @param volume - New volume of the device.
|
||||
*/
|
||||
void SetDeviceVolume(f32 volume) override;
|
||||
|
||||
/**
|
||||
* Set the system volume. Comes from the audio system using this stream.
|
||||
*
|
||||
* @param volume - New volume of the system.
|
||||
*/
|
||||
void SetSystemVolume(f32 volume) override;
|
||||
|
||||
private:
|
||||
/// Backend Cubeb context
|
||||
cubeb* ctx{};
|
||||
/// Cubeb id of the actual hardware output device
|
||||
cubeb_devid output_device{};
|
||||
/// Cubeb id of the actual hardware input device
|
||||
cubeb_devid input_device{};
|
||||
/// Vector of streams managed by this sink
|
||||
std::vector<SinkStreamPtr> sink_streams{};
|
||||
|
||||
#ifdef _WIN32
|
||||
/// Cubeb required COM to be initialized multi-threaded on Windows
|
||||
u32 com_init_result = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list of connected devices from Cubeb.
|
||||
*
|
||||
* @param capture - Return input (capture) devices if true, otherwise output devices.
|
||||
*/
|
||||
std::vector<std::string> ListCubebSinkDevices(bool capture);
|
||||
|
||||
// REVERSION - function GetCubebLatency() reintroduced from EA-3833 - DIABLO 3 FIX
|
||||
/**
|
||||
* Get the reported latency for this sink.
|
||||
*
|
||||
* @return Minimum latency for this sink.
|
||||
*/
|
||||
u32 GetCubebLatency();
|
||||
|
||||
/**
|
||||
* Check if this backend is suitable for use.
|
||||
* Checks if enabled, its latency, whether it opens successfully, etc.
|
||||
*
|
||||
* @return True is this backend is suitable, false otherwise.
|
||||
*/
|
||||
// bool IsCubebSuitable(); // REVERTED BACK TO GetCubebLatency() FROM 3833
|
||||
|
||||
} // namespace AudioCore::Sink
|
||||
@@ -25,9 +25,10 @@ namespace {
|
||||
// See https://github.com/PCSX2/pcsx2/pull/12312
|
||||
// "SDL and cubeb backends previously resulted in different names for the output which
|
||||
// caused them be identified as different applications by the OS."
|
||||
//
|
||||
// Keep in sync with cubeb_sink.cpp name.
|
||||
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "yuzu Latency Getter");
|
||||
SDL_SetHint("SDL_HINT_AUDIO_DEVICE_STREAM_ROLE", "Game");
|
||||
// We do our own processing, so just have SDL copy our audio
|
||||
SDL_SetHint("SDL_HINT_AUDIO_DEVICE_RAW_STREAM", "1");
|
||||
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
|
||||
return false;
|
||||
@@ -82,23 +83,18 @@ public:
|
||||
|
||||
SDL_AudioSpec spec{};
|
||||
spec.freq = TargetSampleRate;
|
||||
spec.channels = static_cast<u8>(device_channels);
|
||||
spec.format = SDL_AUDIO_S16;
|
||||
spec.channels = u8(device_channels);
|
||||
spec.format = SDL_AUDIO_S16LE;
|
||||
|
||||
std::string device_name{output_device};
|
||||
bool capture{false};
|
||||
if (type == StreamType::In) {
|
||||
device_name = input_device;
|
||||
capture = true;
|
||||
}
|
||||
auto const is_capture = (type == StreamType::In);
|
||||
std::string device_name = is_capture ? input_device : output_device;
|
||||
const SDL_AudioDeviceID audio_device = device_name.empty()
|
||||
? (is_capture
|
||||
? SDL_AUDIO_DEVICE_DEFAULT_RECORDING
|
||||
: SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK)
|
||||
: FindAudioDeviceByName(device_name, is_capture);
|
||||
|
||||
const SDL_AudioDeviceID audio_device =
|
||||
device_name.empty() ? (capture ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING
|
||||
: SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK)
|
||||
: FindAudioDeviceByName(device_name, capture);
|
||||
|
||||
stream = SDL_OpenAudioDeviceStream(audio_device, &spec, &SDLSinkStream::DataCallback,
|
||||
this);
|
||||
stream = SDL_OpenAudioDeviceStream(audio_device, &spec, &SDLSinkStream::DataCallback, this);
|
||||
|
||||
if (stream == nullptr) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError());
|
||||
@@ -107,13 +103,12 @@ public:
|
||||
|
||||
SDL_AudioSpec stream_in{};
|
||||
SDL_AudioSpec stream_out{};
|
||||
static_cast<void>(SDL_GetAudioStreamFormat(stream, &stream_in, &stream_out));
|
||||
|
||||
void(SDL_GetAudioStreamFormat(stream, &stream_in, &stream_out));
|
||||
LOG_INFO(Service_Audio,
|
||||
"Opening SDL stream {} with: rate {} channels {} (system channels {}) "
|
||||
" format {}",
|
||||
static_cast<const void*>(stream), stream_out.freq, stream_out.channels,
|
||||
system_channels, static_cast<int>(stream_out.format));
|
||||
system_channels, int(stream_out.format));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,14 +123,12 @@ public:
|
||||
* Finalize the sink stream.
|
||||
*/
|
||||
void Finalize() override {
|
||||
if (stream == nullptr) {
|
||||
return;
|
||||
if (stream != nullptr) {
|
||||
Stop();
|
||||
SDL_ClearAudioStream(stream);
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
}
|
||||
|
||||
Stop();
|
||||
SDL_ClearAudioStream(stream);
|
||||
SDL_DestroyAudioStream(stream);
|
||||
stream = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,9 +141,8 @@ public:
|
||||
if (stream == nullptr || !paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
paused = false;
|
||||
static_cast<void>(SDL_ResumeAudioStreamDevice(stream));
|
||||
void(SDL_ResumeAudioStreamDevice(stream));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +153,7 @@ public:
|
||||
return;
|
||||
}
|
||||
SignalPause();
|
||||
static_cast<void>(SDL_PauseAudioStreamDevice(stream));
|
||||
void(SDL_PauseAudioStreamDevice(stream));
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -173,51 +165,34 @@ private:
|
||||
* @param stream - Buffer of samples to be filled or read.
|
||||
* @param len - Length of the stream in bytes.
|
||||
*/
|
||||
static void DataCallback(void* userdata, SDL_AudioStream* stream, int additional_amount,
|
||||
int total_amount) {
|
||||
auto* impl = static_cast<SDLSinkStream*>(userdata);
|
||||
|
||||
if (!impl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t num_channels = impl->GetDeviceChannels();
|
||||
const std::size_t frame_size = num_channels;
|
||||
|
||||
if (impl->type == StreamType::In) {
|
||||
const int bytes_available = SDL_GetAudioStreamAvailable(stream);
|
||||
if (bytes_available <= 0) {
|
||||
return;
|
||||
static void DataCallback(void* userdata, SDL_AudioStream* stream, int additional_amount, int total_amount) {
|
||||
if (auto* impl = static_cast<SDLSinkStream*>(userdata); impl) {
|
||||
auto const num_channels = impl->GetDeviceChannels();
|
||||
auto const frame_size = num_channels;
|
||||
if (impl->type == StreamType::In) {
|
||||
auto const bytes_available = SDL_GetAudioStreamAvailable(stream);
|
||||
if (bytes_available > 0) {
|
||||
std::vector<s16> input(bytes_available / int(sizeof(s16)));
|
||||
auto const bytes_read = SDL_GetAudioStreamData(stream, input.data(), bytes_available);
|
||||
if (bytes_read > 0) {
|
||||
auto const num_frames = std::size_t(bytes_read) / sizeof(s16) / frame_size;
|
||||
std::span<const s16> input_buffer{input.data(), std::size_t(bytes_read) / sizeof(s16)};
|
||||
impl->ProcessAudioIn(input_buffer, num_frames);
|
||||
}
|
||||
}
|
||||
} else if (additional_amount > 0 || total_amount > 0) {
|
||||
auto const bytes_requested = additional_amount > 0 ? additional_amount : total_amount;
|
||||
std::vector<s16> output(bytes_requested / int(sizeof(s16)));
|
||||
auto const num_frames = std::size_t(bytes_requested) / sizeof(s16) / frame_size;
|
||||
std::span<s16> output_buffer{output.data(), output.size()};
|
||||
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
|
||||
void(SDL_PutAudioStreamData(stream, output.data(), bytes_requested));
|
||||
}
|
||||
|
||||
std::vector<s16> input(bytes_available / static_cast<int>(sizeof(s16)));
|
||||
const int bytes_read = SDL_GetAudioStreamData(stream, input.data(), bytes_available);
|
||||
if (bytes_read <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t num_frames =
|
||||
static_cast<std::size_t>(bytes_read) / sizeof(s16) / frame_size;
|
||||
std::span<const s16> input_buffer{input.data(),
|
||||
static_cast<std::size_t>(bytes_read) / sizeof(s16)};
|
||||
impl->ProcessAudioIn(input_buffer, num_frames);
|
||||
} else {
|
||||
if (additional_amount <= 0 && total_amount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int bytes_requested = additional_amount > 0 ? additional_amount : total_amount;
|
||||
std::vector<s16> output(bytes_requested / static_cast<int>(sizeof(s16)));
|
||||
const std::size_t num_frames =
|
||||
static_cast<std::size_t>(bytes_requested) / sizeof(s16) / frame_size;
|
||||
std::span<s16> output_buffer{output.data(), output.size()};
|
||||
impl->ProcessAudioOutAndRender(output_buffer, num_frames);
|
||||
static_cast<void>(SDL_PutAudioStreamData(stream, output.data(), bytes_requested));
|
||||
}
|
||||
}
|
||||
|
||||
/// SDL stream attached to an opened input/output device
|
||||
SDL_AudioStream* stream{};
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
};
|
||||
|
||||
SDLSink::SDLSink(std::string_view target_device_name) {
|
||||
@@ -233,11 +208,9 @@ SDLSink::SDLSink(std::string_view target_device_name) {
|
||||
|
||||
SDLSink::~SDLSink() = default;
|
||||
|
||||
SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels_,
|
||||
const std::string&, StreamType type) {
|
||||
SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels_, const std::string&, StreamType type) {
|
||||
system_channels = system_channels_;
|
||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
|
||||
device_channels, system_channels, output_device, input_device, type, system));
|
||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(device_channels, system_channels, output_device, input_device, type, system));
|
||||
return stream.get();
|
||||
}
|
||||
|
||||
@@ -256,11 +229,7 @@ void SDLSink::CloseStreams() {
|
||||
}
|
||||
|
||||
f32 SDLSink::GetDeviceVolume() const {
|
||||
if (sink_streams.empty()) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return sink_streams[0]->GetDeviceVolume();
|
||||
return sink_streams.empty() ? 1.0f : sink_streams[0]->GetDeviceVolume();
|
||||
}
|
||||
|
||||
void SDLSink::SetDeviceVolume(f32 volume) {
|
||||
@@ -275,68 +244,21 @@ void SDLSink::SetSystemVolume(f32 volume) {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> ListSDLSinkDevices(bool capture) {
|
||||
if (!InitializeAudio())
|
||||
return {}; //no devices
|
||||
|
||||
std::vector<std::string> device_list;
|
||||
int device_count = 0;
|
||||
SDL_AudioDeviceID* devices =
|
||||
capture ? SDL_GetAudioRecordingDevices(&device_count)
|
||||
: SDL_GetAudioPlaybackDevices(&device_count);
|
||||
if (devices == nullptr) {
|
||||
return device_list;
|
||||
}
|
||||
|
||||
for (int i = 0; i < device_count; ++i) {
|
||||
if (const char* name = SDL_GetAudioDeviceName(devices[i])) {
|
||||
device_list.emplace_back(name);
|
||||
std::vector<std::string> ListSDLSinkDevices(bool is_capture) {
|
||||
std::vector<std::string> device_list{};
|
||||
if (InitializeAudio()) {
|
||||
int device_count = 0;
|
||||
SDL_AudioDeviceID* devices = is_capture ? SDL_GetAudioRecordingDevices(&device_count) : SDL_GetAudioPlaybackDevices(&device_count);
|
||||
if (devices != nullptr) {
|
||||
for (int i = 0; i < device_count; ++i) {
|
||||
if (const char* name = SDL_GetAudioDeviceName(devices[i]); name) {
|
||||
device_list.emplace_back(name);
|
||||
}
|
||||
}
|
||||
SDL_free(devices);
|
||||
}
|
||||
}
|
||||
SDL_free(devices);
|
||||
|
||||
return device_list;
|
||||
}
|
||||
|
||||
/* REVERSION to 3833 - function GetSDLLatency() REINTRODUCED FROM 3833 - DIABLO 3 FIX */
|
||||
u32 GetSDLLatency() {
|
||||
return TargetSampleCount * 2;
|
||||
}
|
||||
|
||||
// REVERTED back to 3833 - Below function IsSDLSuitable() removed, reverting to GetSDLLatency() above. - DIABLO 3 FIX
|
||||
/*
|
||||
bool IsSDLSuitable() {
|
||||
#if !defined(HAVE_SDL3)
|
||||
return false;
|
||||
#else
|
||||
// Check SDL can init
|
||||
if (!InitializeAudio()!
|
||||
return false;
|
||||
|
||||
// We can set any latency frequency we want with SDL, so no need to check that.
|
||||
|
||||
// Check we can open a device with standard parameters
|
||||
SDL_AudioSpec spec;
|
||||
spec.freq = TargetSampleRate;
|
||||
spec.channels = 2u;
|
||||
spec.format = AUDIO_S16SYS;
|
||||
spec.samples = TargetSampleCount * 2;
|
||||
spec.callback = nullptr;
|
||||
spec.userdata = nullptr;
|
||||
|
||||
SDL_AudioSpec obtained;
|
||||
auto device = SDL_OpenAudioDevice(nullptr, false, &spec, &obtained, false);
|
||||
|
||||
if (device == 0) {
|
||||
LOG_ERROR(Audio_Sink, "SDL failed to open a device, it is not suitable. Error: {}",
|
||||
SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_CloseAudioDevice(device);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
|
||||
} // namespace AudioCore::Sink
|
||||
|
||||
@@ -90,20 +90,4 @@ private:
|
||||
*/
|
||||
std::vector<std::string> ListSDLSinkDevices(bool capture);
|
||||
|
||||
// REVERSION - function GetSDLLatency() reintroduced from EA-3833 - DIABLO 3 FIX
|
||||
/**
|
||||
* Get the reported latency for this sink.
|
||||
*
|
||||
* @return Minimum latency for this sink.
|
||||
*/
|
||||
u32 GetSDLLatency();
|
||||
|
||||
/** REVERTED back to 3833 - Below function IsSDLSuitable() removed, reverting to GetSDLLatency() above. - DIABLO 3 FIX
|
||||
* Check if this backend is suitable for use.
|
||||
* Checks if enabled, its latency, whether it opens successfully, etc.
|
||||
*
|
||||
* @return True is this backend is suitable, false otherwise.
|
||||
*/
|
||||
//bool IsSDLSuitable(); // REVERTED for GetSDLLatency() from EA-3833
|
||||
|
||||
} // namespace AudioCore::Sink
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/sink/sink_details.h"
|
||||
#ifdef HAVE_CUBEB
|
||||
#include "audio_core/sink/cubeb_sink.h"
|
||||
#endif
|
||||
#ifdef HAVE_SDL3
|
||||
#include "audio_core/sink/sdl3_sink.h"
|
||||
#endif
|
||||
@@ -34,24 +31,10 @@ struct SinkDetails {
|
||||
FactoryFn factory;
|
||||
/// A method to call to list available devices.
|
||||
ListDevicesFn list_devices;
|
||||
/// Method to get the latency of this backend - REINTRODUCED FROM 3833 - DIABLO 3 FIX
|
||||
LatencyFn latency;
|
||||
/// Check whether this backend is suitable to be used.
|
||||
/// SuitableFn is_suitable; // REVERTED FOR LatencyFn latency ABOVE - DIABLO 3 FIX
|
||||
};
|
||||
|
||||
// sink_details is ordered in terms of desirability, with the best choice at the top.
|
||||
constexpr SinkDetails sink_details[] = {
|
||||
#ifdef HAVE_CUBEB
|
||||
SinkDetails{
|
||||
Settings::AudioEngine::Cubeb,
|
||||
[](std::string_view device_id) -> std::unique_ptr<Sink> {
|
||||
return std::make_unique<CubebSink>(device_id);
|
||||
},
|
||||
&ListCubebSinkDevices,
|
||||
&GetCubebLatency,
|
||||
},
|
||||
#endif
|
||||
static constexpr SinkDetails sink_details[] = {
|
||||
#ifdef HAVE_SDL3
|
||||
SinkDetails{
|
||||
Settings::AudioEngine::Sdl3,
|
||||
@@ -59,7 +42,6 @@ constexpr SinkDetails sink_details[] = {
|
||||
return std::make_unique<SDLSink>(device_id);
|
||||
},
|
||||
&ListSDLSinkDevices,
|
||||
&GetSDLLatency,
|
||||
},
|
||||
#endif
|
||||
SinkDetails{
|
||||
@@ -68,69 +50,32 @@ constexpr SinkDetails sink_details[] = {
|
||||
return std::make_unique<NullSink>(device_id);
|
||||
},
|
||||
[](bool capture) { return std::vector<std::string>{"null"}; },
|
||||
[]() { return 0u; },
|
||||
},
|
||||
};
|
||||
|
||||
const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) {
|
||||
const auto find_backend{[](Settings::AudioEngine id) {
|
||||
return std::find_if(std::begin(sink_details), std::end(sink_details),
|
||||
[&id](const auto& sink_detail) { return sink_detail.id == id; });
|
||||
return std::ranges::find_if(std::begin(sink_details), std::end(sink_details), [&id](const auto& e) {
|
||||
return e.id == id;
|
||||
});
|
||||
}};
|
||||
|
||||
auto iter = find_backend(sink_id);
|
||||
|
||||
if (sink_id == Settings::AudioEngine::Auto) {
|
||||
// REVERTED TO 3833 BELOW - DIABLO 3 FIX
|
||||
/*
|
||||
// Auto-select a backend. Use the sink details ordering, preferring cubeb first, checking
|
||||
// that the backend is available and suitable to use.
|
||||
for (auto& details : sink_details) {
|
||||
if (details.is_suitable()) {
|
||||
iter = &details;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/ // END REVERTED CODE - DIABLO 3 FIX
|
||||
|
||||
// BEGIN REINTRODUCED FROM 3833 - REPLACED CODE BLOCK ABOVE - DIABLO 3 FIX
|
||||
// Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which
|
||||
// causes audio issues, in that case go with SDL.
|
||||
#if defined(HAVE_CUBEB) && defined(HAVE_SDL3)
|
||||
iter = find_backend(Settings::AudioEngine::Cubeb);
|
||||
if (iter->latency() > TargetSampleCount * 3) {
|
||||
if (sink_id == Settings::AudioEngine::Cubeb || sink_id == Settings::AudioEngine::Auto) {
|
||||
iter = find_backend(Settings::AudioEngine::Sdl3);
|
||||
}
|
||||
#else
|
||||
iter = std::begin(sink_details);
|
||||
#endif
|
||||
// END REINTRODUCED SECTION FROM 3833 - DIABLO 3 FIX
|
||||
LOG_INFO(Service_Audio, "Auto-selecting the {} backend",
|
||||
Settings::CanonicalizeEnum(iter->id));
|
||||
/* BEGIN REMOVED - REVERTING BACK TO 3833, this didn't exist at all. - DIABLO 3 FIX
|
||||
} else {
|
||||
if (iter != std::end(sink_details) && !iter->is_suitable()) {
|
||||
LOG_ERROR(Service_Audio, "Selected backend {} is not suitable, falling back to null",
|
||||
Settings::CanonicalizeEnum(iter->id));
|
||||
iter = find_backend(Settings::AudioEngine::Null);
|
||||
} */ // END REMOVED REVERT - DIABLO 3 FIX
|
||||
LOG_INFO(Service_Audio, "Auto-selecting the {} backend", Settings::CanonicalizeEnum(iter->id));
|
||||
}
|
||||
|
||||
if (iter == std::end(sink_details)) {
|
||||
LOG_ERROR(Audio, "Invalid sink_id {}", Settings::CanonicalizeEnum(sink_id));
|
||||
iter = find_backend(Settings::AudioEngine::Null);
|
||||
}
|
||||
|
||||
return *iter;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
std::vector<Settings::AudioEngine> GetSinkIDs() {
|
||||
std::vector<Settings::AudioEngine> sink_ids(std::size(sink_details));
|
||||
|
||||
std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids),
|
||||
[](const auto& sink) { return sink.id; });
|
||||
|
||||
std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids), [](const auto& sink) { return sink.id; });
|
||||
return sink_ids;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#define LOSSLESS_DIR "lossless"
|
||||
#define NAND_DIR "nand"
|
||||
#define PLAY_TIME_DIR "play_time"
|
||||
#define POST_SHADER_DIR "post_shaders"
|
||||
#define SCREENSHOTS_DIR "screenshots"
|
||||
#define SDMC_DIR "sdmc"
|
||||
#define SHADER_DIR "shader"
|
||||
|
||||
@@ -160,7 +160,6 @@ public:
|
||||
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
|
||||
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
|
||||
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
|
||||
GenerateEdenPath(EdenPath::PostShaderDir, eden_path / POST_SHADER_DIR);
|
||||
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
|
||||
GenerateEdenPath(EdenPath::ScreenshotsDir, eden_path / SCREENSHOTS_DIR);
|
||||
GenerateEdenPath(EdenPath::SDMCDir, eden_path / SDMC_DIR);
|
||||
|
||||
@@ -26,7 +26,6 @@ enum class EdenPath {
|
||||
LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
|
||||
NANDDir, // Where the emulated NAND is stored.
|
||||
PlayTimeDir, // Where play time data is stored.
|
||||
PostShaderDir, // Where user post-processing shaders are stored.
|
||||
SaveDir, // Where save data is stored.
|
||||
ScreenshotsDir, // Where yuzu screenshots are stored.
|
||||
SDMCDir, // Where the emulated SDMC is stored.
|
||||
|
||||
@@ -388,14 +388,6 @@ struct Values {
|
||||
true,
|
||||
true};
|
||||
|
||||
SwitchableSetting<std::string> post_shader_chain{linkage,
|
||||
std::string(),
|
||||
"post_shader_chain",
|
||||
Category::Renderer,
|
||||
Specialization::Default,
|
||||
true,
|
||||
true};
|
||||
|
||||
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
|
||||
Specialization::Default, true, false};
|
||||
|
||||
|
||||
@@ -358,7 +358,7 @@ private:
|
||||
|
||||
ConnectionState(boost::asio::ip::tcp::socket&& client_socket_, async_pipe signal_pipe_, Kernel::KernelCore& kernel)
|
||||
: client_socket{std::move(client_socket_)}
|
||||
, signal_pipe{std::move(signal_pipe_)}
|
||||
, signal_pipe{signal_pipe_}
|
||||
, active_thread{kernel, nullptr}
|
||||
{}
|
||||
|
||||
|
||||
@@ -1021,9 +1021,6 @@ Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) {
|
||||
|
||||
// Suspend for debug, if we should.
|
||||
if (kernel.System().DebuggerEnabled()) {
|
||||
LOG_INFO(Debug_GDBStub,
|
||||
"GDB stub enabled; suspending guest process until a debugger continues execution on port {}",
|
||||
Settings::values.gdbstub_port.GetValue());
|
||||
main_thread->RequestSuspend(kernel, SuspendType::Debug);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,3 +31,6 @@ endif()
|
||||
if (SOLARIS)
|
||||
target_link_libraries(network PRIVATE socket nsl)
|
||||
endif()
|
||||
if (MINGW)
|
||||
target_link_libraries(network PRIVATE winmm)
|
||||
endif()
|
||||
|
||||
@@ -150,7 +150,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
||||
INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"),
|
||||
tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA "
|
||||
"can produce a more stable picture in lower resolutions."));
|
||||
INSERT(Settings, post_shader_chain, QString(), QString());
|
||||
INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"),
|
||||
tr("The method used to render the window in fullscreen.\nBorderless offers the best "
|
||||
"compatibility with the on-screen keyboard that some games request for "
|
||||
|
||||
@@ -299,21 +299,6 @@ if (ENABLE_LSFG)
|
||||
target_compile_definitions(video_core PUBLIC HAS_LSFG)
|
||||
endif()
|
||||
|
||||
if (ENABLE_RESHADE)
|
||||
target_sources(video_core PRIVATE
|
||||
post_processing/fx_chain.cpp
|
||||
post_processing/fx_chain.h
|
||||
post_processing/fx_compile.cpp
|
||||
post_processing/fx_compile.h
|
||||
post_processing/fx_effect.cpp
|
||||
post_processing/fx_effect.h
|
||||
renderer_vulkan/present/post_process.cpp
|
||||
renderer_vulkan/present/post_process.h
|
||||
)
|
||||
target_link_libraries(video_core PRIVATE reshadefx::reshadefx)
|
||||
target_compile_definitions(video_core PUBLIC HAS_RESHADE)
|
||||
endif()
|
||||
|
||||
if (ENABLE_OPENGL)
|
||||
target_sources(video_core PRIVATE
|
||||
renderer_opengl/present/filters.cpp
|
||||
|
||||
@@ -1,285 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "video_core/post_processing/fx_chain.h"
|
||||
#include "video_core/post_processing/fx_effect.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<std::string_view> Split(std::string_view value, char separator) {
|
||||
std::vector<std::string_view> out;
|
||||
size_t start = 0;
|
||||
while (start <= value.size()) {
|
||||
size_t end = value.find(separator, start);
|
||||
if (end == std::string_view::npos) {
|
||||
end = value.size();
|
||||
}
|
||||
out.push_back(value.substr(start, end - start));
|
||||
start = end + 1;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool IsSerializableName(std::string_view value) {
|
||||
return value.find_first_of(";|,=") == std::string_view::npos;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
std::vector<FxChainEntry> ParseFxChain(std::string_view value) {
|
||||
std::vector<FxChainEntry> parsed;
|
||||
|
||||
for (const std::string_view record : Split(value, ';')) {
|
||||
if (record.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto fields = Split(record, '|');
|
||||
if (fields.size() < 2 || fields[0].empty() || fields[1].empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FxChainEntry entry;
|
||||
entry.file = std::string(fields[0]);
|
||||
entry.technique = std::string(fields[1]);
|
||||
|
||||
if (fields.size() >= 3) {
|
||||
for (const std::string_view assignment : Split(fields[2], ',')) {
|
||||
const size_t equals = assignment.find('=');
|
||||
if (equals == std::string_view::npos) {
|
||||
continue;
|
||||
}
|
||||
const std::string name(assignment.substr(0, equals));
|
||||
if (name.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::array<f32, 4> components{};
|
||||
size_t index = 0;
|
||||
for (const std::string_view piece : Split(assignment.substr(equals + 1), '/')) {
|
||||
if (index >= components.size()) {
|
||||
break;
|
||||
}
|
||||
const std::string text(piece);
|
||||
if (!text.empty()) {
|
||||
components[index] = std::strtof(text.c_str(), nullptr);
|
||||
}
|
||||
++index;
|
||||
}
|
||||
entry.values.emplace(name, components);
|
||||
}
|
||||
}
|
||||
|
||||
parsed.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
std::string SerializeFxChain(std::span<const FxChainEntry> entries) {
|
||||
std::string out;
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
if (!IsSerializableName(entry.file) || !IsSerializableName(entry.technique)) {
|
||||
continue;
|
||||
}
|
||||
if (!out.empty()) {
|
||||
out += ';';
|
||||
}
|
||||
out += entry.file;
|
||||
out += '|';
|
||||
out += entry.technique;
|
||||
out += '|';
|
||||
|
||||
bool first = true;
|
||||
for (const auto& [name, value] : entry.values) {
|
||||
if (!IsSerializableName(name)) {
|
||||
continue;
|
||||
}
|
||||
if (!first) {
|
||||
out += ',';
|
||||
}
|
||||
first = false;
|
||||
out += name;
|
||||
out += '=';
|
||||
for (size_t i = 0; i < value.size(); ++i) {
|
||||
if (i > 0) {
|
||||
out += '/';
|
||||
}
|
||||
out += fmt::format("{}", value[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
FxChain& FxChain::Instance() {
|
||||
static FxChain instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
FxChainSnapshot FxChain::Snapshot() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return FxChainSnapshot{
|
||||
.entries = entries,
|
||||
.generation = generation.load(std::memory_order_relaxed),
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<FxChainEntry> FxChain::Entries() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return entries;
|
||||
}
|
||||
|
||||
size_t FxChain::Size() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
void FxChain::Append(std::string_view file, std::string_view technique) {
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
FxChainEntry entry;
|
||||
entry.file = std::string(file);
|
||||
entry.technique = std::string(technique);
|
||||
entries.push_back(std::move(entry));
|
||||
}
|
||||
generation.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void FxChain::Replace(size_t index, std::string_view file, std::string_view technique) {
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
if (index >= entries.size()) {
|
||||
return;
|
||||
}
|
||||
if (entries[index].file == file && entries[index].technique == technique) {
|
||||
return;
|
||||
}
|
||||
FxChainEntry entry;
|
||||
entry.file = std::string(file);
|
||||
entry.technique = std::string(technique);
|
||||
entries[index] = std::move(entry);
|
||||
}
|
||||
generation.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void FxChain::Remove(size_t index) {
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
if (index >= entries.size()) {
|
||||
return;
|
||||
}
|
||||
entries.erase(entries.begin() + static_cast<std::ptrdiff_t>(index));
|
||||
}
|
||||
generation.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void FxChain::Move(size_t index, int delta) {
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
if (index >= entries.size()) {
|
||||
return;
|
||||
}
|
||||
const std::ptrdiff_t target = static_cast<std::ptrdiff_t>(index) + delta;
|
||||
if (target < 0 || target >= static_cast<std::ptrdiff_t>(entries.size())) {
|
||||
return;
|
||||
}
|
||||
std::swap(entries[index], entries[static_cast<size_t>(target)]);
|
||||
}
|
||||
generation.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void FxChain::Clear() {
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
entries.clear();
|
||||
}
|
||||
generation.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void FxChain::SetValue(size_t index, std::string_view uniform, const std::array<f32, 4>& value) {
|
||||
std::scoped_lock lock{mutex};
|
||||
if (index >= entries.size()) {
|
||||
return;
|
||||
}
|
||||
entries[index].values[std::string(uniform)] = value;
|
||||
}
|
||||
|
||||
std::array<f32, 4> FxChain::GetValue(size_t index, std::string_view uniform) const {
|
||||
std::scoped_lock lock{mutex};
|
||||
if (index >= entries.size()) {
|
||||
return {};
|
||||
}
|
||||
const auto it = entries[index].values.find(std::string(uniform));
|
||||
if (it == entries[index].values.end()) {
|
||||
return {};
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool FxChain::HasValue(size_t index, std::string_view uniform) const {
|
||||
std::scoped_lock lock{mutex};
|
||||
if (index >= entries.size()) {
|
||||
return false;
|
||||
}
|
||||
return entries[index].values.contains(std::string(uniform));
|
||||
}
|
||||
|
||||
void FxChain::ResetValues(size_t index) {
|
||||
std::scoped_lock lock{mutex};
|
||||
if (index >= entries.size()) {
|
||||
return;
|
||||
}
|
||||
entries[index].values.clear();
|
||||
}
|
||||
|
||||
void FxChain::LoadFromSettings() {
|
||||
auto loaded = ParseFxChain(Settings::values.post_shader_chain.GetValue());
|
||||
|
||||
std::scoped_lock lock{mutex};
|
||||
entries = std::move(loaded);
|
||||
generation.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void FxChain::StoreToSettings() const {
|
||||
std::string serialized;
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
serialized = SerializeFxChain(entries);
|
||||
}
|
||||
Settings::values.post_shader_chain.SetValue(serialized);
|
||||
}
|
||||
|
||||
void FxChain::DropUnknownEntries() {
|
||||
bool changed = false;
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
const auto removed = std::remove_if(entries.begin(), entries.end(), [](const FxChainEntry& entry) {
|
||||
const FxEffectDesc* effect = FindFxEffect(entry.file);
|
||||
if (effect == nullptr || !effect->Valid()) {
|
||||
return true;
|
||||
}
|
||||
return std::find(effect->techniques.begin(), effect->techniques.end(),
|
||||
entry.technique) == effect->techniques.end();
|
||||
});
|
||||
if (removed != entries.end()) {
|
||||
entries.erase(removed, entries.end());
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
generation.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
@@ -1,76 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
struct FxChainEntry {
|
||||
std::string file;
|
||||
std::string technique;
|
||||
std::map<std::string, std::array<f32, 4>> values;
|
||||
};
|
||||
|
||||
struct FxChainSnapshot {
|
||||
std::vector<FxChainEntry> entries;
|
||||
u64 generation{};
|
||||
};
|
||||
|
||||
std::vector<FxChainEntry> ParseFxChain(std::string_view value);
|
||||
|
||||
std::string SerializeFxChain(std::span<const FxChainEntry> entries);
|
||||
|
||||
class FxChain {
|
||||
public:
|
||||
static FxChain& Instance();
|
||||
|
||||
FxChainSnapshot Snapshot() const;
|
||||
|
||||
std::vector<FxChainEntry> Entries() const;
|
||||
|
||||
size_t Size() const;
|
||||
|
||||
void Append(std::string_view file, std::string_view technique);
|
||||
|
||||
void Replace(size_t index, std::string_view file, std::string_view technique);
|
||||
|
||||
void Remove(size_t index);
|
||||
|
||||
void Move(size_t index, int delta);
|
||||
|
||||
void Clear();
|
||||
|
||||
void SetValue(size_t index, std::string_view uniform, const std::array<f32, 4>& value);
|
||||
|
||||
std::array<f32, 4> GetValue(size_t index, std::string_view uniform) const;
|
||||
|
||||
bool HasValue(size_t index, std::string_view uniform) const;
|
||||
|
||||
void ResetValues(size_t index);
|
||||
|
||||
void LoadFromSettings();
|
||||
|
||||
void StoreToSettings() const;
|
||||
|
||||
void DropUnknownEntries();
|
||||
|
||||
private:
|
||||
FxChain() = default;
|
||||
|
||||
mutable std::mutex mutex;
|
||||
std::vector<FxChainEntry> entries;
|
||||
std::atomic<u64> generation{1};
|
||||
};
|
||||
|
||||
} // namespace VideoCore
|
||||
@@ -1,105 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include "effect_codegen.hpp"
|
||||
#include "effect_parser.hpp"
|
||||
#include "effect_preprocessor.hpp"
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/fs_util.h"
|
||||
#include "video_core/post_processing/fx_compile.h"
|
||||
#include "video_core/post_processing/fx_effect.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
FxCompileResult CompileFxEffect(const std::filesystem::path& path, u32 width, u32 height,
|
||||
u32 color_bit_depth) {
|
||||
FxCompileResult result;
|
||||
|
||||
if (!Common::FS::Exists(path)) {
|
||||
result.error = "Effect file not found: " + Common::FS::PathToUTF8String(path);
|
||||
return result;
|
||||
}
|
||||
|
||||
reshadefx::preprocessor preprocessor;
|
||||
preprocessor.add_macro_definition("__RESHADE__", "50000");
|
||||
preprocessor.add_macro_definition("__RESHADE_PERFORMANCE_MODE__", "1");
|
||||
preprocessor.add_macro_definition("__RENDERER__", "0x20000");
|
||||
preprocessor.add_macro_definition("__VENDOR__", "0");
|
||||
preprocessor.add_macro_definition("__DEVICE__", "0");
|
||||
preprocessor.add_macro_definition("__APPLICATION__", "0");
|
||||
preprocessor.add_macro_definition("BUFFER_WIDTH", std::to_string(width));
|
||||
preprocessor.add_macro_definition("BUFFER_HEIGHT", std::to_string(height));
|
||||
preprocessor.add_macro_definition("BUFFER_RCP_WIDTH", "(1.0 / BUFFER_WIDTH)");
|
||||
preprocessor.add_macro_definition("BUFFER_RCP_HEIGHT", "(1.0 / BUFFER_HEIGHT)");
|
||||
preprocessor.add_macro_definition("BUFFER_COLOR_DEPTH", std::to_string(color_bit_depth));
|
||||
preprocessor.add_macro_definition("BUFFER_COLOR_BIT_DEPTH", std::to_string(color_bit_depth));
|
||||
|
||||
for (const auto& include : GetFxIncludePaths(path)) {
|
||||
preprocessor.add_include_path(include);
|
||||
}
|
||||
|
||||
if (!preprocessor.append_file(path)) {
|
||||
result.error = preprocessor.errors();
|
||||
if (result.error.empty()) {
|
||||
result.error = "Failed to preprocess " + Common::FS::PathToUTF8String(path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<reshadefx::codegen> backend(
|
||||
reshadefx::create_codegen_spirv(true, false, false, false, false));
|
||||
|
||||
reshadefx::parser parser;
|
||||
if (!parser.parse(preprocessor.output(), backend.get())) {
|
||||
result.error = parser.errors();
|
||||
if (result.error.empty()) {
|
||||
result.error = "Failed to parse " + Common::FS::PathToUTF8String(path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
result.module = backend->module();
|
||||
|
||||
std::set<std::string> wanted;
|
||||
for (const auto& technique : result.module.techniques) {
|
||||
for (const auto& pass : technique.passes) {
|
||||
if (!pass.vs_entry_point.empty()) {
|
||||
wanted.insert(pass.vs_entry_point);
|
||||
}
|
||||
if (!pass.ps_entry_point.empty()) {
|
||||
wanted.insert(pass.ps_entry_point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& name : wanted) {
|
||||
std::string binary;
|
||||
std::string assembly;
|
||||
std::string errors;
|
||||
if (!backend->assemble_code_for_entry_point(name, binary, assembly, errors)) {
|
||||
result.error = "Failed to assemble entry point '" + name + "': " + errors;
|
||||
return result;
|
||||
}
|
||||
if (binary.size() % sizeof(u32) != 0) {
|
||||
result.error = "Entry point '" + name + "' produced a malformed SPIR-V module";
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<u32> words(binary.size() / sizeof(u32));
|
||||
std::memcpy(words.data(), binary.data(), binary.size());
|
||||
result.entry_points.emplace(name, std::move(words));
|
||||
}
|
||||
|
||||
if (result.entry_points.empty()) {
|
||||
result.error = "Effect declares no usable entry points";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
@@ -1,29 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "effect_module.hpp"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
struct FxCompileResult {
|
||||
reshadefx::effect_module module;
|
||||
std::map<std::string, std::vector<u32>> entry_points;
|
||||
std::string error;
|
||||
|
||||
bool Succeeded() const {
|
||||
return error.empty() && !entry_points.empty();
|
||||
}
|
||||
};
|
||||
|
||||
FxCompileResult CompileFxEffect(const std::filesystem::path& path, u32 width, u32 height,
|
||||
u32 color_bit_depth);
|
||||
|
||||
} // namespace VideoCore
|
||||
@@ -1,283 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/fs_util.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "video_core/post_processing/fx_compile.h"
|
||||
#include "video_core/post_processing/fx_effect.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u32 CATALOG_PROBE_WIDTH = 1280;
|
||||
constexpr u32 CATALOG_PROBE_HEIGHT = 720;
|
||||
constexpr u32 CATALOG_PROBE_DEPTH = 8;
|
||||
|
||||
std::vector<FxEffectDesc> catalog;
|
||||
|
||||
const reshadefx::annotation* FindAnnotation(const std::vector<reshadefx::annotation>& annotations,
|
||||
std::string_view name) {
|
||||
const auto it = std::find_if(annotations.begin(), annotations.end(),
|
||||
[&](const reshadefx::annotation& a) { return a.name == name; });
|
||||
if (it == annotations.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &*it;
|
||||
}
|
||||
|
||||
std::string AnnotationString(const std::vector<reshadefx::annotation>& annotations,
|
||||
std::string_view name) {
|
||||
const reshadefx::annotation* a = FindAnnotation(annotations, name);
|
||||
if (a == nullptr) {
|
||||
return std::string();
|
||||
}
|
||||
return a->value.string_data;
|
||||
}
|
||||
|
||||
bool AnnotationFloat(const std::vector<reshadefx::annotation>& annotations, std::string_view name,
|
||||
f32& out) {
|
||||
const reshadefx::annotation* a = FindAnnotation(annotations, name);
|
||||
if (a == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (a->type.is_floating_point()) {
|
||||
out = a->value.as_float[0];
|
||||
return true;
|
||||
}
|
||||
if (a->type.is_integral()) {
|
||||
out = static_cast<f32>(a->value.as_int[0]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FxUiType ParseUiType(std::string_view value) {
|
||||
if (value == "slider") {
|
||||
return FxUiType::Slider;
|
||||
}
|
||||
if (value == "drag") {
|
||||
return FxUiType::Drag;
|
||||
}
|
||||
if (value == "combo") {
|
||||
return FxUiType::Combo;
|
||||
}
|
||||
if (value == "radio") {
|
||||
return FxUiType::Radio;
|
||||
}
|
||||
if (value == "check" || value == "checkbox") {
|
||||
return FxUiType::CheckBox;
|
||||
}
|
||||
if (value == "color") {
|
||||
return FxUiType::Color;
|
||||
}
|
||||
if (value == "input") {
|
||||
return FxUiType::InputBox;
|
||||
}
|
||||
return FxUiType::Hidden;
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitItems(const std::string& items) {
|
||||
std::vector<std::string> out;
|
||||
std::string current;
|
||||
for (const char c : items) {
|
||||
if (c == '\0') {
|
||||
out.push_back(current);
|
||||
current.clear();
|
||||
continue;
|
||||
}
|
||||
current += c;
|
||||
}
|
||||
if (!current.empty()) {
|
||||
out.push_back(current);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
FxUniformDesc DescribeUniform(const reshadefx::uniform& info) {
|
||||
FxUniformDesc desc;
|
||||
desc.name = info.name;
|
||||
desc.components = std::min<u32>(info.type.components(), 4);
|
||||
|
||||
if (info.type.is_boolean()) {
|
||||
desc.kind = FxUniformKind::Boolean;
|
||||
} else if (info.type.is_integral()) {
|
||||
desc.kind = FxUniformKind::Integer;
|
||||
} else {
|
||||
desc.kind = FxUniformKind::Floating;
|
||||
}
|
||||
|
||||
desc.label = AnnotationString(info.annotations, "ui_label");
|
||||
if (desc.label.empty()) {
|
||||
desc.label = info.name;
|
||||
}
|
||||
desc.tooltip = AnnotationString(info.annotations, "ui_tooltip");
|
||||
desc.category = AnnotationString(info.annotations, "ui_category");
|
||||
desc.ui_type = ParseUiType(AnnotationString(info.annotations, "ui_type"));
|
||||
desc.items = SplitItems(AnnotationString(info.annotations, "ui_items"));
|
||||
|
||||
if (desc.kind == FxUniformKind::Boolean) {
|
||||
desc.ui_min = 0.0f;
|
||||
desc.ui_max = 1.0f;
|
||||
desc.ui_step = 1.0f;
|
||||
} else if (desc.kind == FxUniformKind::Integer) {
|
||||
desc.ui_min = 0.0f;
|
||||
desc.ui_max = 100.0f;
|
||||
desc.ui_step = 1.0f;
|
||||
}
|
||||
|
||||
void(AnnotationFloat(info.annotations, "ui_min", desc.ui_min));
|
||||
void(AnnotationFloat(info.annotations, "ui_max", desc.ui_max));
|
||||
void(AnnotationFloat(info.annotations, "ui_step", desc.ui_step));
|
||||
|
||||
if (desc.ui_step <= 0.0f) {
|
||||
desc.ui_step = 0.01f;
|
||||
if (desc.kind != FxUniformKind::Floating) {
|
||||
desc.ui_step = 1.0f;
|
||||
}
|
||||
}
|
||||
if (desc.ui_max < desc.ui_min) {
|
||||
std::swap(desc.ui_min, desc.ui_max);
|
||||
}
|
||||
|
||||
if (info.has_initializer_value) {
|
||||
for (u32 i = 0; i < desc.components; ++i) {
|
||||
if (desc.kind == FxUniformKind::Floating) {
|
||||
desc.default_value[i] = info.initializer_value.as_float[i];
|
||||
} else {
|
||||
desc.default_value[i] = static_cast<f32>(info.initializer_value.as_int[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
FxEffectDesc DescribeEffect(const std::filesystem::path& path, const std::filesystem::path& root) {
|
||||
FxEffectDesc desc;
|
||||
desc.file = Common::FS::PathToUTF8String(std::filesystem::relative(path, root));
|
||||
desc.name = Common::FS::PathToUTF8String(path.stem());
|
||||
|
||||
const auto compiled =
|
||||
CompileFxEffect(path, CATALOG_PROBE_WIDTH, CATALOG_PROBE_HEIGHT, CATALOG_PROBE_DEPTH);
|
||||
if (!compiled.Succeeded()) {
|
||||
desc.error = compiled.error;
|
||||
return desc;
|
||||
}
|
||||
|
||||
for (const auto& technique : compiled.module.techniques) {
|
||||
desc.techniques.push_back(technique.name);
|
||||
}
|
||||
|
||||
for (const auto& uniform : compiled.module.uniforms) {
|
||||
FxUniformDesc uniform_desc = DescribeUniform(uniform);
|
||||
if (uniform_desc.ui_type == FxUiType::Hidden) {
|
||||
continue;
|
||||
}
|
||||
desc.uniforms.push_back(std::move(uniform_desc));
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
std::filesystem::path GetFxRootDirectory() {
|
||||
return Common::FS::GetEdenPath(Common::FS::EdenPath::PostShaderDir);
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> GetFxIncludePaths(const std::filesystem::path& effect_path) {
|
||||
const auto root = GetFxRootDirectory();
|
||||
std::vector<std::filesystem::path> paths;
|
||||
paths.push_back(effect_path.parent_path());
|
||||
paths.push_back(root);
|
||||
paths.push_back(root / "Shaders");
|
||||
|
||||
const auto last = std::unique(paths.begin(), paths.end());
|
||||
paths.erase(last, paths.end());
|
||||
return paths;
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveFxTexturePath(const std::filesystem::path& effect_path,
|
||||
std::string_view source) {
|
||||
const auto root = GetFxRootDirectory();
|
||||
const std::filesystem::path name{source};
|
||||
|
||||
const std::array candidates{
|
||||
effect_path.parent_path() / name,
|
||||
root / "Textures" / name,
|
||||
root / name,
|
||||
};
|
||||
|
||||
for (const auto& candidate : candidates) {
|
||||
if (Common::FS::Exists(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return std::filesystem::path();
|
||||
}
|
||||
|
||||
void ReloadFxCatalog() {
|
||||
catalog.clear();
|
||||
|
||||
const auto root = GetFxRootDirectory();
|
||||
if (!Common::FS::Exists(root)) {
|
||||
void(Common::FS::CreateDirs(root));
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> effect_files;
|
||||
Common::FS::IterateDirEntriesRecursively(
|
||||
root,
|
||||
[&](const std::filesystem::directory_entry& entry) {
|
||||
if (entry.path().extension() == ".fx") {
|
||||
effect_files.push_back(entry.path());
|
||||
}
|
||||
return true;
|
||||
},
|
||||
Common::FS::DirEntryFilter::File);
|
||||
|
||||
std::sort(effect_files.begin(), effect_files.end());
|
||||
|
||||
for (const auto& file : effect_files) {
|
||||
FxEffectDesc desc = DescribeEffect(file, root);
|
||||
if (!desc.error.empty()) {
|
||||
LOG_WARNING(Render, "Post-processing effect '{}' failed to compile:\n{}", desc.file,
|
||||
desc.error);
|
||||
}
|
||||
catalog.push_back(std::move(desc));
|
||||
}
|
||||
|
||||
const size_t usable = std::count_if(catalog.begin(), catalog.end(),
|
||||
[](const FxEffectDesc& d) { return d.Valid(); });
|
||||
LOG_INFO(Render, "Loaded {} post-processing effects ({} usable)", catalog.size(), usable);
|
||||
}
|
||||
|
||||
const std::vector<FxEffectDesc>& GetFxCatalog() {
|
||||
return catalog;
|
||||
}
|
||||
|
||||
const FxEffectDesc* FindFxEffect(std::string_view file) {
|
||||
const auto it = std::find_if(catalog.begin(), catalog.end(),
|
||||
[&](const FxEffectDesc& d) { return d.file == file; });
|
||||
if (it == catalog.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &*it;
|
||||
}
|
||||
|
||||
const FxUniformDesc* FindFxUniform(const FxEffectDesc& effect, std::string_view name) {
|
||||
const auto it = std::find_if(effect.uniforms.begin(), effect.uniforms.end(),
|
||||
[&](const FxUniformDesc& u) { return u.name == name; });
|
||||
if (it == effect.uniforms.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &*it;
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
@@ -1,75 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
enum class FxUniformKind {
|
||||
Boolean,
|
||||
Integer,
|
||||
Floating,
|
||||
};
|
||||
|
||||
enum class FxUiType {
|
||||
Hidden,
|
||||
Slider,
|
||||
Drag,
|
||||
Combo,
|
||||
Radio,
|
||||
CheckBox,
|
||||
Color,
|
||||
InputBox,
|
||||
};
|
||||
|
||||
struct FxUniformDesc {
|
||||
std::string name;
|
||||
std::string label;
|
||||
std::string tooltip;
|
||||
std::string category;
|
||||
FxUniformKind kind{FxUniformKind::Floating};
|
||||
u32 components{1};
|
||||
FxUiType ui_type{FxUiType::Hidden};
|
||||
f32 ui_min{0.0f};
|
||||
f32 ui_max{1.0f};
|
||||
f32 ui_step{0.01f};
|
||||
std::vector<std::string> items;
|
||||
std::array<f32, 4> default_value{};
|
||||
};
|
||||
|
||||
struct FxEffectDesc {
|
||||
std::string file;
|
||||
std::string name;
|
||||
std::vector<std::string> techniques;
|
||||
std::vector<FxUniformDesc> uniforms;
|
||||
std::string error;
|
||||
|
||||
bool Valid() const {
|
||||
return error.empty() && !techniques.empty();
|
||||
}
|
||||
};
|
||||
|
||||
std::filesystem::path GetFxRootDirectory();
|
||||
|
||||
std::vector<std::filesystem::path> GetFxIncludePaths(const std::filesystem::path& effect_path);
|
||||
|
||||
std::filesystem::path ResolveFxTexturePath(const std::filesystem::path& effect_path,
|
||||
std::string_view source);
|
||||
|
||||
void ReloadFxCatalog();
|
||||
|
||||
const std::vector<FxEffectDesc>& GetFxCatalog();
|
||||
|
||||
const FxEffectDesc* FindFxEffect(std::string_view file);
|
||||
|
||||
const FxUniformDesc* FindFxUniform(const FxEffectDesc& effect, std::string_view name);
|
||||
|
||||
} // namespace VideoCore
|
||||
@@ -18,10 +18,6 @@
|
||||
#include "video_core/renderer_vulkan/present/sgsr.h"
|
||||
#include "video_core/renderer_vulkan/present/fxaa.h"
|
||||
#include "video_core/renderer_vulkan/present/layer.h"
|
||||
#ifdef HAS_RESHADE
|
||||
#include "video_core/post_processing/fx_chain.h"
|
||||
#include "video_core/renderer_vulkan/present/post_process.h"
|
||||
#endif
|
||||
#include "video_core/renderer_vulkan/present/present_push_constants.h"
|
||||
#include "video_core/renderer_vulkan/present/smaa.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
@@ -97,9 +93,6 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
|
||||
|
||||
RefreshResources(device, framebuffer);
|
||||
SetAntiAliasPass(device);
|
||||
#ifdef HAS_RESHADE
|
||||
SetPostProcessPass(device);
|
||||
#endif
|
||||
|
||||
// Finish any pending renderpass
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
@@ -122,12 +115,6 @@ void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_c
|
||||
smaa->Draw(device, scheduler, image_index, &source_image, &source_image_view);
|
||||
}
|
||||
|
||||
#ifdef HAS_RESHADE
|
||||
if (post_process.has_value()) {
|
||||
post_process->Draw(device, scheduler, image_index, &source_image, &source_image_view);
|
||||
}
|
||||
#endif
|
||||
|
||||
auto crop_rect = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height);
|
||||
const VkExtent2D render_extent{
|
||||
.width = scaled_width,
|
||||
@@ -227,40 +214,6 @@ void Layer::SetAntiAliasPass(const Device& device) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAS_RESHADE
|
||||
void Layer::SetPostProcessPass(const Device& device) {
|
||||
const VkExtent2D render_area{
|
||||
.width = Settings::values.resolution_info.ScaleUp(raw_width),
|
||||
.height = Settings::values.resolution_info.ScaleUp(raw_height),
|
||||
};
|
||||
|
||||
const u64 generation = VideoCore::FxChain::Instance().Snapshot().generation;
|
||||
|
||||
if (post_process_generation == generation && post_process_extent.width == render_area.width &&
|
||||
post_process_extent.height == render_area.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const u64 tick : resource_ticks) {
|
||||
scheduler.Wait(tick);
|
||||
}
|
||||
|
||||
post_process_generation = generation;
|
||||
post_process_extent = render_area;
|
||||
post_process.reset();
|
||||
|
||||
if (VideoCore::FxChain::Instance().Size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
post_process.emplace(device, memory_allocator, scheduler, image_count, render_area);
|
||||
|
||||
if (post_process->Empty()) {
|
||||
post_process.reset();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Layer::ReleaseRawImages() {
|
||||
for (const u64 tick : resource_ticks) {
|
||||
scheduler.Wait(tick);
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
#include "video_core/renderer_vulkan/present/fsr.h"
|
||||
#include "video_core/renderer_vulkan/present/sgsr.h"
|
||||
#include "video_core/renderer_vulkan/present/fxaa.h"
|
||||
#ifdef HAS_RESHADE
|
||||
#include "video_core/renderer_vulkan/present/post_process.h"
|
||||
#endif
|
||||
#include "video_core/renderer_vulkan/present/smaa.h"
|
||||
|
||||
namespace Layout {
|
||||
@@ -69,9 +66,6 @@ private:
|
||||
|
||||
void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer);
|
||||
void SetAntiAliasPass(const Device& device);
|
||||
#ifdef HAS_RESHADE
|
||||
void SetPostProcessPass(const Device& device);
|
||||
#endif
|
||||
void ReleaseRawImages();
|
||||
|
||||
u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const;
|
||||
@@ -101,11 +95,6 @@ private:
|
||||
Settings::AntiAliasing anti_alias_setting{};
|
||||
std::variant<std::monostate, FXAA, SMAA> anti_alias{};
|
||||
std::variant<std::monostate, SGSR, FSR> sr_filter{};
|
||||
#ifdef HAS_RESHADE
|
||||
std::optional<PostProcessChain> post_process{};
|
||||
u64 post_process_generation{};
|
||||
VkExtent2D post_process_extent{};
|
||||
#endif
|
||||
std::vector<u64> resource_ticks{};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,898 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/fs_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "video_core/post_processing/fx_chain.h"
|
||||
#include "video_core/post_processing/fx_compile.h"
|
||||
#include "video_core/post_processing/fx_effect.h"
|
||||
#include "video_core/renderer_vulkan/present/post_process.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr VkFormat BACKBUFFER_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
constexpr size_t NO_TEXTURE = ~size_t{0};
|
||||
|
||||
VkFormat ToVkFormat(reshadefx::texture_format format) {
|
||||
switch (format) {
|
||||
case reshadefx::texture_format::r8:
|
||||
return VK_FORMAT_R8_UNORM;
|
||||
case reshadefx::texture_format::r16f:
|
||||
return VK_FORMAT_R16_SFLOAT;
|
||||
case reshadefx::texture_format::r32f:
|
||||
return VK_FORMAT_R32_SFLOAT;
|
||||
case reshadefx::texture_format::rg8:
|
||||
return VK_FORMAT_R8G8_UNORM;
|
||||
case reshadefx::texture_format::rg16:
|
||||
return VK_FORMAT_R16G16_UNORM;
|
||||
case reshadefx::texture_format::rg16f:
|
||||
return VK_FORMAT_R16G16_SFLOAT;
|
||||
case reshadefx::texture_format::rg32f:
|
||||
return VK_FORMAT_R32G32_SFLOAT;
|
||||
case reshadefx::texture_format::rgba8:
|
||||
return VK_FORMAT_R8G8B8A8_UNORM;
|
||||
case reshadefx::texture_format::rgba16:
|
||||
return VK_FORMAT_R16G16B16A16_UNORM;
|
||||
case reshadefx::texture_format::rgba16f:
|
||||
return VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
case reshadefx::texture_format::rgba32f:
|
||||
return VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
case reshadefx::texture_format::rgb10a2:
|
||||
return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
|
||||
default:
|
||||
return VK_FORMAT_R8G8B8A8_UNORM;
|
||||
}
|
||||
}
|
||||
|
||||
VkSamplerAddressMode ToAddressMode(reshadefx::texture_address_mode mode) {
|
||||
switch (mode) {
|
||||
case reshadefx::texture_address_mode::wrap:
|
||||
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||||
case reshadefx::texture_address_mode::mirror:
|
||||
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
|
||||
case reshadefx::texture_address_mode::border:
|
||||
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
||||
case reshadefx::texture_address_mode::clamp:
|
||||
default:
|
||||
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
}
|
||||
}
|
||||
|
||||
VkBlendFactor ToBlendFactor(reshadefx::blend_factor func) {
|
||||
switch (func) {
|
||||
case reshadefx::blend_factor::zero:
|
||||
return VK_BLEND_FACTOR_ZERO;
|
||||
case reshadefx::blend_factor::source_color:
|
||||
return VK_BLEND_FACTOR_SRC_COLOR;
|
||||
case reshadefx::blend_factor::source_alpha:
|
||||
return VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
case reshadefx::blend_factor::one_minus_source_color:
|
||||
return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
|
||||
case reshadefx::blend_factor::one_minus_source_alpha:
|
||||
return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
case reshadefx::blend_factor::dest_color:
|
||||
return VK_BLEND_FACTOR_DST_COLOR;
|
||||
case reshadefx::blend_factor::dest_alpha:
|
||||
return VK_BLEND_FACTOR_DST_ALPHA;
|
||||
case reshadefx::blend_factor::one_minus_dest_color:
|
||||
return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
|
||||
case reshadefx::blend_factor::one_minus_dest_alpha:
|
||||
return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
|
||||
case reshadefx::blend_factor::one:
|
||||
default:
|
||||
return VK_BLEND_FACTOR_ONE;
|
||||
}
|
||||
}
|
||||
|
||||
VkBlendOp ToBlendOp(reshadefx::blend_op op) {
|
||||
switch (op) {
|
||||
case reshadefx::blend_op::subtract:
|
||||
return VK_BLEND_OP_SUBTRACT;
|
||||
case reshadefx::blend_op::reverse_subtract:
|
||||
return VK_BLEND_OP_REVERSE_SUBTRACT;
|
||||
case reshadefx::blend_op::min:
|
||||
return VK_BLEND_OP_MIN;
|
||||
case reshadefx::blend_op::max:
|
||||
return VK_BLEND_OP_MAX;
|
||||
case reshadefx::blend_op::add:
|
||||
default:
|
||||
return VK_BLEND_OP_ADD;
|
||||
}
|
||||
}
|
||||
|
||||
VkPrimitiveTopology ToTopology(reshadefx::primitive_topology topology) {
|
||||
switch (topology) {
|
||||
case reshadefx::primitive_topology::point_list:
|
||||
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
case reshadefx::primitive_topology::line_list:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||
case reshadefx::primitive_topology::line_strip:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
case reshadefx::primitive_topology::triangle_strip:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
||||
case reshadefx::primitive_topology::triangle_list:
|
||||
default:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
}
|
||||
}
|
||||
|
||||
vk::RenderPass CreateFxRenderPass(const Device& device, VkFormat format, bool clear) {
|
||||
VkAttachmentLoadOp load_op = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
VkImageLayout initial_layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
if (clear) {
|
||||
load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
initial_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
}
|
||||
|
||||
const VkAttachmentDescription attachment{
|
||||
.flags = 0,
|
||||
.format = format,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.loadOp = load_op,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.initialLayout = initial_layout,
|
||||
.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
};
|
||||
|
||||
const VkAttachmentReference reference{
|
||||
.attachment = 0,
|
||||
.layout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
};
|
||||
|
||||
const VkSubpassDescription subpass{
|
||||
.flags = 0,
|
||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.inputAttachmentCount = 0,
|
||||
.pInputAttachments = nullptr,
|
||||
.colorAttachmentCount = 1,
|
||||
.pColorAttachments = &reference,
|
||||
.pResolveAttachments = nullptr,
|
||||
.pDepthStencilAttachment = nullptr,
|
||||
.preserveAttachmentCount = 0,
|
||||
.pPreserveAttachments = nullptr,
|
||||
};
|
||||
|
||||
return device.GetLogical().CreateRenderPass(VkRenderPassCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.attachmentCount = 1,
|
||||
.pAttachments = &attachment,
|
||||
.subpassCount = 1,
|
||||
.pSubpasses = &subpass,
|
||||
.dependencyCount = 0,
|
||||
.pDependencies = nullptr,
|
||||
});
|
||||
}
|
||||
|
||||
vk::Pipeline CreateFxPipeline(const Device& device, vk::RenderPass& renderpass,
|
||||
vk::PipelineLayout& layout, VkShaderModule vertex_shader,
|
||||
VkShaderModule fragment_shader,
|
||||
const reshadefx::pass& pass) {
|
||||
const std::array<VkPipelineShaderStageCreateInfo, 2> stages{{
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.stage = VK_SHADER_STAGE_VERTEX_BIT,
|
||||
.module = vertex_shader,
|
||||
.pName = pass.vs_entry_point.c_str(),
|
||||
.pSpecializationInfo = nullptr,
|
||||
},
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.module = fragment_shader,
|
||||
.pName = pass.ps_entry_point.c_str(),
|
||||
.pSpecializationInfo = nullptr,
|
||||
},
|
||||
}};
|
||||
|
||||
constexpr VkPipelineVertexInputStateCreateInfo vertex_input{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.vertexBindingDescriptionCount = 0,
|
||||
.pVertexBindingDescriptions = nullptr,
|
||||
.vertexAttributeDescriptionCount = 0,
|
||||
.pVertexAttributeDescriptions = nullptr,
|
||||
};
|
||||
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.topology = ToTopology(pass.topology),
|
||||
.primitiveRestartEnable = VK_FALSE,
|
||||
};
|
||||
|
||||
constexpr VkPipelineViewportStateCreateInfo viewport_state{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.viewportCount = 1,
|
||||
.pViewports = nullptr,
|
||||
.scissorCount = 1,
|
||||
.pScissors = nullptr,
|
||||
};
|
||||
|
||||
constexpr VkPipelineRasterizationStateCreateInfo rasterization{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.depthClampEnable = VK_FALSE,
|
||||
.rasterizerDiscardEnable = VK_FALSE,
|
||||
.polygonMode = VK_POLYGON_MODE_FILL,
|
||||
.cullMode = VK_CULL_MODE_NONE,
|
||||
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
||||
.depthBiasEnable = VK_FALSE,
|
||||
.depthBiasConstantFactor = 0.0f,
|
||||
.depthBiasClamp = 0.0f,
|
||||
.depthBiasSlopeFactor = 0.0f,
|
||||
.lineWidth = 1.0f,
|
||||
};
|
||||
|
||||
constexpr VkPipelineMultisampleStateCreateInfo multisampling{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.sampleShadingEnable = VK_FALSE,
|
||||
.minSampleShading = 0.0f,
|
||||
.pSampleMask = nullptr,
|
||||
.alphaToCoverageEnable = VK_FALSE,
|
||||
.alphaToOneEnable = VK_FALSE,
|
||||
};
|
||||
|
||||
VkBool32 blend_enable = VK_FALSE;
|
||||
if (pass.blend_enable[0]) {
|
||||
blend_enable = VK_TRUE;
|
||||
}
|
||||
|
||||
const VkPipelineColorBlendAttachmentState blending{
|
||||
.blendEnable = blend_enable,
|
||||
.srcColorBlendFactor = ToBlendFactor(pass.source_color_blend_factor[0]),
|
||||
.dstColorBlendFactor = ToBlendFactor(pass.dest_color_blend_factor[0]),
|
||||
.colorBlendOp = ToBlendOp(pass.color_blend_op[0]),
|
||||
.srcAlphaBlendFactor = ToBlendFactor(pass.source_alpha_blend_factor[0]),
|
||||
.dstAlphaBlendFactor = ToBlendFactor(pass.dest_alpha_blend_factor[0]),
|
||||
.alphaBlendOp = ToBlendOp(pass.alpha_blend_op[0]),
|
||||
.colorWriteMask = static_cast<VkColorComponentFlags>(pass.render_target_write_mask[0] & 0xF),
|
||||
};
|
||||
|
||||
const VkPipelineColorBlendStateCreateInfo color_blend{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.logicOpEnable = VK_FALSE,
|
||||
.logicOp = VK_LOGIC_OP_COPY,
|
||||
.attachmentCount = 1,
|
||||
.pAttachments = &blending,
|
||||
.blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
|
||||
};
|
||||
|
||||
constexpr std::array dynamic_states{
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
};
|
||||
|
||||
const VkPipelineDynamicStateCreateInfo dynamic_state{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.dynamicStateCount = static_cast<u32>(dynamic_states.size()),
|
||||
.pDynamicStates = dynamic_states.data(),
|
||||
};
|
||||
|
||||
return device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.stageCount = static_cast<u32>(stages.size()),
|
||||
.pStages = stages.data(),
|
||||
.pVertexInputState = &vertex_input,
|
||||
.pInputAssemblyState = &input_assembly,
|
||||
.pTessellationState = nullptr,
|
||||
.pViewportState = &viewport_state,
|
||||
.pRasterizationState = &rasterization,
|
||||
.pMultisampleState = &multisampling,
|
||||
.pDepthStencilState = nullptr,
|
||||
.pColorBlendState = &color_blend,
|
||||
.pDynamicState = &dynamic_state,
|
||||
.layout = *layout,
|
||||
.renderPass = *renderpass,
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineIndex = 0,
|
||||
});
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
PostProcessChain::PostProcessChain(const Device& device, MemoryAllocator& allocator,
|
||||
Scheduler& scheduler, size_t image_count, VkExtent2D extent)
|
||||
: m_extent(extent)
|
||||
, m_image_count(u32(image_count))
|
||||
{
|
||||
m_start = std::chrono::steady_clock::now();
|
||||
m_previous = m_start;
|
||||
|
||||
CreatePingPongImages(device, allocator);
|
||||
|
||||
m_fallback_sampler = CreateWrappedSampler(device);
|
||||
m_fallback_image = CreateWrappedImage(allocator, VkExtent2D{1, 1}, VK_FORMAT_R8G8B8A8_UNORM);
|
||||
m_fallback_view = CreateWrappedImageView(device, m_fallback_image, VK_FORMAT_R8G8B8A8_UNORM);
|
||||
|
||||
if (!BuildEffects(device, allocator, scheduler)) {
|
||||
m_effects.clear();
|
||||
}
|
||||
}
|
||||
|
||||
PostProcessChain::~PostProcessChain() = default;
|
||||
|
||||
bool PostProcessChain::Empty() const {
|
||||
return m_effects.empty();
|
||||
}
|
||||
|
||||
void PostProcessChain::CreatePingPongImages(const Device& device, MemoryAllocator& allocator) {
|
||||
m_frames.resize(m_image_count);
|
||||
for (auto& frame : m_frames) {
|
||||
for (size_t i = 0; i < frame.images.size(); ++i) {
|
||||
frame.images[i] = CreateWrappedImage(allocator, m_extent, BACKBUFFER_FORMAT);
|
||||
frame.views[i] = CreateWrappedImageView(device, frame.images[i], BACKBUFFER_FORMAT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PostProcessChain::BuildEffects(const Device& device, MemoryAllocator& allocator,
|
||||
Scheduler& scheduler) {
|
||||
const auto snapshot = VideoCore::FxChain::Instance().Snapshot();
|
||||
if (snapshot.entries.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto root = VideoCore::GetFxRootDirectory();
|
||||
|
||||
for (size_t entry_index = 0; entry_index < snapshot.entries.size(); ++entry_index) {
|
||||
const auto& entry = snapshot.entries[entry_index];
|
||||
const auto path = root / entry.file;
|
||||
|
||||
const auto compiled = VideoCore::CompileFxEffect(path, m_extent.width, m_extent.height, 8);
|
||||
if (!compiled.Succeeded()) {
|
||||
LOG_ERROR(Render_Vulkan, "Post-processing effect '{}' failed to compile:\n{}",
|
||||
entry.file, compiled.error);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& module = compiled.module;
|
||||
|
||||
const auto technique = std::find_if(
|
||||
module.techniques.begin(), module.techniques.end(),
|
||||
[&](const reshadefx::technique& t) { return t.name == entry.technique; });
|
||||
if (technique == module.techniques.end()) {
|
||||
LOG_ERROR(Render_Vulkan, "Effect '{}' has no technique '{}'", entry.file,
|
||||
entry.technique);
|
||||
continue;
|
||||
}
|
||||
|
||||
Effect effect;
|
||||
effect.entry_index = entry_index;
|
||||
effect.file = entry.file;
|
||||
effect.uniform_size = module.total_uniform_size;
|
||||
for (const auto& [name, words] : compiled.entry_points) {
|
||||
effect.shaders.emplace(name, CreateWrappedShaderModule(device, words));
|
||||
}
|
||||
|
||||
for (const auto& texture : module.textures) {
|
||||
Texture out;
|
||||
out.name = texture.unique_name;
|
||||
out.extent = VkExtent2D{texture.width, texture.height};
|
||||
out.format = ToVkFormat(texture.format);
|
||||
|
||||
if (texture.semantic == "COLOR") {
|
||||
out.is_backbuffer = true;
|
||||
effect.textures.push_back(std::move(out));
|
||||
continue;
|
||||
}
|
||||
if (texture.semantic == "DEPTH") {
|
||||
effect.textures.push_back(std::move(out));
|
||||
continue;
|
||||
}
|
||||
|
||||
out.image = CreateWrappedImage(allocator, out.extent, out.format);
|
||||
out.view = CreateWrappedImageView(device, out.image, out.format);
|
||||
effect.textures.push_back(std::move(out));
|
||||
}
|
||||
|
||||
for (const auto& sampler : module.samplers) {
|
||||
Sampler out;
|
||||
out.texture_index = NO_TEXTURE;
|
||||
for (size_t i = 0; i < effect.textures.size(); ++i) {
|
||||
if (effect.textures[i].name == sampler.texture_name) {
|
||||
out.texture_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
VkFilter mag_filter = VK_FILTER_LINEAR;
|
||||
VkFilter min_filter = VK_FILTER_LINEAR;
|
||||
VkSamplerMipmapMode mip_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||||
const u32 filter = static_cast<u32>(sampler.filter);
|
||||
if ((filter & 0x10) == 0) {
|
||||
min_filter = VK_FILTER_NEAREST;
|
||||
}
|
||||
if ((filter & 0x04) == 0) {
|
||||
mag_filter = VK_FILTER_NEAREST;
|
||||
}
|
||||
if ((filter & 0x01) == 0) {
|
||||
mip_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||||
}
|
||||
|
||||
out.sampler = device.GetLogical().CreateSampler(VkSamplerCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.magFilter = mag_filter,
|
||||
.minFilter = min_filter,
|
||||
.mipmapMode = mip_mode,
|
||||
.addressModeU = ToAddressMode(sampler.address_u),
|
||||
.addressModeV = ToAddressMode(sampler.address_v),
|
||||
.addressModeW = ToAddressMode(sampler.address_w),
|
||||
.mipLodBias = sampler.lod_bias,
|
||||
.anisotropyEnable = VK_FALSE,
|
||||
.maxAnisotropy = 1.0f,
|
||||
.compareEnable = VK_FALSE,
|
||||
.compareOp = VK_COMPARE_OP_NEVER,
|
||||
.minLod = sampler.min_lod,
|
||||
.maxLod = sampler.max_lod,
|
||||
.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
|
||||
.unnormalizedCoordinates = VK_FALSE,
|
||||
});
|
||||
|
||||
effect.samplers.push_back(std::move(out));
|
||||
}
|
||||
|
||||
for (const auto& uniform : module.uniforms) {
|
||||
UniformWrite write;
|
||||
write.name = uniform.name;
|
||||
write.offset = uniform.offset;
|
||||
write.components = std::min<u32>(uniform.type.components(), 4);
|
||||
write.kind = UniformKind::Floating;
|
||||
if (uniform.type.is_boolean()) {
|
||||
write.kind = UniformKind::Boolean;
|
||||
} else if (uniform.type.is_integral()) {
|
||||
write.kind = UniformKind::Integer;
|
||||
}
|
||||
|
||||
for (const auto& annotation : uniform.annotations) {
|
||||
if (annotation.name != "source") {
|
||||
continue;
|
||||
}
|
||||
const std::string& source = annotation.value.string_data;
|
||||
if (source == "frametime") {
|
||||
write.source = UniformSource::FrameTime;
|
||||
} else if (source == "framecount") {
|
||||
write.source = UniformSource::FrameCount;
|
||||
} else if (source == "timer") {
|
||||
write.source = UniformSource::Timer;
|
||||
} else if (source == "random") {
|
||||
write.source = UniformSource::Random;
|
||||
} else if (source == "pingpong") {
|
||||
write.source = UniformSource::PingPong;
|
||||
}
|
||||
}
|
||||
|
||||
if (uniform.has_initializer_value) {
|
||||
for (u32 i = 0; i < write.components; ++i) {
|
||||
if (write.kind == UniformKind::Floating) {
|
||||
write.fallback[i] = uniform.initializer_value.as_float[i];
|
||||
} else {
|
||||
write.fallback[i] = static_cast<f32>(uniform.initializer_value.as_int[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto override = entry.values.find(uniform.name);
|
||||
if (override != entry.values.end()) {
|
||||
write.fallback = override->second;
|
||||
}
|
||||
|
||||
write.args = {0.0f, 1.0f, 1.0f, 0.0f};
|
||||
for (const auto& annotation : uniform.annotations) {
|
||||
if (annotation.name == "min" && annotation.type.is_floating_point()) {
|
||||
write.args[0] = annotation.value.as_float[0];
|
||||
}
|
||||
if (annotation.name == "max" && annotation.type.is_floating_point()) {
|
||||
write.args[1] = annotation.value.as_float[0];
|
||||
}
|
||||
if (annotation.name == "step" && annotation.type.is_floating_point()) {
|
||||
write.args[2] = annotation.value.as_float[0];
|
||||
}
|
||||
}
|
||||
write.state = write.args[0];
|
||||
|
||||
effect.uniforms.push_back(std::move(write));
|
||||
}
|
||||
|
||||
effect.uniform_layout = CreateWrappedDescriptorSetLayout(
|
||||
device, std::array{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER},
|
||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
for (const auto& pass : technique->passes) {
|
||||
Pass out;
|
||||
out.num_vertices = pass.num_vertices;
|
||||
out.clear = pass.clear_render_targets != 0;
|
||||
out.target_texture = NO_TEXTURE;
|
||||
out.extent = m_extent;
|
||||
|
||||
const std::string& target = pass.render_target_names[0];
|
||||
if (target.empty()) {
|
||||
out.writes_backbuffer = true;
|
||||
} else {
|
||||
for (size_t i = 0; i < effect.textures.size(); ++i) {
|
||||
if (effect.textures[i].name == target) {
|
||||
out.target_texture = i;
|
||||
out.extent = effect.textures[i].extent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (out.target_texture == NO_TEXTURE) {
|
||||
LOG_WARNING(Render_Vulkan, "Effect '{}' pass targets unknown texture '{}'",
|
||||
entry.file, target);
|
||||
out.writes_backbuffer = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (pass.viewport_width != 0 && pass.viewport_height != 0) {
|
||||
out.extent = VkExtent2D{pass.viewport_width, pass.viewport_height};
|
||||
}
|
||||
|
||||
VkFormat target_format = BACKBUFFER_FORMAT;
|
||||
if (!out.writes_backbuffer) {
|
||||
target_format = effect.textures[out.target_texture].format;
|
||||
}
|
||||
|
||||
u32 binding_count = 0;
|
||||
for (const auto& binding : pass.sampler_bindings) {
|
||||
SamplerBinding entry_binding;
|
||||
entry_binding.binding = binding.entry_point_binding;
|
||||
entry_binding.sampler_index = binding.index;
|
||||
out.sampler_bindings.push_back(entry_binding);
|
||||
binding_count = std::max(binding_count, binding.entry_point_binding + 1);
|
||||
}
|
||||
|
||||
const std::vector<VkDescriptorType> sampler_types(
|
||||
std::max<size_t>(binding_count, 1), VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
out.sampler_layout = CreateWrappedDescriptorSetLayout(
|
||||
device, sampler_types, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
const std::array set_layouts{*effect.uniform_layout, *out.sampler_layout};
|
||||
out.pipeline_layout =
|
||||
device.GetLogical().CreatePipelineLayout(VkPipelineLayoutCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.setLayoutCount = static_cast<u32>(set_layouts.size()),
|
||||
.pSetLayouts = set_layouts.data(),
|
||||
.pushConstantRangeCount = 0,
|
||||
.pPushConstantRanges = nullptr,
|
||||
});
|
||||
|
||||
const auto vertex_shader = effect.shaders.find(pass.vs_entry_point);
|
||||
const auto fragment_shader = effect.shaders.find(pass.ps_entry_point);
|
||||
if (vertex_shader == effect.shaders.end() ||
|
||||
fragment_shader == effect.shaders.end()) {
|
||||
LOG_WARNING(Render_Vulkan, "Effect '{}' pass references a missing entry point",
|
||||
entry.file);
|
||||
continue;
|
||||
}
|
||||
|
||||
out.renderpass = CreateFxRenderPass(device, target_format, out.clear);
|
||||
out.pipeline = CreateFxPipeline(device, out.renderpass, out.pipeline_layout,
|
||||
*vertex_shader->second, *fragment_shader->second, pass);
|
||||
|
||||
if (out.writes_backbuffer) {
|
||||
out.backbuffer_slot = static_cast<u32>(effect.backbuffer_pass_count % 2);
|
||||
++effect.backbuffer_pass_count;
|
||||
for (u32 image = 0; image < m_image_count; ++image) {
|
||||
for (size_t slot = 0; slot < 2; ++slot) {
|
||||
out.framebuffers.push_back(CreateWrappedFramebuffer(
|
||||
device, out.renderpass, m_frames[image].views[slot], out.extent));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.framebuffers.push_back(
|
||||
CreateWrappedFramebuffer(device, out.renderpass,
|
||||
effect.textures[out.target_texture].view, out.extent));
|
||||
}
|
||||
|
||||
effect.passes.push_back(std::move(out));
|
||||
}
|
||||
|
||||
if (effect.passes.empty()) {
|
||||
LOG_WARNING(Render_Vulkan, "Effect '{}' technique '{}' has no passes", entry.file,
|
||||
entry.technique);
|
||||
continue;
|
||||
}
|
||||
|
||||
const u32 buffer_size = std::max<u32>(effect.uniform_size, 4);
|
||||
for (u32 i = 0; i < m_image_count; ++i) {
|
||||
effect.uniform_buffers.push_back(
|
||||
CreateWrappedBuffer(allocator, buffer_size, MemoryUsage::Upload));
|
||||
}
|
||||
|
||||
size_t sampler_descriptor_count = 0;
|
||||
size_t sampler_set_count = 0;
|
||||
for (const auto& pass : effect.passes) {
|
||||
sampler_descriptor_count +=
|
||||
m_image_count * std::max<size_t>(pass.sampler_bindings.size(), 1);
|
||||
sampler_set_count += m_image_count;
|
||||
}
|
||||
|
||||
effect.descriptor_pool = CreateWrappedDescriptorPool(
|
||||
device, m_image_count + sampler_descriptor_count, m_image_count + sampler_set_count,
|
||||
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER});
|
||||
|
||||
const std::vector<VkDescriptorSetLayout> uniform_layouts(m_image_count,
|
||||
*effect.uniform_layout);
|
||||
effect.uniform_sets = CreateWrappedDescriptorSets(effect.descriptor_pool, uniform_layouts);
|
||||
|
||||
for (auto& pass : effect.passes) {
|
||||
const std::vector<VkDescriptorSetLayout> layouts(m_image_count, *pass.sampler_layout);
|
||||
pass.sampler_sets = CreateWrappedDescriptorSets(effect.descriptor_pool, layouts);
|
||||
}
|
||||
|
||||
m_effects.push_back(std::move(effect));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PostProcessChain::PrepareImages(const Device& device, Scheduler& scheduler) {
|
||||
if (m_images_ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler.Record([this](vk::CommandBuffer cmdbuf) {
|
||||
ClearColorImage(cmdbuf, *m_fallback_image);
|
||||
for (auto& frame : m_frames) {
|
||||
for (auto& image : frame.images) {
|
||||
ClearColorImage(cmdbuf, *image);
|
||||
}
|
||||
}
|
||||
for (auto& effect : m_effects) {
|
||||
for (auto& texture : effect.textures) {
|
||||
if (texture.image) {
|
||||
ClearColorImage(cmdbuf, *texture.image);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
scheduler.Finish();
|
||||
|
||||
m_images_ready = true;
|
||||
}
|
||||
|
||||
void PostProcessChain::UpdateUniforms(Effect& effect, size_t image_index, f32 delta_seconds) {
|
||||
if (effect.uniform_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
static thread_local std::mt19937 rng{std::random_device{}()};
|
||||
|
||||
std::vector<u8> staging(effect.uniform_size, 0);
|
||||
const f32 elapsed =
|
||||
std::chrono::duration<f32>(std::chrono::steady_clock::now() - m_start).count();
|
||||
|
||||
for (auto& uniform : effect.uniforms) {
|
||||
std::array<f32, 4> value = uniform.fallback;
|
||||
|
||||
switch (uniform.source) {
|
||||
case UniformSource::FrameTime:
|
||||
value[0] = delta_seconds * 1000.0f;
|
||||
break;
|
||||
case UniformSource::FrameCount:
|
||||
value[0] = static_cast<f32>(m_frame_count);
|
||||
break;
|
||||
case UniformSource::Timer:
|
||||
value[0] = elapsed * 1000.0f;
|
||||
break;
|
||||
case UniformSource::Random: {
|
||||
const int low = static_cast<int>(uniform.args[0]);
|
||||
int high = static_cast<int>(uniform.args[1]);
|
||||
if (high <= low) {
|
||||
high = low + 1;
|
||||
}
|
||||
std::uniform_int_distribution<int> dist(low, high);
|
||||
value[0] = static_cast<f32>(dist(rng));
|
||||
break;
|
||||
}
|
||||
case UniformSource::PingPong: {
|
||||
const f32 min_value = uniform.args[0];
|
||||
f32 max_value = uniform.args[1];
|
||||
if (max_value <= min_value) {
|
||||
max_value = min_value + 1.0f;
|
||||
}
|
||||
f32 step = uniform.args[2];
|
||||
if (step == 0.0f) {
|
||||
step = 1.0f;
|
||||
}
|
||||
uniform.state += uniform.direction * step * delta_seconds;
|
||||
if (uniform.state >= max_value) {
|
||||
uniform.state = max_value;
|
||||
uniform.direction = -1.0f;
|
||||
}
|
||||
if (uniform.state <= min_value) {
|
||||
uniform.state = min_value;
|
||||
uniform.direction = 1.0f;
|
||||
}
|
||||
value[0] = uniform.state;
|
||||
value[1] = uniform.direction;
|
||||
break;
|
||||
}
|
||||
case UniformSource::Value:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < uniform.components; ++i) {
|
||||
const size_t offset = uniform.offset + i * sizeof(u32);
|
||||
if (offset + sizeof(u32) > staging.size()) {
|
||||
break;
|
||||
}
|
||||
if (uniform.kind == UniformKind::Floating) {
|
||||
const f32 element = value[i];
|
||||
std::memcpy(staging.data() + offset, &element, sizeof(f32));
|
||||
} else {
|
||||
const s32 element = static_cast<s32>(value[i]);
|
||||
std::memcpy(staging.data() + offset, &element, sizeof(s32));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::span<u8> mapped = effect.uniform_buffers[image_index].Mapped();
|
||||
if (mapped.size() >= staging.size()) {
|
||||
std::memcpy(mapped.data(), staging.data(), staging.size());
|
||||
effect.uniform_buffers[image_index].Flush();
|
||||
}
|
||||
}
|
||||
|
||||
void PostProcessChain::UpdateDescriptors(const Device& device, Effect& effect, Pass& pass,
|
||||
size_t image_index, VkImageView backbuffer_view) {
|
||||
std::vector<VkDescriptorImageInfo> image_infos;
|
||||
std::vector<VkWriteDescriptorSet> writes;
|
||||
image_infos.reserve(pass.sampler_bindings.size() + 1);
|
||||
|
||||
const VkDescriptorSet sampler_set = pass.sampler_sets[image_index];
|
||||
|
||||
for (const auto& binding : pass.sampler_bindings) {
|
||||
VkImageView view = *m_fallback_view;
|
||||
VkSampler handle = *m_fallback_sampler;
|
||||
|
||||
if (binding.sampler_index < effect.samplers.size()) {
|
||||
const Sampler& sampler = effect.samplers[binding.sampler_index];
|
||||
if (sampler.sampler) {
|
||||
handle = *sampler.sampler;
|
||||
}
|
||||
if (sampler.texture_index != NO_TEXTURE) {
|
||||
const Texture& texture = effect.textures[sampler.texture_index];
|
||||
if (texture.is_backbuffer) {
|
||||
view = backbuffer_view;
|
||||
} else if (texture.view) {
|
||||
view = *texture.view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writes.push_back(
|
||||
CreateWriteDescriptorSet(image_infos, handle, view, sampler_set, binding.binding));
|
||||
}
|
||||
|
||||
const VkDescriptorBufferInfo buffer_info{
|
||||
.buffer = *effect.uniform_buffers[image_index],
|
||||
.offset = 0,
|
||||
.range = VK_WHOLE_SIZE,
|
||||
};
|
||||
|
||||
writes.push_back(VkWriteDescriptorSet{
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = effect.uniform_sets[image_index],
|
||||
.dstBinding = 0,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
.pImageInfo = nullptr,
|
||||
.pBufferInfo = &buffer_info,
|
||||
.pTexelBufferView = nullptr,
|
||||
});
|
||||
|
||||
device.GetLogical().UpdateDescriptorSets(writes, {});
|
||||
}
|
||||
|
||||
void PostProcessChain::Draw(const Device& device, Scheduler& scheduler, size_t image_index,
|
||||
VkImage* inout_image, VkImageView* inout_image_view) {
|
||||
if (m_effects.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PrepareImages(device, scheduler);
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const f32 delta_seconds = std::chrono::duration<f32>(now - m_previous).count();
|
||||
m_previous = now;
|
||||
++m_frame_count;
|
||||
|
||||
FrameImages& frame = m_frames[image_index];
|
||||
VkImage current_image = *inout_image;
|
||||
VkImageView current_view = *inout_image_view;
|
||||
u32 slot = 0;
|
||||
|
||||
for (auto& effect : m_effects) {
|
||||
UpdateUniforms(effect, image_index, delta_seconds);
|
||||
|
||||
for (size_t pass_index = 0; pass_index < effect.passes.size(); ++pass_index) {
|
||||
Pass& pass = effect.passes[pass_index];
|
||||
|
||||
UpdateDescriptors(device, effect, pass, image_index, current_view);
|
||||
|
||||
VkFramebuffer framebuffer{};
|
||||
VkImage target_image{};
|
||||
if (pass.writes_backbuffer) {
|
||||
const u32 target_slot = (slot + 1) % 2;
|
||||
framebuffer = *pass.framebuffers[image_index * 2 + target_slot];
|
||||
target_image = *frame.images[target_slot];
|
||||
} else {
|
||||
framebuffer = *pass.framebuffers[0];
|
||||
target_image = *effect.textures[pass.target_texture].image;
|
||||
}
|
||||
|
||||
const VkImage source_image = current_image;
|
||||
const VkRenderPass renderpass = *pass.renderpass;
|
||||
const VkPipeline pipeline = *pass.pipeline;
|
||||
const VkPipelineLayout layout = *pass.pipeline_layout;
|
||||
const VkDescriptorSet uniform_set = effect.uniform_sets[image_index];
|
||||
const VkDescriptorSet sampler_set = pass.sampler_sets[image_index];
|
||||
const VkExtent2D extent = pass.extent;
|
||||
const u32 vertices = pass.num_vertices;
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([=](vk::CommandBuffer cmdbuf) {
|
||||
TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL);
|
||||
TransitionImageLayout(cmdbuf, target_image, VK_IMAGE_LAYOUT_GENERAL);
|
||||
BeginRenderPass(cmdbuf, renderpass, framebuffer, extent);
|
||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0,
|
||||
std::array{uniform_set, sampler_set}, {});
|
||||
cmdbuf.Draw(vertices, 1, 0, 0);
|
||||
cmdbuf.EndRenderPass();
|
||||
TransitionImageLayout(cmdbuf, target_image, VK_IMAGE_LAYOUT_GENERAL);
|
||||
});
|
||||
|
||||
if (pass.writes_backbuffer) {
|
||||
slot = (slot + 1) % 2;
|
||||
current_image = *frame.images[slot];
|
||||
current_view = *frame.views[slot];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*inout_image = current_image;
|
||||
*inout_image_view = current_view;
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -1,140 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
class Scheduler;
|
||||
|
||||
class PostProcessChain {
|
||||
public:
|
||||
explicit PostProcessChain(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler,
|
||||
size_t image_count, VkExtent2D extent);
|
||||
~PostProcessChain();
|
||||
|
||||
void Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage* inout_image,
|
||||
VkImageView* inout_image_view);
|
||||
|
||||
bool Empty() const;
|
||||
|
||||
private:
|
||||
enum class UniformKind : u32 {
|
||||
Boolean,
|
||||
Integer,
|
||||
Floating,
|
||||
};
|
||||
|
||||
enum class UniformSource : u32 {
|
||||
Value,
|
||||
FrameTime,
|
||||
FrameCount,
|
||||
Timer,
|
||||
Random,
|
||||
PingPong,
|
||||
};
|
||||
|
||||
struct UniformWrite {
|
||||
std::string name;
|
||||
u32 offset{};
|
||||
u32 components{};
|
||||
UniformKind kind{UniformKind::Floating};
|
||||
UniformSource source{UniformSource::Value};
|
||||
std::array<f32, 4> fallback{};
|
||||
std::array<f32, 4> args{};
|
||||
f32 state{};
|
||||
f32 direction{1.0f};
|
||||
};
|
||||
|
||||
struct Texture {
|
||||
std::string name;
|
||||
vk::Image image{};
|
||||
vk::ImageView view{};
|
||||
VkExtent2D extent{};
|
||||
VkFormat format{};
|
||||
bool is_backbuffer{};
|
||||
};
|
||||
|
||||
struct Sampler {
|
||||
vk::Sampler sampler{};
|
||||
size_t texture_index{};
|
||||
};
|
||||
|
||||
struct SamplerBinding {
|
||||
u32 binding{};
|
||||
size_t sampler_index{};
|
||||
};
|
||||
|
||||
struct Pass {
|
||||
vk::RenderPass renderpass{};
|
||||
vk::Pipeline pipeline{};
|
||||
vk::DescriptorSetLayout sampler_layout{};
|
||||
vk::PipelineLayout pipeline_layout{};
|
||||
vk::DescriptorSets sampler_sets{};
|
||||
std::vector<SamplerBinding> sampler_bindings{};
|
||||
std::vector<vk::Framebuffer> framebuffers{};
|
||||
size_t target_texture{};
|
||||
VkExtent2D extent{};
|
||||
u32 num_vertices{3};
|
||||
bool clear{};
|
||||
bool writes_backbuffer{};
|
||||
u32 backbuffer_slot{};
|
||||
};
|
||||
|
||||
struct Effect {
|
||||
size_t entry_index{};
|
||||
std::string file{};
|
||||
std::map<std::string, vk::ShaderModule> shaders{};
|
||||
std::vector<Texture> textures{};
|
||||
std::vector<Sampler> samplers{};
|
||||
std::vector<Pass> passes{};
|
||||
std::vector<UniformWrite> uniforms{};
|
||||
u32 uniform_size{};
|
||||
std::vector<vk::Buffer> uniform_buffers{};
|
||||
vk::DescriptorSetLayout uniform_layout{};
|
||||
vk::DescriptorPool descriptor_pool{};
|
||||
vk::DescriptorSets uniform_sets{};
|
||||
size_t backbuffer_pass_count{};
|
||||
u32 backbuffer_slots{1};
|
||||
};
|
||||
|
||||
struct FrameImages {
|
||||
std::array<vk::Image, 2> images{};
|
||||
std::array<vk::ImageView, 2> views{};
|
||||
};
|
||||
|
||||
bool BuildEffects(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler);
|
||||
void CreatePingPongImages(const Device& device, MemoryAllocator& allocator);
|
||||
void PrepareImages(const Device& device, Scheduler& scheduler);
|
||||
void UpdateUniforms(Effect& effect, size_t image_index, f32 delta_seconds);
|
||||
void UpdateDescriptors(const Device& device, Effect& effect, Pass& pass, size_t image_index,
|
||||
VkImageView backbuffer_view);
|
||||
|
||||
const VkExtent2D m_extent;
|
||||
const u32 m_image_count;
|
||||
|
||||
std::vector<Effect> m_effects{};
|
||||
std::vector<FrameImages> m_frames{};
|
||||
|
||||
vk::Sampler m_fallback_sampler{};
|
||||
vk::Image m_fallback_image{};
|
||||
vk::ImageView m_fallback_view{};
|
||||
|
||||
std::chrono::steady_clock::time_point m_start{};
|
||||
std::chrono::steady_clock::time_point m_previous{};
|
||||
u64 m_frame_count{};
|
||||
bool m_images_ready{};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -24,10 +24,6 @@
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/present.h"
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
#ifdef HAS_RESHADE
|
||||
#include "video_core/post_processing/fx_chain.h"
|
||||
#include "video_core/post_processing/fx_effect.h"
|
||||
#endif
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
#include "video_core/renderer_vulkan/vk_blit_screen.h"
|
||||
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||
@@ -187,12 +183,6 @@ try
|
||||
scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
|
||||
}
|
||||
|
||||
#ifdef HAS_RESHADE
|
||||
VideoCore::ReloadFxCatalog();
|
||||
VideoCore::FxChain::Instance().LoadFromSettings();
|
||||
VideoCore::FxChain::Instance().DropUnknownEntries();
|
||||
#endif
|
||||
|
||||
Report();
|
||||
} catch (const vk::Exception& exception) {
|
||||
LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what());
|
||||
|
||||
@@ -250,12 +250,6 @@ if (YUZU_CRASH_DUMPS)
|
||||
target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS)
|
||||
endif()
|
||||
|
||||
if (ENABLE_RESHADE)
|
||||
target_sources(yuzu PRIVATE
|
||||
configuration/configure_post_processing.cpp
|
||||
configuration/configure_post_processing.h)
|
||||
endif()
|
||||
|
||||
if (CXX_CLANG)
|
||||
target_compile_definitions(yuzu PRIVATE
|
||||
$<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,15>:CANNOT_EXPLICITLY_INSTANTIATE>)
|
||||
|
||||
@@ -1,378 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDesktopServices>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSlider>
|
||||
#include <QToolButton>
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/fs_util.h"
|
||||
#include "video_core/post_processing/fx_chain.h"
|
||||
#include "video_core/post_processing/fx_effect.h"
|
||||
#include "yuzu/configuration/configure_post_processing.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::array<float, 4> CurrentValue(int index, const VideoCore::FxUniformDesc& uniform) {
|
||||
auto& chain = VideoCore::FxChain::Instance();
|
||||
if (chain.HasValue(static_cast<size_t>(index), uniform.name)) {
|
||||
return chain.GetValue(static_cast<size_t>(index), uniform.name);
|
||||
}
|
||||
return uniform.default_value;
|
||||
}
|
||||
|
||||
int SliderSteps(const VideoCore::FxUniformDesc& uniform) {
|
||||
const float span = uniform.ui_max - uniform.ui_min;
|
||||
const int steps = static_cast<int>(std::lround(span / uniform.ui_step));
|
||||
if (steps < 1) {
|
||||
return 1;
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
QString FormatValue(const VideoCore::FxUniformDesc& uniform, float value) {
|
||||
if (uniform.kind == VideoCore::FxUniformKind::Floating) {
|
||||
return QString::number(value, 'f', 3);
|
||||
}
|
||||
return QString::number(static_cast<int>(std::lround(value)));
|
||||
}
|
||||
|
||||
QString SlotLabel(const VideoCore::FxEffectDesc& effect, const std::string& technique) {
|
||||
const QString name = QString::fromStdString(effect.name);
|
||||
if (effect.techniques.size() == 1) {
|
||||
return name;
|
||||
}
|
||||
return name + QStringLiteral(" · ") + QString::fromStdString(technique);
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(parent) {
|
||||
setWindowTitle(tr("Post-Processing Effects"));
|
||||
setMinimumWidth(560);
|
||||
setMinimumHeight(460);
|
||||
|
||||
auto* root = new QVBoxLayout(this);
|
||||
|
||||
auto* description = new QLabel(
|
||||
tr("ReShade FX effects are loaded from the post_shaders folder in the Eden data "
|
||||
"directory. Changes apply immediately while a game is running."),
|
||||
this);
|
||||
description->setWordWrap(true);
|
||||
root->addWidget(description);
|
||||
|
||||
auto* scroll = new QScrollArea(this);
|
||||
scroll->setWidgetResizable(true);
|
||||
slots_container = new QWidget(scroll);
|
||||
slots_layout = new QVBoxLayout(slots_container);
|
||||
slots_layout->setAlignment(Qt::AlignTop);
|
||||
scroll->setWidget(slots_container);
|
||||
root->addWidget(scroll, 1);
|
||||
|
||||
auto* actions = new QHBoxLayout();
|
||||
|
||||
auto* add_button = new QPushButton(tr("Add Effect"), this);
|
||||
connect(add_button, &QPushButton::clicked, this, [this]() {
|
||||
for (const auto& effect : VideoCore::GetFxCatalog()) {
|
||||
if (!effect.Valid()) {
|
||||
continue;
|
||||
}
|
||||
VideoCore::FxChain::Instance().Append(effect.file, effect.techniques.front());
|
||||
ApplyStructuralChange();
|
||||
return;
|
||||
}
|
||||
});
|
||||
actions->addWidget(add_button);
|
||||
|
||||
auto* reload_button = new QPushButton(tr("Reload From Disk"), this);
|
||||
connect(reload_button, &QPushButton::clicked, this, [this]() {
|
||||
VideoCore::ReloadFxCatalog();
|
||||
VideoCore::FxChain::Instance().DropUnknownEntries();
|
||||
ApplyStructuralChange();
|
||||
});
|
||||
actions->addWidget(reload_button);
|
||||
|
||||
auto* open_button = new QPushButton(tr("Open Folder"), this);
|
||||
connect(open_button, &QPushButton::clicked, this, []() {
|
||||
const auto path = VideoCore::GetFxRootDirectory();
|
||||
void(Common::FS::CreateDirs(path));
|
||||
QDesktopServices::openUrl(
|
||||
QUrl::fromLocalFile(QString::fromStdString(Common::FS::PathToUTF8String(path))));
|
||||
});
|
||||
actions->addWidget(open_button);
|
||||
|
||||
actions->addStretch();
|
||||
root->addLayout(actions);
|
||||
|
||||
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, this);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::close);
|
||||
root->addWidget(buttons);
|
||||
|
||||
RebuildRows();
|
||||
}
|
||||
|
||||
ConfigurePostProcessing::~ConfigurePostProcessing() = default;
|
||||
|
||||
void ConfigurePostProcessing::ApplyStructuralChange() {
|
||||
VideoCore::FxChain::Instance().StoreToSettings();
|
||||
RebuildRows();
|
||||
}
|
||||
|
||||
void ConfigurePostProcessing::PopulateEffectCombo(QComboBox* combo,
|
||||
const VideoCore::FxChainEntry& entry) const {
|
||||
combo->clear();
|
||||
int selected = -1;
|
||||
|
||||
for (const auto& effect : VideoCore::GetFxCatalog()) {
|
||||
if (!effect.Valid()) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& technique : effect.techniques) {
|
||||
const QString key = QString::fromStdString(effect.file + "|" + technique);
|
||||
combo->addItem(SlotLabel(effect, technique), key);
|
||||
if (effect.file == entry.file && technique == entry.technique) {
|
||||
selected = combo->count() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selected >= 0) {
|
||||
combo->setCurrentIndex(selected);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigurePostProcessing::BuildUniformWidget(QWidget* parent, QVBoxLayout* layout, int index,
|
||||
const VideoCore::FxUniformDesc& uniform) {
|
||||
const auto value = CurrentValue(index, uniform);
|
||||
const QString label = QString::fromStdString(uniform.label);
|
||||
|
||||
if (uniform.ui_type == VideoCore::FxUiType::CheckBox) {
|
||||
auto* box = new QCheckBox(label, parent);
|
||||
box->setChecked(value[0] != 0.0f);
|
||||
if (!uniform.tooltip.empty()) {
|
||||
box->setToolTip(QString::fromStdString(uniform.tooltip));
|
||||
}
|
||||
const std::string name = uniform.name;
|
||||
connect(box, &QCheckBox::toggled, this, [index, name](bool checked) {
|
||||
std::array<float, 4> next{};
|
||||
if (checked) {
|
||||
next[0] = 1.0f;
|
||||
}
|
||||
VideoCore::FxChain::Instance().SetValue(static_cast<size_t>(index), name, next);
|
||||
VideoCore::FxChain::Instance().StoreToSettings();
|
||||
});
|
||||
layout->addWidget(box);
|
||||
return;
|
||||
}
|
||||
|
||||
if (uniform.ui_type == VideoCore::FxUiType::Combo ||
|
||||
uniform.ui_type == VideoCore::FxUiType::Radio) {
|
||||
auto* row = new QHBoxLayout();
|
||||
row->addWidget(new QLabel(label, parent));
|
||||
auto* combo = new QComboBox(parent);
|
||||
for (size_t i = 0; i < uniform.items.size(); ++i) {
|
||||
combo->addItem(QString::fromStdString(uniform.items[i]), static_cast<int>(i));
|
||||
}
|
||||
if (combo->count() == 0) {
|
||||
combo->addItem(tr("Enabled"), 1);
|
||||
combo->addItem(tr("Disabled"), 0);
|
||||
}
|
||||
const int current = static_cast<int>(std::lround(value[0]));
|
||||
if (current >= 0 && current < combo->count()) {
|
||||
combo->setCurrentIndex(current);
|
||||
}
|
||||
if (!uniform.tooltip.empty()) {
|
||||
combo->setToolTip(QString::fromStdString(uniform.tooltip));
|
||||
}
|
||||
const std::string name = uniform.name;
|
||||
connect(combo, &QComboBox::currentIndexChanged, this, [index, name](int selected) {
|
||||
std::array<float, 4> next{};
|
||||
next[0] = static_cast<float>(selected);
|
||||
VideoCore::FxChain::Instance().SetValue(static_cast<size_t>(index), name, next);
|
||||
VideoCore::FxChain::Instance().StoreToSettings();
|
||||
});
|
||||
row->addWidget(combo, 1);
|
||||
layout->addLayout(row);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* grid = new QGridLayout();
|
||||
for (unsigned component = 0; component < uniform.components; ++component) {
|
||||
QString component_label = label;
|
||||
if (uniform.components > 1) {
|
||||
component_label = label + QStringLiteral(" [%1]").arg(component);
|
||||
}
|
||||
|
||||
auto* name_label = new QLabel(component_label, parent);
|
||||
auto* value_label = new QLabel(parent);
|
||||
value_label->setMinimumWidth(64);
|
||||
value_label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
value_label->setText(FormatValue(uniform, value[component]));
|
||||
|
||||
auto* slider = new QSlider(Qt::Horizontal, parent);
|
||||
slider->setMinimum(0);
|
||||
slider->setMaximum(SliderSteps(uniform));
|
||||
slider->setValue(
|
||||
static_cast<int>(std::lround((value[component] - uniform.ui_min) / uniform.ui_step)));
|
||||
if (!uniform.tooltip.empty()) {
|
||||
slider->setToolTip(QString::fromStdString(uniform.tooltip));
|
||||
}
|
||||
|
||||
const std::string name = uniform.name;
|
||||
const auto desc = uniform;
|
||||
connect(slider, &QSlider::valueChanged, this,
|
||||
[index, name, desc, component, value_label](int steps) {
|
||||
auto next = CurrentValue(index, desc);
|
||||
next[component] = desc.ui_min + static_cast<float>(steps) * desc.ui_step;
|
||||
VideoCore::FxChain::Instance().SetValue(static_cast<size_t>(index), name, next);
|
||||
value_label->setText(FormatValue(desc, next[component]));
|
||||
});
|
||||
connect(slider, &QSlider::sliderReleased, this,
|
||||
[]() { VideoCore::FxChain::Instance().StoreToSettings(); });
|
||||
|
||||
grid->addWidget(name_label, static_cast<int>(component), 0);
|
||||
grid->addWidget(slider, static_cast<int>(component), 1);
|
||||
grid->addWidget(value_label, static_cast<int>(component), 2);
|
||||
}
|
||||
layout->addLayout(grid);
|
||||
}
|
||||
|
||||
QWidget* ConfigurePostProcessing::BuildSlot(int index, const VideoCore::FxChainEntry& entry) {
|
||||
auto* group = new QGroupBox(slots_container);
|
||||
auto* layout = new QVBoxLayout(group);
|
||||
|
||||
auto* header = new QHBoxLayout();
|
||||
|
||||
auto* combo = new QComboBox(group);
|
||||
PopulateEffectCombo(combo, entry);
|
||||
connect(combo, &QComboBox::currentIndexChanged, this, [this, index, combo](int) {
|
||||
const QString key = combo->currentData().toString();
|
||||
const qsizetype separator = key.indexOf(QLatin1Char('|'));
|
||||
if (separator < 0) {
|
||||
return;
|
||||
}
|
||||
VideoCore::FxChain::Instance().Replace(static_cast<size_t>(index),
|
||||
key.left(separator).toStdString(),
|
||||
key.mid(separator + 1).toStdString());
|
||||
ApplyStructuralChange();
|
||||
});
|
||||
header->addWidget(combo, 1);
|
||||
|
||||
auto* up_button = new QToolButton(group);
|
||||
up_button->setText(QStringLiteral("▲"));
|
||||
up_button->setEnabled(index > 0);
|
||||
connect(up_button, &QToolButton::clicked, this, [this, index]() {
|
||||
VideoCore::FxChain::Instance().Move(static_cast<size_t>(index), -1);
|
||||
ApplyStructuralChange();
|
||||
});
|
||||
header->addWidget(up_button);
|
||||
|
||||
auto* down_button = new QToolButton(group);
|
||||
down_button->setText(QStringLiteral("▼"));
|
||||
down_button->setEnabled(static_cast<size_t>(index) + 1 < VideoCore::FxChain::Instance().Size());
|
||||
connect(down_button, &QToolButton::clicked, this, [this, index]() {
|
||||
VideoCore::FxChain::Instance().Move(static_cast<size_t>(index), 1);
|
||||
ApplyStructuralChange();
|
||||
});
|
||||
header->addWidget(down_button);
|
||||
|
||||
auto* reset_button = new QToolButton(group);
|
||||
reset_button->setText(QStringLiteral("⟲"));
|
||||
reset_button->setToolTip(tr("Reset to defaults"));
|
||||
connect(reset_button, &QToolButton::clicked, this, [this, index]() {
|
||||
VideoCore::FxChain::Instance().ResetValues(static_cast<size_t>(index));
|
||||
ApplyStructuralChange();
|
||||
});
|
||||
header->addWidget(reset_button);
|
||||
|
||||
auto* remove_button = new QToolButton(group);
|
||||
remove_button->setText(QStringLiteral("✕"));
|
||||
connect(remove_button, &QToolButton::clicked, this, [this, index]() {
|
||||
VideoCore::FxChain::Instance().Remove(static_cast<size_t>(index));
|
||||
ApplyStructuralChange();
|
||||
});
|
||||
header->addWidget(remove_button);
|
||||
|
||||
layout->addLayout(header);
|
||||
|
||||
const VideoCore::FxEffectDesc* effect = VideoCore::FindFxEffect(entry.file);
|
||||
if (effect == nullptr) {
|
||||
auto* missing =
|
||||
new QLabel(tr("Effect '%1' was not found.").arg(QString::fromStdString(entry.file)),
|
||||
group);
|
||||
missing->setWordWrap(true);
|
||||
layout->addWidget(missing);
|
||||
return group;
|
||||
}
|
||||
|
||||
if (!effect->error.empty()) {
|
||||
auto* failed = new QLabel(
|
||||
tr("Effect failed to compile:\n%1").arg(QString::fromStdString(effect->error)), group);
|
||||
failed->setWordWrap(true);
|
||||
layout->addWidget(failed);
|
||||
return group;
|
||||
}
|
||||
|
||||
std::string current_category;
|
||||
for (const auto& uniform : effect->uniforms) {
|
||||
if (uniform.category != current_category) {
|
||||
current_category = uniform.category;
|
||||
if (!current_category.empty()) {
|
||||
auto* category = new QLabel(QString::fromStdString(current_category), group);
|
||||
category->setStyleSheet(QStringLiteral("font-weight: bold;"));
|
||||
layout->addWidget(category);
|
||||
}
|
||||
}
|
||||
BuildUniformWidget(group, layout, index, uniform);
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
void ConfigurePostProcessing::RebuildRows() {
|
||||
QLayoutItem* item = nullptr;
|
||||
while ((item = slots_layout->takeAt(0)) != nullptr) {
|
||||
if (item->widget() != nullptr) {
|
||||
item->widget()->deleteLater();
|
||||
}
|
||||
delete item;
|
||||
}
|
||||
|
||||
bool has_usable = false;
|
||||
for (const auto& effect : VideoCore::GetFxCatalog()) {
|
||||
if (effect.Valid()) {
|
||||
has_usable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_usable) {
|
||||
auto* empty = new QLabel(
|
||||
tr("No usable ReShade FX effects were found. Place .fx files in the post_shaders "
|
||||
"folder."),
|
||||
slots_container);
|
||||
empty->setWordWrap(true);
|
||||
slots_layout->addWidget(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto entries = VideoCore::FxChain::Instance().Entries();
|
||||
for (size_t i = 0; i < entries.size(); ++i) {
|
||||
slots_layout->addWidget(BuildSlot(static_cast<int>(i), entries[i]));
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QComboBox;
|
||||
class QVBoxLayout;
|
||||
class QWidget;
|
||||
|
||||
namespace VideoCore {
|
||||
struct FxChainEntry;
|
||||
struct FxEffectDesc;
|
||||
struct FxUniformDesc;
|
||||
}
|
||||
|
||||
class ConfigurePostProcessing : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfigurePostProcessing(QWidget* parent = nullptr);
|
||||
~ConfigurePostProcessing() override;
|
||||
|
||||
private:
|
||||
void RebuildRows();
|
||||
QWidget* BuildSlot(int index, const VideoCore::FxChainEntry& entry);
|
||||
void BuildUniformWidget(QWidget* parent, QVBoxLayout* layout, int index,
|
||||
const VideoCore::FxUniformDesc& uniform);
|
||||
void PopulateEffectCombo(QComboBox* combo, const VideoCore::FxChainEntry& entry) const;
|
||||
void ApplyStructuralChange();
|
||||
|
||||
QVBoxLayout* slots_layout{};
|
||||
QWidget* slots_container{};
|
||||
};
|
||||
@@ -148,7 +148,6 @@
|
||||
<addaction name="action_Show_Filter_Bar"/>
|
||||
<addaction name="action_Show_Status_Bar"/>
|
||||
<addaction name="action_Show_Performance_Overlay"/>
|
||||
<addaction name="action_Post_Processing_Shaders"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menu_Reset_Window_Size"/>
|
||||
<addaction name="menu_View_Debugging"/>
|
||||
@@ -611,11 +610,6 @@
|
||||
<string>Show &Performance Overlay</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Post_Processing_Shaders">
|
||||
<property name="text">
|
||||
<string>Post-Processing &Shaders...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Carousel_View">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
#include "common/settings_enums.h"
|
||||
#include "frontend_common/settings_generator.h"
|
||||
#include "render/performance_overlay.h"
|
||||
#ifdef HAS_RESHADE
|
||||
#include "configuration/configure_post_processing.h"
|
||||
#endif
|
||||
#include "updater/update_dialog.h"
|
||||
|
||||
#include "common/fs/ryujinx_compat.h"
|
||||
@@ -1521,11 +1518,6 @@ void MainWindow::ConnectMenuEvents() {
|
||||
connect_menu(ui->action_Show_Filter_Bar, &MainWindow::OnToggleFilterBar);
|
||||
connect_menu(ui->action_Show_Status_Bar, &MainWindow::OnToggleStatusBar);
|
||||
connect_menu(ui->action_Show_Performance_Overlay, &MainWindow::OnTogglePerfOverlay);
|
||||
#ifdef HAS_RESHADE
|
||||
connect_menu(ui->action_Post_Processing_Shaders, &MainWindow::OnPostProcessingShaders);
|
||||
#else
|
||||
ui->action_Post_Processing_Shaders->setVisible(false);
|
||||
#endif
|
||||
|
||||
connect_menu(ui->action_Reset_Window_Size_720, &MainWindow::ResetWindowSize720);
|
||||
connect_menu(ui->action_Reset_Window_Size_900, &MainWindow::ResetWindowSize900);
|
||||
@@ -3908,22 +3900,6 @@ void MainWindow::OnTogglePerfOverlay() {
|
||||
perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked());
|
||||
}
|
||||
|
||||
#ifdef HAS_RESHADE
|
||||
void MainWindow::OnPostProcessingShaders() {
|
||||
if (post_processing_dialog == nullptr) {
|
||||
post_processing_dialog = new ConfigurePostProcessing(this);
|
||||
connect(post_processing_dialog, &QDialog::finished, post_processing_dialog, [this]() {
|
||||
post_processing_dialog->deleteLater();
|
||||
post_processing_dialog = nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
post_processing_dialog->show();
|
||||
post_processing_dialog->raise();
|
||||
post_processing_dialog->activateWindow();
|
||||
}
|
||||
#endif
|
||||
|
||||
void MainWindow::OnGameListRefresh() {
|
||||
// Resets metadata cache and reloads
|
||||
QtCommon::Game::ResetMetadata(false);
|
||||
|
||||
@@ -56,9 +56,6 @@ class QSlider;
|
||||
class QHBoxLayout;
|
||||
class WaitTreeWidget;
|
||||
class PerformanceOverlay;
|
||||
#ifdef HAS_RESHADE
|
||||
class ConfigurePostProcessing;
|
||||
#endif
|
||||
enum class GameListOpenTarget;
|
||||
enum class DumpRomFSTarget;
|
||||
class GameListPlaceholder;
|
||||
@@ -395,9 +392,6 @@ private slots:
|
||||
void OnToggleFilterBar();
|
||||
void OnToggleStatusBar();
|
||||
void OnTogglePerfOverlay();
|
||||
#ifdef HAS_RESHADE
|
||||
void OnPostProcessingShaders();
|
||||
#endif
|
||||
void OnGameListRefresh();
|
||||
void InitializeHotkeys();
|
||||
void ToggleFullscreen();
|
||||
@@ -502,9 +496,6 @@ private:
|
||||
QTimer shutdown_timer;
|
||||
OverlayDialog* shutdown_dialog{};
|
||||
PerformanceOverlay* perf_overlay = nullptr;
|
||||
#ifdef HAS_RESHADE
|
||||
ConfigurePostProcessing* post_processing_dialog = nullptr;
|
||||
#endif
|
||||
|
||||
GameListPlaceholder* game_list_placeholder = nullptr;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user