mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-10 14:07:25 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f744bcf9e1 |
@@ -1,11 +0,0 @@
|
|||||||
--- a/source/effect_lexer.cpp
|
|
||||||
+++ b/source/effect_lexer.cpp
|
|
||||||
@@ -1181,7 +1181,7 @@
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
-#if 0
|
|
||||||
+#if !defined(__cpp_lib_to_chars)
|
|
||||||
exponent += decimal_location - mantissa_size;
|
|
||||||
|
|
||||||
const bool exponent_negative = exponent < 0;
|
|
||||||
@@ -85,8 +85,6 @@ option(ENABLE_WERROR "Enable -Werror diagnostics" ON)
|
|||||||
# Lossless Scaling frame generation. Only Android.
|
# Lossless Scaling frame generation. Only Android.
|
||||||
cmake_dependent_option(ENABLE_LSFG "Enable Lossless Scaling frame generation" ON "ANDROID" OFF)
|
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
|
# non-linux bundled qt are static
|
||||||
if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX))
|
if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX))
|
||||||
set(YUZU_STATIC_BUILD ON)
|
set(YUZU_STATIC_BUILD ON)
|
||||||
|
|||||||
@@ -238,14 +238,6 @@
|
|||||||
"repo": "stachenov/quazip",
|
"repo": "stachenov/quazip",
|
||||||
"version": "2e95c9001b"
|
"version": "2e95c9001b"
|
||||||
},
|
},
|
||||||
"reshade": {
|
|
||||||
"hash": "6fc9b39821ca6e2d91160f05846b3a42159fec759f5d6fba91d1a57801a1bdaf6047740578c136f0b1a60a9c29ad160d87dbfe0dbfbcdf6ea879a8a078377b8c",
|
|
||||||
"patches": [
|
|
||||||
"0001-from-chars-fallback.patch"
|
|
||||||
],
|
|
||||||
"repo": "crosire/reshade",
|
|
||||||
"version": "v6.8.0"
|
|
||||||
},
|
|
||||||
"sdl3": {
|
"sdl3": {
|
||||||
"hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3",
|
"hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3",
|
||||||
"min_version": "3.2.10",
|
"min_version": "3.2.10",
|
||||||
|
|||||||
Vendored
-3
@@ -1,3 +0,0 @@
|
|||||||
name=Anime
|
|
||||||
description=Cel shaded look: the picture is cleaned first, then the lighting is collapsed into flat bands with inked edges, finished with a small glow.
|
|
||||||
chain=Denoise.fx|Denoise|Strength=0.18,Radius=1.2;CelShading.fx|CelShading|Bands=5,BandContrast=0.35,BandEdge=0.2,Detail=0.7,OutlineStrength=0.7,OutlineThreshold=0.2,Saturation=1.3;Bloom.fx|Bloom|Radius=1.2,Amount=0.25
|
|
||||||
Vendored
-3
@@ -1,3 +0,0 @@
|
|||||||
name=Cinematic
|
|
||||||
description=Film look: soft glow on the highlights, a filmic contrast curve, cool shadows against warm highlights, fine grain and a gentle vignette.
|
|
||||||
chain=Bloom.fx|Bloom|Radius=1.6,Amount=0.35;FilmicCurve.fx|FilmicCurve|Toe=1.35,Shoulder=1.3,Amount=0.85;SplitToning.fx|SplitToning|ShadowHue=210,ShadowStrength=0.25,HighlightHue=38,HighlightStrength=0.3;FilmGrain.fx|FilmGrain|Intensity=0.022;Vignette.fx|Vignette|Strength=0.5
|
|
||||||
Vendored
-3
@@ -1,3 +0,0 @@
|
|||||||
name=Clean
|
|
||||||
description=Cleans up a low internal resolution: removes dithering and compression noise, smooths banded skies and brings edge detail back. The safe starting point if you are not sure what you want.
|
|
||||||
chain=Denoise.fx|Denoise|Strength=0.14,Curve=1.2;Deband.fx|Deband|;Sharpen.fx|Sharpen|Amount=0.75
|
|
||||||
Vendored
-3
@@ -1,3 +0,0 @@
|
|||||||
name=Dreamy
|
|
||||||
description=Soft focus: the centre of the screen stays sharp while everything around it blurs, with a wide glow over the highlights. Good for slower games and cutscenes.
|
|
||||||
chain=Blur.fx|Blur|Radius=6,Strength=0.85,FocusSize=0.28,FocusSoftness=0.5;Bloom.fx|Bloom|Radius=2.0,Amount=0.55
|
|
||||||
Vendored
-3
@@ -1,3 +0,0 @@
|
|||||||
name=Retro
|
|
||||||
description=Old television look: colour fringing at the edges of the screen, curved scanlines with a phosphor mask, and darkened corners. The scanline roll is switched off so it does not crawl.
|
|
||||||
chain=ChromaticAberration.fx|ChromaticAberration|Strength=1.0;CRT.fx|CRT|RollSpeed=0,Bleed=1.2;Vignette.fx|Vignette|Strength=0.7
|
|
||||||
Vendored
-3
@@ -1,3 +0,0 @@
|
|||||||
name=Vivid
|
|
||||||
description=Punchier colour without stylising the image: warmer and more saturated, blacks and whites pushed to the ends of the range, and a light sharpen.
|
|
||||||
chain=NaturalColors.fx|NaturalColors|Luma=1.15,Chroma=1.35;Levels.fx|Levels|InputBlack=0.02,InputWhite=0.98;Sharpen.fx|Sharpen|Amount=0.5
|
|
||||||
Vendored
-70
@@ -1,70 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
// Ported from bloomnoblur.fsh in PPSSPP.
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Radius <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Radius";
|
|
||||||
ui_tooltip = "How far the glow spreads from bright areas.";
|
|
||||||
ui_min = 0.0; ui_max = 4.0; ui_step = 0.05;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
uniform float Amount <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Amount";
|
|
||||||
ui_tooltip = "Strength of the glow added on top of the image.";
|
|
||||||
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
|
||||||
> = 0.6;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float Weight(float3 color)
|
|
||||||
{
|
|
||||||
float gray = (color.r + color.g + color.b) / 3.0;
|
|
||||||
float saturation = (abs(color.r - gray) + abs(color.g - gray) + abs(color.b - gray)) / 3.0;
|
|
||||||
return gray * gray / max(saturation, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_Bloom(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float3 color = tex2D(BackBuffer, uv).rgb;
|
|
||||||
|
|
||||||
float gray = (color.r + color.g + color.b) / 3.0;
|
|
||||||
float saturation = (abs(color.r - gray) + abs(color.g - gray) + abs(color.b - gray)) / 3.0;
|
|
||||||
float spread = 0.002 * gray / max(saturation, 0.25) * Radius;
|
|
||||||
|
|
||||||
float3 sum = float3(0.0, 0.0, 0.0);
|
|
||||||
[unroll] for (int x = -3; x <= 3; x += 2)
|
|
||||||
{
|
|
||||||
[unroll] for (int y = -3; y <= 3; y += 2)
|
|
||||||
{
|
|
||||||
float3 tap = tex2D(BackBuffer, uv + float2(x, y) * spread).rgb;
|
|
||||||
sum += tap * Weight(tap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sum /= 16.0;
|
|
||||||
|
|
||||||
return float4(saturate(color + sum * Amount), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique Bloom <
|
|
||||||
ui_label = "Bloom";
|
|
||||||
ui_tooltip = "Bleeds light out of the brightest areas into a soft halo, the way a camera does when pointed at something too bright.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_Bloom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-87
@@ -1,87 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Radius <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Radius";
|
|
||||||
ui_tooltip = "How far the blur reaches, in pixels.";
|
|
||||||
ui_min = 1.0; ui_max = 12.0; ui_step = 0.5;
|
|
||||||
> = 4.0;
|
|
||||||
|
|
||||||
uniform float Strength <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Strength";
|
|
||||||
ui_tooltip = "Blend between the original image and the blurred one.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
uniform float FocusSize <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Sharp Centre";
|
|
||||||
ui_tooltip = "Size of the region left in focus at the centre of the screen. At zero the whole image is blurred evenly.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 0.0;
|
|
||||||
|
|
||||||
uniform float FocusSoftness <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Focus Falloff";
|
|
||||||
ui_tooltip = "How gradually the sharp centre gives way to the blur.";
|
|
||||||
ui_min = 0.05; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 0.4;
|
|
||||||
|
|
||||||
static const int TAPS = 17;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_Blur(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
|
||||||
float3 original = tex2D(BackBuffer, uv).rgb;
|
|
||||||
|
|
||||||
float2 centred = (uv - 0.5) * float2(BUFFER_WIDTH * BUFFER_RCP_HEIGHT, 1.0) * 2.0;
|
|
||||||
float distance_from_centre = length(centred);
|
|
||||||
float no_focus = 1.0 - step(0.001, FocusSize);
|
|
||||||
float focus = max(smoothstep(FocusSize, FocusSize + FocusSoftness, distance_from_centre), no_focus);
|
|
||||||
float amount = Strength * focus;
|
|
||||||
|
|
||||||
float angle = frac(sin(dot(pos.xy, float2(12.9898, 78.233))) * 43758.5453) * 6.2831853;
|
|
||||||
float2 spoke = float2(cos(angle), sin(angle));
|
|
||||||
|
|
||||||
const float2 turn = float2(cos(2.39996323), sin(2.39996323));
|
|
||||||
const float decay = exp(-2.0 / float(TAPS));
|
|
||||||
float weight = exp(-1.0 / float(TAPS));
|
|
||||||
|
|
||||||
float3 sum = float3(0.0, 0.0, 0.0);
|
|
||||||
float total = 0.0;
|
|
||||||
[unroll] for (int i = 0; i < TAPS; ++i)
|
|
||||||
{
|
|
||||||
float reach = sqrt((float(i) + 0.5) / float(TAPS));
|
|
||||||
sum += tex2D(BackBuffer, uv + spoke * reach * texel * Radius).rgb * weight;
|
|
||||||
total += weight;
|
|
||||||
weight *= decay;
|
|
||||||
spoke = float2(spoke.x * turn.x - spoke.y * turn.y,
|
|
||||||
spoke.x * turn.y + spoke.y * turn.x);
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 blurred = sum / total;
|
|
||||||
return float4(lerp(original, blurred, amount), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique Blur <
|
|
||||||
ui_label = "Blur";
|
|
||||||
ui_tooltip = "Gaussian blur with an optional sharp centre, for softening the picture or faking depth of field.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_Blur;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-74
@@ -1,74 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
// Ported from crt.fsh in PPSSPP, by KillaMaaki.
|
|
||||||
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Timer < source = "timer"; > = 0.0;
|
|
||||||
|
|
||||||
uniform float Density <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Line Density";
|
|
||||||
ui_min = 60.0; ui_max = 720.0; ui_step = 10.0;
|
|
||||||
> = 272.0;
|
|
||||||
|
|
||||||
uniform float RollSpeed <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Roll Speed";
|
|
||||||
ui_tooltip = "Speed of the rolling bar. Zero disables it.";
|
|
||||||
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
uniform float Bleed <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Colour Bleed";
|
|
||||||
ui_tooltip = "Horizontal separation of the red and green channels.";
|
|
||||||
ui_min = 0.0; ui_max = 4.0; ui_step = 0.1;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_CRT(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float seconds = Timer * 0.001;
|
|
||||||
float scan = floor((uv.y + seconds * RollSpeed * 0.5) * Density);
|
|
||||||
float line_intensity = frac(scan * 0.5) * 2.0;
|
|
||||||
|
|
||||||
float2 shift = float2(line_intensity * 0.0005, 0.0);
|
|
||||||
float2 bleed = float2(BUFFER_RCP_WIDTH * Bleed, 0.0);
|
|
||||||
|
|
||||||
float r = tex2D(BackBuffer, uv + bleed + shift).r;
|
|
||||||
float g = tex2D(BackBuffer, uv - bleed + shift).g;
|
|
||||||
float b = tex2D(BackBuffer, uv).b;
|
|
||||||
|
|
||||||
float3 color = float3(r, g * 0.99, b) * clamp(line_intensity, 0.85, 1.0);
|
|
||||||
|
|
||||||
if (RollSpeed > 0.0)
|
|
||||||
{
|
|
||||||
float rollbar = sin((uv.y + seconds * RollSpeed) * 4.0);
|
|
||||||
color += rollbar * 0.02;
|
|
||||||
}
|
|
||||||
|
|
||||||
return float4(saturate(color), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique CRT <
|
|
||||||
ui_label = "CRT";
|
|
||||||
ui_tooltip = "Curved scanlines and the phosphor mask of a CRT television, for games that were drawn with that kind of screen in mind.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_CRT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-79
@@ -1,79 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
|
||||||
// SPDX-FileCopyrightText: guest(r)
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
// Ported from cartoon.fsh in PPSSPP, Advanced Cartoon shader I by guest(r).
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float EdgeStrength <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Edge Strength";
|
|
||||||
ui_tooltip = "Darkness of the ink outline drawn around detected edges.";
|
|
||||||
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
|
||||||
> = 0.5;
|
|
||||||
|
|
||||||
uniform float Levels <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Colour Levels";
|
|
||||||
ui_tooltip = "How many bands the colours are quantised into.";
|
|
||||||
ui_min = 2.0; ui_max = 16.0; ui_step = 1.0;
|
|
||||||
> = 4.0;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_Cartoon(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
|
||||||
|
|
||||||
float3 c00 = tex2D(BackBuffer, uv + texel * float2(-1.0, -1.0)).rgb;
|
|
||||||
float3 c10 = tex2D(BackBuffer, uv + texel * float2( 0.0, -1.0)).rgb;
|
|
||||||
float3 c20 = tex2D(BackBuffer, uv + texel * float2( 1.0, -1.0)).rgb;
|
|
||||||
float3 c01 = tex2D(BackBuffer, uv + texel * float2(-1.0, 0.0)).rgb;
|
|
||||||
float3 c11 = tex2D(BackBuffer, uv).rgb;
|
|
||||||
float3 c21 = tex2D(BackBuffer, uv + texel * float2( 1.0, 0.0)).rgb;
|
|
||||||
float3 c02 = tex2D(BackBuffer, uv + texel * float2(-1.0, 1.0)).rgb;
|
|
||||||
float3 c12 = tex2D(BackBuffer, uv + texel * float2( 0.0, 1.0)).rgb;
|
|
||||||
float3 c22 = tex2D(BackBuffer, uv + texel * float2( 1.0, 1.0)).rgb;
|
|
||||||
|
|
||||||
const float3 dt = float3(1.0, 1.0, 1.0);
|
|
||||||
|
|
||||||
float d1 = dot(abs(c00 - c22), dt);
|
|
||||||
float d2 = dot(abs(c20 - c02), dt);
|
|
||||||
float hl = dot(abs(c01 - c21), dt);
|
|
||||||
float vl = dot(abs(c10 - c12), dt);
|
|
||||||
float edge = EdgeStrength * (d1 + d2 + hl + vl) / (dot(c11, dt) + 0.15);
|
|
||||||
|
|
||||||
float lc = Levels * length(c11);
|
|
||||||
float f = frac(lc);
|
|
||||||
f *= f;
|
|
||||||
lc = (floor(lc) + f * f) / Levels + 0.05;
|
|
||||||
|
|
||||||
float3 unit = normalize(max(c11, 0.0001));
|
|
||||||
float3 quant = Levels * unit;
|
|
||||||
float3 frct = frac(quant);
|
|
||||||
frct *= frct;
|
|
||||||
quant = floor(quant) + 0.05 * dt + frct * frct;
|
|
||||||
|
|
||||||
float3 color = lc * (1.1 - edge * sqrt(edge)) * quant / Levels;
|
|
||||||
return float4(saturate(color), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique Cartoon <
|
|
||||||
ui_label = "Cartoon";
|
|
||||||
ui_tooltip = "Ink outlines and flat colour bands. Faithful port of the PPSSPP shader; the outline gets heavy in dark scenes, so try Cartoon Soft or Cel Shading if the blacks smear.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_Cartoon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-100
@@ -1,100 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
|
||||||
// SPDX-FileCopyrightText: guest(r)
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float EdgeStrength <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Edge Strength";
|
|
||||||
ui_tooltip = "Darkness of the ink outline drawn around detected edges.";
|
|
||||||
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
|
||||||
> = 0.45;
|
|
||||||
|
|
||||||
uniform float ShadowGuard <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Shadow Guard";
|
|
||||||
ui_tooltip = "Holds the outline back in dark areas. Raise it if shadows turn into black blobs.";
|
|
||||||
ui_min = 0.1; ui_max = 2.0; ui_step = 0.05;
|
|
||||||
> = 0.8;
|
|
||||||
|
|
||||||
uniform float Levels <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Colour Levels";
|
|
||||||
ui_tooltip = "How many bands the colours are quantised into.";
|
|
||||||
ui_min = 2.0; ui_max = 16.0; ui_step = 1.0;
|
|
||||||
> = 6.0;
|
|
||||||
|
|
||||||
uniform float Smoothing <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Banding";
|
|
||||||
ui_tooltip = "How far the picture is pushed towards flat bands.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 0.75;
|
|
||||||
|
|
||||||
uniform float Saturation <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Saturation";
|
|
||||||
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
|
||||||
> = 1.15;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_CartoonSoft(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
|
||||||
|
|
||||||
float3 c00 = tex2D(BackBuffer, uv + texel * float2(-1.0, -1.0)).rgb;
|
|
||||||
float3 c10 = tex2D(BackBuffer, uv + texel * float2( 0.0, -1.0)).rgb;
|
|
||||||
float3 c20 = tex2D(BackBuffer, uv + texel * float2( 1.0, -1.0)).rgb;
|
|
||||||
float3 c01 = tex2D(BackBuffer, uv + texel * float2(-1.0, 0.0)).rgb;
|
|
||||||
float3 c11 = tex2D(BackBuffer, uv).rgb;
|
|
||||||
float3 c21 = tex2D(BackBuffer, uv + texel * float2( 1.0, 0.0)).rgb;
|
|
||||||
float3 c02 = tex2D(BackBuffer, uv + texel * float2(-1.0, 1.0)).rgb;
|
|
||||||
float3 c12 = tex2D(BackBuffer, uv + texel * float2( 0.0, 1.0)).rgb;
|
|
||||||
float3 c22 = tex2D(BackBuffer, uv + texel * float2( 1.0, 1.0)).rgb;
|
|
||||||
|
|
||||||
const float3 dt = float3(1.0, 1.0, 1.0);
|
|
||||||
const float3 luma_weights = float3(0.299, 0.587, 0.114);
|
|
||||||
|
|
||||||
float d1 = dot(abs(c00 - c22), dt);
|
|
||||||
float d2 = dot(abs(c20 - c02), dt);
|
|
||||||
float hl = dot(abs(c01 - c21), dt);
|
|
||||||
float vl = dot(abs(c10 - c12), dt);
|
|
||||||
|
|
||||||
float luma = dot(c11, luma_weights);
|
|
||||||
float response = (d1 + d2 + hl + vl) / (luma * 2.0 + ShadowGuard);
|
|
||||||
float ink = 1.0 - saturate(response * EdgeStrength);
|
|
||||||
|
|
||||||
float scaled = luma * Levels;
|
|
||||||
float step_position = frac(scaled);
|
|
||||||
float eased = step_position * step_position * (3.0 - 2.0 * step_position);
|
|
||||||
float banded = (floor(scaled) + eased) / Levels;
|
|
||||||
float target = lerp(luma, banded, Smoothing);
|
|
||||||
|
|
||||||
float3 tinted = c11 * (target / max(luma, 0.001));
|
|
||||||
float3 grey = dot(tinted, luma_weights);
|
|
||||||
float3 color = lerp(grey, tinted, Saturation) * ink;
|
|
||||||
|
|
||||||
return float4(saturate(color), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique CartoonSoft <
|
|
||||||
ui_label = "Cartoon Soft";
|
|
||||||
ui_tooltip = "Ink outlines and flat colour bands, with the outline held back in the shadows so dark scenes do not turn into black blobs.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_CartoonSoft;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-120
@@ -1,120 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Bands <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Shading Bands";
|
|
||||||
ui_tooltip = "How many flat steps the lighting is collapsed into. Three or four gives the classic look.";
|
|
||||||
ui_min = 2.0; ui_max = 8.0; ui_step = 1.0;
|
|
||||||
> = 4.0;
|
|
||||||
|
|
||||||
uniform float BandContrast <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Band Contrast";
|
|
||||||
ui_tooltip = "Spreads the bands towards pure black and white. Lower it if the shadows crush.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 0.5;
|
|
||||||
|
|
||||||
uniform float BandEdge <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Band Edge";
|
|
||||||
ui_tooltip = "Width of the transition between bands. Low values give hard cel steps.";
|
|
||||||
ui_min = 0.02; ui_max = 1.0; ui_step = 0.02;
|
|
||||||
> = 0.15;
|
|
||||||
|
|
||||||
uniform float Detail <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Texture Detail";
|
|
||||||
ui_tooltip = "How much surface texture survives the flattening. At zero the bands are completely flat.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 0.6;
|
|
||||||
|
|
||||||
uniform float OutlineStrength <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Outline Strength";
|
|
||||||
ui_tooltip = "Opacity of the ink line drawn along detected edges.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 0.8;
|
|
||||||
|
|
||||||
uniform float OutlineThreshold <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Outline Threshold";
|
|
||||||
ui_tooltip = "How strong an edge has to be before it is inked. Raise it to keep ink off textures and specular highlights.";
|
|
||||||
ui_min = 0.01; ui_max = 0.4; ui_step = 0.01;
|
|
||||||
> = 0.18;
|
|
||||||
|
|
||||||
uniform float Saturation <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Saturation";
|
|
||||||
ui_tooltip = "Colour intensity of the flat regions.";
|
|
||||||
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
|
||||||
> = 1.25;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_CelShading(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
const float3 luma_weights = float3(0.2126, 0.7152, 0.0722);
|
|
||||||
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
|
||||||
|
|
||||||
float3 centre = tex2D(BackBuffer, uv).rgb;
|
|
||||||
float luma = dot(centre, luma_weights);
|
|
||||||
|
|
||||||
float2 near = texel * 1.5;
|
|
||||||
float2 far = texel * 3.5;
|
|
||||||
|
|
||||||
float a00 = dot(tex2D(BackBuffer, uv + near * float2(-1.0, -1.0)).rgb, luma_weights);
|
|
||||||
float a20 = dot(tex2D(BackBuffer, uv + near * float2( 1.0, -1.0)).rgb, luma_weights);
|
|
||||||
float a02 = dot(tex2D(BackBuffer, uv + near * float2(-1.0, 1.0)).rgb, luma_weights);
|
|
||||||
float a22 = dot(tex2D(BackBuffer, uv + near * float2( 1.0, 1.0)).rgb, luma_weights);
|
|
||||||
|
|
||||||
float b00 = dot(tex2D(BackBuffer, uv + far * float2(-1.0, -1.0)).rgb, luma_weights);
|
|
||||||
float b20 = dot(tex2D(BackBuffer, uv + far * float2( 1.0, -1.0)).rgb, luma_weights);
|
|
||||||
float b02 = dot(tex2D(BackBuffer, uv + far * float2(-1.0, 1.0)).rgb, luma_weights);
|
|
||||||
float b22 = dot(tex2D(BackBuffer, uv + far * float2( 1.0, 1.0)).rgb, luma_weights);
|
|
||||||
|
|
||||||
float gx = (a00 + a02) - (a20 + a22);
|
|
||||||
float gy = (a00 + a20) - (a02 + a22);
|
|
||||||
float gradient = sqrt(gx * gx + gy * gy);
|
|
||||||
float ink = smoothstep(OutlineThreshold, OutlineThreshold + 0.04, gradient);
|
|
||||||
|
|
||||||
float lighting = (a00 + a20 + a02 + a22 + b00 + b20 + b02 + b22) * 0.125;
|
|
||||||
float detail = luma - lighting;
|
|
||||||
|
|
||||||
float softness = max(BandEdge, 0.001);
|
|
||||||
float coord = lighting * Bands - softness * 0.5;
|
|
||||||
float index = floor(coord) + smoothstep(1.0 - softness, 1.0, frac(coord));
|
|
||||||
|
|
||||||
float centred = (index + 0.5) / Bands;
|
|
||||||
float stretched = index / max(Bands - 1.0, 1.0);
|
|
||||||
float banded = saturate(lerp(centred, stretched, BandContrast));
|
|
||||||
|
|
||||||
float target = saturate(banded + detail * Detail);
|
|
||||||
|
|
||||||
float gain = min(target / max(luma, 0.001), 4.0);
|
|
||||||
float3 shaded = centre * gain;
|
|
||||||
float3 grey = dot(shaded, luma_weights);
|
|
||||||
float3 color = lerp(grey, shaded, Saturation);
|
|
||||||
color *= 1.0 - ink * OutlineStrength;
|
|
||||||
|
|
||||||
return float4(saturate(color), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique CelShading <
|
|
||||||
ui_label = "Cel Shading";
|
|
||||||
ui_tooltip = "Collapses the lighting into a few hard steps and inks the edges, keeping texture detail and hue intact.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_CelShading;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-53
@@ -1,53 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Strength <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Strength";
|
|
||||||
ui_tooltip = "Channel separation in pixels, measured at the edge of the screen.";
|
|
||||||
ui_min = 0.0; ui_max = 8.0; ui_step = 0.1;
|
|
||||||
> = 1.5;
|
|
||||||
|
|
||||||
uniform float Falloff <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Falloff";
|
|
||||||
ui_tooltip = "How fast the separation grows away from the centre. Higher keeps the middle clean.";
|
|
||||||
ui_min = 1.0; ui_max = 4.0; ui_step = 0.1;
|
|
||||||
> = 2.0;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_ChromaticAberration(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
|
||||||
|
|
||||||
float2 direction = uv - float2(0.5, 0.5);
|
|
||||||
float radius = length(direction);
|
|
||||||
float2 unit = direction / max(radius, 0.0001);
|
|
||||||
float2 offset = unit * Strength * pow(radius * 2.0, Falloff) * texel;
|
|
||||||
|
|
||||||
float red = tex2D(BackBuffer, uv + offset).r;
|
|
||||||
float green = tex2D(BackBuffer, uv).g;
|
|
||||||
float blue = tex2D(BackBuffer, uv - offset).b;
|
|
||||||
|
|
||||||
return float4(red, green, blue, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique ChromaticAberration <
|
|
||||||
ui_label = "Chromatic Aberration";
|
|
||||||
ui_tooltip = "Splits the colour channels apart towards the edges of the screen, the way a cheap lens fails to focus every colour on the same spot.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_ChromaticAberration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-64
@@ -1,64 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
// Based on colorcorrection.fsh in PPSSPP.
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
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_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
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(BackBuffer, 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 ColorGrade <
|
|
||||||
ui_label = "Colour Grade";
|
|
||||||
ui_tooltip = "The four basic colour controls in one pass: saturation, brightness, contrast and gamma. Reach for this before anything more specialised.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_ColorGrade;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-91
@@ -1,91 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2015 Niklas Haas
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Threshold <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Threshold";
|
|
||||||
ui_tooltip = "How flat a neighbourhood must be before it gets smoothed. Raise it to catch wider bands, lower it to keep more detail.";
|
|
||||||
ui_min = 0.002; ui_max = 0.05; ui_step = 0.001;
|
|
||||||
> = 0.012;
|
|
||||||
|
|
||||||
uniform float Radius <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Radius";
|
|
||||||
ui_tooltip = "How far the sampling reaches, in pixels. Wider gradients need a larger radius.";
|
|
||||||
ui_min = 1.0; ui_max = 32.0; ui_step = 1.0;
|
|
||||||
> = 8.0;
|
|
||||||
|
|
||||||
uniform float Grain <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Dither";
|
|
||||||
ui_tooltip = "Noise added to break up whatever banding survives the smoothing.";
|
|
||||||
ui_min = 0.0; ui_max = 0.02; ui_step = 0.001;
|
|
||||||
> = 0.004;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float Hash(float2 p)
|
|
||||||
{
|
|
||||||
float3 scattered = frac(float3(p.x, p.y, p.x) * 0.1031);
|
|
||||||
scattered += dot(scattered, scattered.yzx + 33.33);
|
|
||||||
return frac((scattered.x + scattered.y) * scattered.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_Deband(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
|
||||||
|
|
||||||
float3 centre = tex2D(BackBuffer, uv).rgb;
|
|
||||||
float base = Hash(pos.xy) * 6.2831853;
|
|
||||||
|
|
||||||
float3 total = float3(0.0, 0.0, 0.0);
|
|
||||||
float3 deviation = float3(0.0, 0.0, 0.0);
|
|
||||||
|
|
||||||
[unroll] for (int ring = 1; ring <= 2; ++ring)
|
|
||||||
{
|
|
||||||
float angle = base + float(ring) * 2.3999632;
|
|
||||||
float reach = Radius * float(ring) * 0.5;
|
|
||||||
float2 along = float2(cos(angle), sin(angle)) * reach;
|
|
||||||
float2 across = float2(-along.y, along.x);
|
|
||||||
|
|
||||||
float3 s0 = tex2D(BackBuffer, uv + along * texel).rgb;
|
|
||||||
float3 s1 = tex2D(BackBuffer, uv - along * texel).rgb;
|
|
||||||
float3 s2 = tex2D(BackBuffer, uv + across * texel).rgb;
|
|
||||||
float3 s3 = tex2D(BackBuffer, uv - across * texel).rgb;
|
|
||||||
|
|
||||||
total += s0 + s1 + s2 + s3;
|
|
||||||
deviation = max(deviation, max(max(abs(s0 - centre), abs(s1 - centre)),
|
|
||||||
max(abs(s2 - centre), abs(s3 - centre))));
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 average = total * 0.125;
|
|
||||||
float3 flatness = float3(1.0, 1.0, 1.0) -
|
|
||||||
smoothstep(Threshold * 0.5, Threshold, deviation);
|
|
||||||
float3 result = lerp(centre, average, flatness);
|
|
||||||
|
|
||||||
float dither = (Hash(pos.xy + float2(71.3, 41.7)) - 0.5) * Grain;
|
|
||||||
|
|
||||||
return float4(saturate(result + dither), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique Deband <
|
|
||||||
ui_label = "Deband";
|
|
||||||
ui_tooltip = "Smooths the visible steps in gradients such as skies, then dithers whatever survives. Worth adding whenever large flat areas show rings.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_Deband;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-83
@@ -1,83 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2019-2021 bloc97
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Strength <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Strength";
|
|
||||||
ui_min = 0.01; ui_max = 0.5; ui_step = 0.01;
|
|
||||||
> = 0.1;
|
|
||||||
|
|
||||||
uniform float Radius <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Radius";
|
|
||||||
ui_min = 0.3; ui_max = 2.0; ui_step = 0.05;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
uniform float Curve <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Shadow Bias";
|
|
||||||
ui_min = 0.0; ui_max = 2.0; ui_step = 0.05;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 IntensityWeight(float3 value, float3 sigma, float3 centre)
|
|
||||||
{
|
|
||||||
float3 scaled = (value - centre) / sigma;
|
|
||||||
return exp(-0.5 * scaled * scaled);
|
|
||||||
}
|
|
||||||
|
|
||||||
float SpatialWeight(float distance, float sigma)
|
|
||||||
{
|
|
||||||
float scaled = distance / sigma;
|
|
||||||
return exp(-0.5 * scaled * scaled);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_Denoise(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT);
|
|
||||||
|
|
||||||
float3 centre = tex2D(BackBuffer, uv).rgb;
|
|
||||||
float3 intensity_sigma = max(pow(centre + 0.0001, Curve) * Strength, 0.0001);
|
|
||||||
float spatial_sigma = max(Radius, 0.05);
|
|
||||||
|
|
||||||
float3 sum = float3(0.0, 0.0, 0.0);
|
|
||||||
float3 total = float3(0.0, 0.0, 0.0);
|
|
||||||
|
|
||||||
[unroll] for (int y = -2; y <= 2; ++y)
|
|
||||||
{
|
|
||||||
[unroll] for (int x = -2; x <= 2; ++x)
|
|
||||||
{
|
|
||||||
float2 offset = float2(x, y);
|
|
||||||
float3 tap = tex2D(BackBuffer, uv + offset * texel).rgb;
|
|
||||||
float3 weight = IntensityWeight(tap, intensity_sigma, centre) *
|
|
||||||
SpatialWeight(length(offset), spatial_sigma);
|
|
||||||
sum += weight * tap;
|
|
||||||
total += weight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return float4(sum / total, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique Denoise <
|
|
||||||
ui_label = "Denoise";
|
|
||||||
ui_tooltip = "Edge preserving blur that clears dithering and compression noise while leaving outlines sharp. Ported from Anime4K.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_Denoise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-65
@@ -1,65 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Intensity <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Intensity";
|
|
||||||
ui_min = 0.0; ui_max = 0.2; ui_step = 0.005;
|
|
||||||
> = 0.03;
|
|
||||||
|
|
||||||
uniform float Size <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Size";
|
|
||||||
ui_tooltip = "Grain cell size in pixels.";
|
|
||||||
ui_min = 1.0; ui_max = 4.0; ui_step = 1.0;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
uniform float Colored <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Colour";
|
|
||||||
ui_tooltip = "Zero gives monochrome grain, one gives independent noise per channel.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
|
||||||
> = 0.0;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float Hash(float2 p)
|
|
||||||
{
|
|
||||||
float3 scattered = frac(float3(p.x, p.y, p.x) * 0.1031);
|
|
||||||
scattered += dot(scattered, scattered.yzx + 33.33);
|
|
||||||
return frac((scattered.x + scattered.y) * scattered.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_FilmGrain(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
|
||||||
|
|
||||||
float2 cell = floor(pos.xy / max(Size, 1.0));
|
|
||||||
float mono = Hash(cell) - 0.5;
|
|
||||||
float3 chroma = float3(Hash(cell + 11.7), Hash(cell + 23.1), Hash(cell + 37.5)) - 0.5;
|
|
||||||
float3 noise = lerp(float3(mono, mono, mono), chroma, Colored);
|
|
||||||
|
|
||||||
float luma = dot(rgb, float3(0.2126, 0.7152, 0.0722));
|
|
||||||
float response = 1.0 - abs(luma * 2.0 - 1.0);
|
|
||||||
|
|
||||||
return float4(saturate(rgb + noise * Intensity * response), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique FilmGrain <
|
|
||||||
ui_label = "Film Grain";
|
|
||||||
ui_tooltip = "Adds photographic grain, strongest in the midtones and fading out in blacks and whites.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_FilmGrain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-60
@@ -1,60 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Exposure <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Exposure";
|
|
||||||
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
uniform float Toe <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Toe";
|
|
||||||
ui_tooltip = "Above 1.0 deepens the shadows, below 1.0 lifts them.";
|
|
||||||
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
|
||||||
> = 1.2;
|
|
||||||
|
|
||||||
uniform float Shoulder <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Shoulder";
|
|
||||||
ui_tooltip = "Above 1.0 opens up the highlights, below 1.0 compresses them.";
|
|
||||||
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
|
||||||
> = 1.2;
|
|
||||||
|
|
||||||
uniform float Amount <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Amount";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
|
||||||
> = 0.7;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_FilmicCurve(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
|
||||||
|
|
||||||
float3 curved = saturate(rgb * Exposure);
|
|
||||||
curved = pow(max(curved, 0.0), Toe);
|
|
||||||
curved = 1.0 - pow(max(1.0 - curved, 0.0), Shoulder);
|
|
||||||
|
|
||||||
return float4(saturate(lerp(rgb, curved, Amount)), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique FilmicCurve <
|
|
||||||
ui_label = "Filmic Curve";
|
|
||||||
ui_tooltip = "Filmic contrast curve. Deepens the shadows and opens the highlights without clipping either end.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_FilmicCurve;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-53
@@ -1,53 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Distortion <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Distortion";
|
|
||||||
ui_tooltip = "Positive bulges the picture outwards, negative pinches it inwards.";
|
|
||||||
ui_min = -0.5; ui_max = 0.5; ui_step = 0.01;
|
|
||||||
> = 0.1;
|
|
||||||
|
|
||||||
uniform float Zoom <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Zoom";
|
|
||||||
ui_tooltip = "Scales the picture to hide the edges the warp pulls in.";
|
|
||||||
ui_min = 0.5; ui_max = 1.5; ui_step = 0.01;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_LensDistortion(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float aspect = BUFFER_WIDTH * BUFFER_RCP_HEIGHT;
|
|
||||||
float2 half_size = float2(aspect, 1.0);
|
|
||||||
float2 unit = half_size / length(half_size);
|
|
||||||
|
|
||||||
float2 centred = (uv - 0.5) * 2.0 * unit;
|
|
||||||
float r2 = dot(centred, centred);
|
|
||||||
centred *= 1.0 + Distortion * r2;
|
|
||||||
centred /= max(Zoom, 0.001);
|
|
||||||
|
|
||||||
float2 source = centred / (2.0 * unit) + 0.5;
|
|
||||||
|
|
||||||
return float4(tex2D(BackBuffer, source).rgb, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique LensDistortion <
|
|
||||||
ui_label = "Lens Distortion";
|
|
||||||
ui_tooltip = "Barrel or pincushion warp, like looking through a wide angle lens.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_LensDistortion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-66
@@ -1,66 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float InputBlack <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Input Black";
|
|
||||||
ui_tooltip = "Input level mapped to black. Raise it to deepen washed out shadows.";
|
|
||||||
ui_min = 0.0; ui_max = 0.5; ui_step = 0.005;
|
|
||||||
> = 0.0;
|
|
||||||
|
|
||||||
uniform float InputWhite <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Input White";
|
|
||||||
ui_min = 0.5; ui_max = 1.0; ui_step = 0.005;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
uniform float Gamma <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Gamma";
|
|
||||||
ui_min = 0.2; ui_max = 3.0; ui_step = 0.01;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
uniform float OutputBlack <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Output Black";
|
|
||||||
ui_tooltip = "Lifts crushed shadows so detail stops collapsing into one flat black.";
|
|
||||||
ui_min = 0.0; ui_max = 0.5; ui_step = 0.005;
|
|
||||||
> = 0.0;
|
|
||||||
|
|
||||||
uniform float OutputWhite <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Output White";
|
|
||||||
ui_min = 0.5; ui_max = 1.0; ui_step = 0.005;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_Levels(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
|
||||||
|
|
||||||
rgb = saturate((rgb - InputBlack) / max(InputWhite - InputBlack, 0.001));
|
|
||||||
rgb = pow(max(rgb, 0.0), 1.0 / max(Gamma, 0.001));
|
|
||||||
rgb = lerp(OutputBlack, OutputWhite, rgb);
|
|
||||||
|
|
||||||
return float4(saturate(rgb), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique Levels <
|
|
||||||
ui_label = "Levels";
|
|
||||||
ui_tooltip = "Black point, white point, gamma and output range. Use it to fix crushed or washed out shadows.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_Levels;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-88
@@ -1,88 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Length <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Length";
|
|
||||||
ui_tooltip = "How far the streaks reach, as a percentage of screen height.";
|
|
||||||
ui_min = 0.0; ui_max = 20.0; ui_step = 0.5;
|
|
||||||
> = 5.0;
|
|
||||||
|
|
||||||
uniform float Zoom <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Zoom";
|
|
||||||
ui_tooltip = "Streaks running out from the centre of the screen, as if rushing forward. The centre stays sharp.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
uniform float Pan <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Pan";
|
|
||||||
ui_tooltip = "Streaks running in one fixed direction, as if the camera were sweeping across.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 0.0;
|
|
||||||
|
|
||||||
uniform float Angle <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Pan Angle";
|
|
||||||
ui_tooltip = "Direction of the sweep, in degrees.";
|
|
||||||
ui_min = 0.0; ui_max = 360.0; ui_step = 5.0;
|
|
||||||
> = 0.0;
|
|
||||||
|
|
||||||
uniform float Spin <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Spin";
|
|
||||||
ui_tooltip = "Streaks curling around the centre, as if the camera were rolling. Negative turns the other way.";
|
|
||||||
ui_min = -1.0; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 0.0;
|
|
||||||
|
|
||||||
static const int TAPS = 24;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_MotionBlur(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float aspect = BUFFER_WIDTH * BUFFER_RCP_HEIGHT;
|
|
||||||
float2 to_screen = float2(aspect, 1.0);
|
|
||||||
|
|
||||||
float2 centred = (uv - 0.5) * to_screen * 2.0;
|
|
||||||
float2 outward = centred;
|
|
||||||
float2 around = float2(-centred.y, centred.x);
|
|
||||||
|
|
||||||
float radians_angle = Angle * 0.01745329;
|
|
||||||
float2 sweep = float2(cos(radians_angle), sin(radians_angle));
|
|
||||||
|
|
||||||
float2 velocity = sweep * Pan + outward * Zoom + around * Spin;
|
|
||||||
velocity *= Length * 0.01;
|
|
||||||
velocity /= to_screen;
|
|
||||||
|
|
||||||
float jitter = frac(sin(dot(pos.xy, float2(12.9898, 78.233))) * 43758.5453);
|
|
||||||
|
|
||||||
float3 sum = float3(0.0, 0.0, 0.0);
|
|
||||||
[unroll] for (int i = 0; i < TAPS; ++i)
|
|
||||||
{
|
|
||||||
float t = (float(i) + jitter) / float(TAPS) - 0.5;
|
|
||||||
sum += tex2D(BackBuffer, uv + velocity * t).rgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
return float4(sum / float(TAPS), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique MotionBlur <
|
|
||||||
ui_label = "Motion Blur";
|
|
||||||
ui_tooltip = "Camera style motion blur: streaks the picture outwards, sideways or around, without touching still detail at the centre.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_MotionBlur;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-60
@@ -1,60 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
// Ported from naturalA.fsh in PPSSPP, by ShadX, modified by SimoneT.
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Luma <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Luma Curve";
|
|
||||||
ui_tooltip = "Gamma applied to the luminance channel in YIQ space.";
|
|
||||||
ui_min = 0.5; ui_max = 2.0; ui_step = 0.01;
|
|
||||||
> = 1.2;
|
|
||||||
|
|
||||||
uniform float Chroma <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Chroma Gain";
|
|
||||||
ui_tooltip = "Boost applied to the two colour difference channels.";
|
|
||||||
ui_min = 0.0; ui_max = 2.0; ui_step = 0.01;
|
|
||||||
> = 1.2;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_Natural(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
const float3x3 RGBtoYIQ = float3x3(0.299, 0.587, 0.114,
|
|
||||||
0.596, -0.275, -0.321,
|
|
||||||
0.212, -0.523, 0.311);
|
|
||||||
|
|
||||||
const float3x3 YIQtoRGB = float3x3(1.0, 0.95568806, 0.61985809,
|
|
||||||
1.0, -0.27158180, -0.64687382,
|
|
||||||
1.0, -1.10817733, 1.70506456);
|
|
||||||
|
|
||||||
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
|
||||||
float3 yiq = mul(RGBtoYIQ, rgb);
|
|
||||||
|
|
||||||
yiq.x = pow(max(yiq.x, 0.0), Luma);
|
|
||||||
yiq.yz *= Chroma;
|
|
||||||
|
|
||||||
return float4(saturate(mul(YIQtoRGB, yiq)), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique NaturalColors <
|
|
||||||
ui_label = "Natural Colours";
|
|
||||||
ui_tooltip = "Warms the picture and lifts saturation for a less washed out look. Ported from the PPSSPP shader.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_Natural;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-90
@@ -1,90 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Timer < source = "timer"; > = 0.0;
|
|
||||||
|
|
||||||
uniform float Horizon <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Reflection Line";
|
|
||||||
ui_tooltip = "Height on screen where the reflective floor begins. Everything above it is left untouched.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
|
||||||
> = 0.55;
|
|
||||||
|
|
||||||
uniform float Amount <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Amount";
|
|
||||||
ui_tooltip = "How strongly the reflection shows through the floor.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 0.35;
|
|
||||||
|
|
||||||
uniform float Falloff <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Falloff";
|
|
||||||
ui_tooltip = "How quickly the reflection fades as the floor comes towards the viewer. At zero it stays even.";
|
|
||||||
ui_min = 0.0; ui_max = 4.0; ui_step = 0.1;
|
|
||||||
> = 1.2;
|
|
||||||
|
|
||||||
uniform float Perspective <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Perspective";
|
|
||||||
ui_tooltip = "Stretches or squashes the mirrored image. One is a true mirror, higher values pull it towards the line.";
|
|
||||||
ui_min = 0.2; ui_max = 2.0; ui_step = 0.05;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
uniform float Ripple <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Ripple";
|
|
||||||
ui_tooltip = "Amplitude of the waves disturbing the reflection. At zero the mirror is perfectly still.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 0.0;
|
|
||||||
|
|
||||||
uniform float RippleSpeed <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Ripple Speed";
|
|
||||||
ui_tooltip = "How fast the waves travel.";
|
|
||||||
ui_min = 0.0; ui_max = 4.0; ui_step = 0.1;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_Reflections(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float3 color = tex2D(BackBuffer, uv).rgb;
|
|
||||||
|
|
||||||
float depth = uv.y - Horizon;
|
|
||||||
float on_floor = step(0.0, depth);
|
|
||||||
float span = max(1.0 - Horizon, 0.001);
|
|
||||||
float distance_down = saturate(depth / span);
|
|
||||||
|
|
||||||
float seconds = Timer * 0.001;
|
|
||||||
float wave = sin(uv.x * 38.0 + seconds * RippleSpeed * 2.0) *
|
|
||||||
sin(uv.y * 21.0 - seconds * RippleSpeed * 1.3);
|
|
||||||
float2 disturbance = float2(wave * 0.004, wave * 0.002) * Ripple * distance_down;
|
|
||||||
|
|
||||||
float2 mirrored = float2(uv.x, Horizon - depth * Perspective) + disturbance;
|
|
||||||
float3 reflection = tex2D(BackBuffer, saturate(mirrored)).rgb;
|
|
||||||
|
|
||||||
float fade = pow(max(1.0 - distance_down, 0.0001), Falloff);
|
|
||||||
float strength = Amount * fade * on_floor;
|
|
||||||
|
|
||||||
return float4(lerp(color, reflection, strength), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique Reflections <
|
|
||||||
ui_label = "Reflections";
|
|
||||||
ui_tooltip = "Mirrors the picture into a reflective floor below a line you choose, with optional water ripples.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_Reflections;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-66
@@ -1,66 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
// Ported from scanlines.fsh in PPSSPP.
|
|
||||||
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Density <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Line Density";
|
|
||||||
ui_tooltip = "Number of scanline pairs across the screen.";
|
|
||||||
ui_min = 60.0; ui_max = 720.0; ui_step = 10.0;
|
|
||||||
> = 340.0;
|
|
||||||
|
|
||||||
uniform float Intensity <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Intensity";
|
|
||||||
ui_tooltip = "How dark the gaps between lines become.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 0.5;
|
|
||||||
|
|
||||||
uniform float Tint <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Phosphor Tint";
|
|
||||||
ui_tooltip = "Strength of the green-warm phosphor cast.";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.05;
|
|
||||||
> = 1.0;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_Scanlines(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float line_pos = uv.y * Density * 0.5;
|
|
||||||
float gate = cos((frac(line_pos) - 0.5) * 3.1415926 * Intensity) * 1.5;
|
|
||||||
|
|
||||||
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
|
||||||
float3 color = rgb * 0.5 + 0.5 * rgb * rgb * 1.2;
|
|
||||||
|
|
||||||
float3 phosphor = lerp(float3(1.0, 1.0, 1.0), float3(0.9, 1.0, 0.7), Tint);
|
|
||||||
color *= phosphor;
|
|
||||||
|
|
||||||
float2 diff = uv - 0.5;
|
|
||||||
color *= 1.1 - 0.6 * (dot(diff, diff) * 2.0);
|
|
||||||
|
|
||||||
return float4(saturate(color * saturate(gate)), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique Scanlines <
|
|
||||||
ui_label = "Scanlines";
|
|
||||||
ui_tooltip = "Darkens alternating rows of pixels to imitate the horizontal scanlines of a CRT. Cheaper than the full CRT effect when you only want the lines.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_Scanlines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-43
@@ -1,43 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
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_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
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(BackBuffer, uv).rgb;
|
|
||||||
float3 blur = tex2D(BackBuffer, uv + float2(-texel.x, 0.0)).rgb;
|
|
||||||
blur += tex2D(BackBuffer, uv + float2(texel.x, 0.0)).rgb;
|
|
||||||
blur += tex2D(BackBuffer, uv + float2(0.0, -texel.y)).rgb;
|
|
||||||
blur += tex2D(BackBuffer, uv + float2(0.0, texel.y)).rgb;
|
|
||||||
blur *= 0.25;
|
|
||||||
|
|
||||||
return float4(centre + (centre - blur) * Amount, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique Sharpen <
|
|
||||||
ui_label = "Sharpen";
|
|
||||||
ui_tooltip = "Unsharp mask that brings back edge detail lost to scaling. Most useful after upscaling a low internal resolution.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_Sharpen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-75
@@ -1,75 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float ShadowHue <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Shadow Hue";
|
|
||||||
ui_min = 0.0; ui_max = 360.0; ui_step = 1.0;
|
|
||||||
> = 210.0;
|
|
||||||
|
|
||||||
uniform float ShadowStrength <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Shadow Strength";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
|
||||||
> = 0.0;
|
|
||||||
|
|
||||||
uniform float HighlightHue <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Highlight Hue";
|
|
||||||
ui_min = 0.0; ui_max = 360.0; ui_step = 1.0;
|
|
||||||
> = 45.0;
|
|
||||||
|
|
||||||
uniform float HighlightStrength <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Highlight Strength";
|
|
||||||
ui_min = 0.0; ui_max = 1.0; ui_step = 0.01;
|
|
||||||
> = 0.0;
|
|
||||||
|
|
||||||
uniform float Balance <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Balance";
|
|
||||||
ui_tooltip = "Moves the split between what counts as shadow and what counts as highlight.";
|
|
||||||
ui_min = -0.5; ui_max = 0.5; ui_step = 0.01;
|
|
||||||
> = 0.0;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 HueToRGB(float hue)
|
|
||||||
{
|
|
||||||
float h = frac(hue / 360.0) * 6.0;
|
|
||||||
return saturate(float3(abs(h - 3.0) - 1.0, 2.0 - abs(h - 2.0), 2.0 - abs(h - 4.0)));
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_SplitToning(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float3 rgb = tex2D(BackBuffer, uv).rgb;
|
|
||||||
|
|
||||||
float luma = saturate(dot(rgb, float3(0.2126, 0.7152, 0.0722)) + Balance);
|
|
||||||
|
|
||||||
float3 shadow_tint = HueToRGB(ShadowHue) - 0.5;
|
|
||||||
float3 highlight_tint = HueToRGB(HighlightHue) - 0.5;
|
|
||||||
|
|
||||||
rgb += shadow_tint * ShadowStrength * 0.25 * (1.0 - luma);
|
|
||||||
rgb += highlight_tint * HighlightStrength * 0.25 * luma;
|
|
||||||
|
|
||||||
return float4(saturate(rgb), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique SplitToning <
|
|
||||||
ui_label = "Split Toning";
|
|
||||||
ui_tooltip = "Tints the shadows and the highlights towards two different hues.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_SplitToning;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-52
@@ -1,52 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
// Ported from vignette.fsh in PPSSPP, by Henrik Rydgard.
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
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_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
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(BackBuffer, uv).rgb;
|
|
||||||
|
|
||||||
return float4(rgb * falloff, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique Vignette <
|
|
||||||
ui_label = "Vignette";
|
|
||||||
ui_tooltip = "Darkens the corners of the screen to pull the eye towards the middle, the way a camera lens falls off at its edges.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_Vignette;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-76
@@ -1,76 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
texture BackBufferTex : COLOR;
|
|
||||||
sampler BackBuffer { Texture = BackBufferTex; };
|
|
||||||
|
|
||||||
uniform float Temperature <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Temperature";
|
|
||||||
ui_tooltip = "Negative cools the picture towards blue, positive warms it towards orange.";
|
|
||||||
ui_min = -100.0; ui_max = 100.0; ui_step = 1.0;
|
|
||||||
> = 0.0;
|
|
||||||
|
|
||||||
uniform float Tint <
|
|
||||||
ui_type = "slider";
|
|
||||||
ui_label = "Tint";
|
|
||||||
ui_tooltip = "Negative shifts towards green, positive towards magenta.";
|
|
||||||
ui_min = -100.0; ui_max = 100.0; ui_step = 1.0;
|
|
||||||
> = 0.0;
|
|
||||||
|
|
||||||
void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD)
|
|
||||||
{
|
|
||||||
uv = float2(float(id & 2), float((id & 1) << 1));
|
|
||||||
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float3 WhitePointLMS(float t1, float t2)
|
|
||||||
{
|
|
||||||
float shift = 0.05;
|
|
||||||
if (t1 < 0.0)
|
|
||||||
{
|
|
||||||
shift = 0.10;
|
|
||||||
}
|
|
||||||
float x = 0.31271 - t1 * shift;
|
|
||||||
float y = 2.87 * x - 3.0 * x * x - 0.27509507 + t2 * 0.05;
|
|
||||||
|
|
||||||
float big_y = 1.0;
|
|
||||||
float big_x = big_y * x / y;
|
|
||||||
float big_z = big_y * (1.0 - x - y) / y;
|
|
||||||
|
|
||||||
return float3( 0.7328 * big_x + 0.4296 * big_y - 0.1624 * big_z,
|
|
||||||
-0.7036 * big_x + 1.6975 * big_y + 0.0061 * big_z,
|
|
||||||
0.0030 * big_x + 0.0136 * big_y + 0.9834 * big_z);
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PS_WhiteBalance(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
|
||||||
{
|
|
||||||
float3 rgb = pow(max(tex2D(BackBuffer, uv).rgb, 0.0), 2.2);
|
|
||||||
|
|
||||||
float3 balance = float3(0.949237, 1.03542, 1.08728) /
|
|
||||||
WhitePointLMS(Temperature / 65.0, Tint / 65.0);
|
|
||||||
|
|
||||||
const float3x3 rgb_to_lms = float3x3(0.390405, 0.549941, 0.008926,
|
|
||||||
0.070841, 0.963172, 0.001358,
|
|
||||||
0.023108, 0.128021, 0.936245);
|
|
||||||
const float3x3 lms_to_rgb = float3x3( 2.858470, -1.628790, -0.024891,
|
|
||||||
-0.210182, 1.158200, 0.000324,
|
|
||||||
-0.041812, -0.118169, 1.068670);
|
|
||||||
|
|
||||||
float3 lms = mul(rgb_to_lms, rgb) * balance;
|
|
||||||
rgb = mul(lms_to_rgb, lms);
|
|
||||||
|
|
||||||
return float4(saturate(pow(max(rgb, 0.0), 1.0 / 2.2)), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
technique WhiteBalance <
|
|
||||||
ui_label = "White Balance";
|
|
||||||
ui_tooltip = "Corrects a picture that looks too cool, too warm or tinted.";
|
|
||||||
>
|
|
||||||
{
|
|
||||||
pass
|
|
||||||
{
|
|
||||||
VertexShader = VS_PostProcess;
|
|
||||||
PixelShader = PS_WhiteBalance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-44
@@ -195,50 +195,6 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
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")
|
|
||||||
if (APPLE)
|
|
||||||
file(WRITE ${RESHADEFX_SHIM_DIR}/malloc.h "#include <stdlib.h>\n#include <alloca.h>\n")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
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 -fno-char8_t)
|
|
||||||
else()
|
|
||||||
target_compile_options(reshadefx PRIVATE /w /Zc:char8_t-)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (WIN32)
|
|
||||||
if (NOT MSVC)
|
|
||||||
target_compile_options(reshadefx PRIVATE -include share.h)
|
|
||||||
else()
|
|
||||||
target_compile_options(reshadefx PRIVATE /FIshare.h)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (NOT TARGET reshadefx::reshadefx)
|
|
||||||
add_library(reshadefx::reshadefx ALIAS reshadefx)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Catch2
|
# Catch2
|
||||||
if (YUZU_TESTS OR DYNARMIC_TESTS)
|
if (YUZU_TESTS OR DYNARMIC_TESTS)
|
||||||
AddJsonPackage(catch2)
|
AddJsonPackage(catch2)
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.dialogs
|
package org.yuzu.yuzu_emu.dialogs
|
||||||
|
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -20,28 +18,11 @@ import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.fragments.EmulationFragment
|
import org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||||
|
|
||||||
class QuickSettings(val emulationFragment: EmulationFragment) {
|
class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||||
private val expandedShaders = mutableSetOf<Int>()
|
|
||||||
|
|
||||||
private fun forgetShaderSlot(index: Int) {
|
|
||||||
val shifted = mutableSetOf<Int>()
|
|
||||||
for (slot in expandedShaders) {
|
|
||||||
if (slot < index) {
|
|
||||||
shifted.add(slot)
|
|
||||||
}
|
|
||||||
if (slot > index) {
|
|
||||||
shifted.add(slot - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expandedShaders.clear()
|
|
||||||
expandedShaders.addAll(shifted)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun saveSettings() {
|
private fun saveSettings() {
|
||||||
if (emulationFragment.shouldUseCustom) {
|
if (emulationFragment.shouldUseCustom) {
|
||||||
NativeConfig.savePerGameConfig()
|
NativeConfig.savePerGameConfig()
|
||||||
@@ -251,428 +232,6 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
|||||||
container.addView(itemView)
|
container.addView(itemView)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addChoice(
|
|
||||||
title: String,
|
|
||||||
container: ViewGroup,
|
|
||||||
choices: List<String>,
|
|
||||||
selectedIndex: Int,
|
|
||||||
onSelected: (Int) -> Unit
|
|
||||||
) {
|
|
||||||
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
|
||||||
val itemView = inflater.inflate(R.layout.item_quick_settings_menu, container, false)
|
|
||||||
val headerView = itemView.findViewById<ViewGroup>(R.id.setting_header)
|
|
||||||
val titleView = itemView.findViewById<TextView>(R.id.setting_title)
|
|
||||||
val valueView = itemView.findViewById<TextView>(R.id.setting_value)
|
|
||||||
val expandIcon = itemView.findViewById<android.widget.ImageView>(R.id.expand_icon)
|
|
||||||
val radioGroup = itemView.findViewById<RadioGroup>(R.id.radio_group)
|
|
||||||
|
|
||||||
titleView.text = title
|
|
||||||
|
|
||||||
var current = ""
|
|
||||||
if (selectedIndex in choices.indices) {
|
|
||||||
current = choices[selectedIndex]
|
|
||||||
}
|
|
||||||
valueView.text = current
|
|
||||||
headerView.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
var isExpanded = false
|
|
||||||
choices.forEachIndexed { index, name ->
|
|
||||||
val radioButton = com.google.android.material.radiobutton.MaterialRadioButton(
|
|
||||||
emulationFragment.requireContext()
|
|
||||||
)
|
|
||||||
radioButton.text = name
|
|
||||||
radioButton.id = View.generateViewId()
|
|
||||||
radioButton.isChecked = index == selectedIndex
|
|
||||||
radioButton.setPadding(16, 8, 16, 8)
|
|
||||||
|
|
||||||
radioButton.setOnCheckedChangeListener { _, isChecked ->
|
|
||||||
if (isChecked) {
|
|
||||||
valueView.text = name
|
|
||||||
onSelected(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
radioGroup.addView(radioButton)
|
|
||||||
}
|
|
||||||
|
|
||||||
headerView.setOnClickListener {
|
|
||||||
isExpanded = !isExpanded
|
|
||||||
if (isExpanded) {
|
|
||||||
radioGroup.visibility = View.VISIBLE
|
|
||||||
expandIcon.animate().rotation(180f).setDuration(200).start()
|
|
||||||
} else {
|
|
||||||
radioGroup.visibility = View.GONE
|
|
||||||
expandIcon.animate().rotation(0f).setDuration(200).start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
container.addView(itemView)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addStepSlider(
|
|
||||||
title: String,
|
|
||||||
container: ViewGroup,
|
|
||||||
steps: Int,
|
|
||||||
selectedStep: Int,
|
|
||||||
describe: (Int) -> String,
|
|
||||||
onCommitted: (Int) -> Unit,
|
|
||||||
onChanged: (Int) -> Unit
|
|
||||||
) {
|
|
||||||
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
|
||||||
val itemView = inflater.inflate(R.layout.item_quick_settings_menu, container, false)
|
|
||||||
|
|
||||||
val sliderContainer = itemView.findViewById<ViewGroup>(R.id.slider_container)
|
|
||||||
val titleView = itemView.findViewById<TextView>(R.id.slider_title)
|
|
||||||
val valueDisplay = itemView.findViewById<TextView>(R.id.slider_value_display)
|
|
||||||
val slider = itemView.findViewById<com.google.android.material.slider.Slider>(
|
|
||||||
R.id.setting_slider
|
|
||||||
)
|
|
||||||
|
|
||||||
titleView.text = title
|
|
||||||
sliderContainer.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
slider.valueFrom = 0f
|
|
||||||
slider.valueTo = steps.toFloat()
|
|
||||||
slider.stepSize = 1f
|
|
||||||
slider.value = selectedStep.toFloat().coerceIn(0f, steps.toFloat())
|
|
||||||
valueDisplay.text = describe(slider.value.toInt())
|
|
||||||
|
|
||||||
slider.addOnChangeListener { _, value, fromUser ->
|
|
||||||
if (fromUser) {
|
|
||||||
val step = value.toInt()
|
|
||||||
onChanged(step)
|
|
||||||
valueDisplay.text = describe(step)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var pressedValue = slider.value
|
|
||||||
|
|
||||||
slider.setOnTouchListener { _, event ->
|
|
||||||
val drawer = emulationFragment.view?.findViewById<DrawerLayout>(R.id.drawer_layout)
|
|
||||||
when (event.action) {
|
|
||||||
MotionEvent.ACTION_DOWN -> {
|
|
||||||
drawer?.requestDisallowInterceptTouchEvent(true)
|
|
||||||
pressedValue = slider.value
|
|
||||||
}
|
|
||||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
|
||||||
drawer?.requestDisallowInterceptTouchEvent(false)
|
|
||||||
if (slider.value != pressedValue) {
|
|
||||||
onCommitted(slider.value.toInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
container.addView(itemView)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addShaderCard(
|
|
||||||
index: Int,
|
|
||||||
title: String,
|
|
||||||
summary: String,
|
|
||||||
container: ViewGroup,
|
|
||||||
onRemove: () -> Unit
|
|
||||||
): ViewGroup {
|
|
||||||
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
|
||||||
val itemView = inflater.inflate(R.layout.item_quick_settings_shader, container, false)
|
|
||||||
|
|
||||||
val headerView = itemView.findViewById<ViewGroup>(R.id.shader_header)
|
|
||||||
val titleView = itemView.findViewById<TextView>(R.id.shader_title)
|
|
||||||
val summaryView = itemView.findViewById<TextView>(R.id.shader_summary)
|
|
||||||
val removeView = itemView.findViewById<android.widget.ImageView>(R.id.shader_remove)
|
|
||||||
val expandIcon = itemView.findViewById<android.widget.ImageView>(R.id.shader_expand)
|
|
||||||
val bodyView = itemView.findViewById<ViewGroup>(R.id.shader_body)
|
|
||||||
|
|
||||||
titleView.text = title
|
|
||||||
if (summary.isEmpty()) {
|
|
||||||
summaryView.visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
summaryView.text = summary
|
|
||||||
}
|
|
||||||
|
|
||||||
var isExpanded = expandedShaders.contains(index)
|
|
||||||
if (isExpanded) {
|
|
||||||
bodyView.visibility = View.VISIBLE
|
|
||||||
expandIcon.rotation = 180f
|
|
||||||
}
|
|
||||||
|
|
||||||
headerView.setOnClickListener {
|
|
||||||
isExpanded = !isExpanded
|
|
||||||
if (isExpanded) {
|
|
||||||
expandedShaders.add(index)
|
|
||||||
bodyView.visibility = View.VISIBLE
|
|
||||||
expandIcon.animate().rotation(180f).setDuration(200).start()
|
|
||||||
} else {
|
|
||||||
expandedShaders.remove(index)
|
|
||||||
bodyView.visibility = View.GONE
|
|
||||||
expandIcon.animate().rotation(0f).setDuration(200).start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeView.setOnClickListener {
|
|
||||||
onRemove()
|
|
||||||
}
|
|
||||||
|
|
||||||
container.addView(itemView)
|
|
||||||
return bodyView
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addEffectPicker(
|
|
||||||
container: ViewGroup,
|
|
||||||
choices: List<String>,
|
|
||||||
hasEffects: Boolean,
|
|
||||||
onRemoveAll: () -> Unit,
|
|
||||||
onPicked: (Int) -> Unit
|
|
||||||
) {
|
|
||||||
val context = emulationFragment.requireContext()
|
|
||||||
val inflater = LayoutInflater.from(context)
|
|
||||||
val itemView = inflater.inflate(R.layout.item_quick_settings_add, container, false)
|
|
||||||
|
|
||||||
val button = itemView.findViewById<com.google.android.material.button.MaterialButton>(
|
|
||||||
R.id.add_button
|
|
||||||
)
|
|
||||||
val removeButton =
|
|
||||||
itemView.findViewById<com.google.android.material.button.MaterialButton>(
|
|
||||||
R.id.remove_all_button
|
|
||||||
)
|
|
||||||
val choiceGroup = itemView.findViewById<RadioGroup>(R.id.add_choices)
|
|
||||||
|
|
||||||
choices.forEachIndexed { index, name ->
|
|
||||||
val radioButton = com.google.android.material.radiobutton.MaterialRadioButton(context)
|
|
||||||
radioButton.text = name
|
|
||||||
radioButton.id = View.generateViewId()
|
|
||||||
radioButton.setPadding(16, 8, 16, 8)
|
|
||||||
radioButton.setOnCheckedChangeListener { _, isChecked ->
|
|
||||||
if (isChecked) {
|
|
||||||
onPicked(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
choiceGroup.addView(radioButton)
|
|
||||||
}
|
|
||||||
|
|
||||||
var closedLabel = R.string.post_processing_add
|
|
||||||
if (hasEffects) {
|
|
||||||
closedLabel = R.string.post_processing_open_list
|
|
||||||
removeButton.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
button.setText(closedLabel)
|
|
||||||
|
|
||||||
val removeBackground = MaterialColors.getColor(
|
|
||||||
removeButton,
|
|
||||||
com.google.android.material.R.attr.colorErrorContainer
|
|
||||||
)
|
|
||||||
val removeForeground = MaterialColors.getColor(
|
|
||||||
removeButton,
|
|
||||||
com.google.android.material.R.attr.colorOnErrorContainer
|
|
||||||
)
|
|
||||||
removeButton.backgroundTintList = ColorStateList.valueOf(removeBackground)
|
|
||||||
removeButton.setTextColor(removeForeground)
|
|
||||||
removeButton.iconTint = ColorStateList.valueOf(removeForeground)
|
|
||||||
removeButton.setOnClickListener {
|
|
||||||
onRemoveAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
val slide = context.resources.displayMetrics.density * 24.0f
|
|
||||||
|
|
||||||
var isOpen = false
|
|
||||||
button.setOnClickListener {
|
|
||||||
isOpen = !isOpen
|
|
||||||
if (isOpen) {
|
|
||||||
choiceGroup.alpha = 0.0f
|
|
||||||
choiceGroup.translationY = slide
|
|
||||||
choiceGroup.visibility = View.VISIBLE
|
|
||||||
choiceGroup.animate()
|
|
||||||
.alpha(1.0f)
|
|
||||||
.translationY(0.0f)
|
|
||||||
.setDuration(220)
|
|
||||||
.withEndAction {
|
|
||||||
choiceGroup.requestRectangleOnScreen(
|
|
||||||
Rect(0, 0, choiceGroup.width, choiceGroup.height),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.start()
|
|
||||||
|
|
||||||
button.setText(R.string.post_processing_close_list)
|
|
||||||
button.setIconResource(R.drawable.ic_clear)
|
|
||||||
} else {
|
|
||||||
choiceGroup.animate()
|
|
||||||
.alpha(0.0f)
|
|
||||||
.translationY(slide)
|
|
||||||
.setDuration(160)
|
|
||||||
.withEndAction {
|
|
||||||
choiceGroup.visibility = View.GONE
|
|
||||||
}
|
|
||||||
.start()
|
|
||||||
|
|
||||||
button.setText(closedLabel)
|
|
||||||
button.setIconResource(R.drawable.ic_add)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
container.addView(itemView)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addPresetBand(container: ViewGroup, name: String, summary: String) {
|
|
||||||
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
|
||||||
val itemView = inflater.inflate(R.layout.item_quick_settings_preset, container, false)
|
|
||||||
|
|
||||||
val titleView = itemView.findViewById<TextView>(R.id.preset_title)
|
|
||||||
val summaryView = itemView.findViewById<TextView>(R.id.preset_summary)
|
|
||||||
val switchView = itemView.findViewById<MaterialSwitch>(R.id.preset_switch)
|
|
||||||
|
|
||||||
titleView.text = name
|
|
||||||
if (summary.isEmpty()) {
|
|
||||||
summaryView.visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
summaryView.text = summary
|
|
||||||
}
|
|
||||||
|
|
||||||
switchView.isChecked = NativePostProcessing.isEnabled()
|
|
||||||
switchView.setOnCheckedChangeListener { _, checked ->
|
|
||||||
emulationFragment.editPostProcessing {
|
|
||||||
NativePostProcessing.setEnabled(checked)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
container.addView(itemView)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addPostProcessing(container: ViewGroup, onStructureChanged: () -> Unit) {
|
|
||||||
val usable = NativePostProcessing.catalog().filter { it.valid }
|
|
||||||
if (usable.isEmpty()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val preset = NativePostProcessing.getActivePreset()
|
|
||||||
if (preset.isNotEmpty()) {
|
|
||||||
addDivider(container)
|
|
||||||
|
|
||||||
var summary = ""
|
|
||||||
val described = NativePostProcessing.presets().firstOrNull { it.name == preset }
|
|
||||||
if (described != null) {
|
|
||||||
summary = described.description
|
|
||||||
}
|
|
||||||
if (summary.isEmpty()) {
|
|
||||||
summary =
|
|
||||||
YuzuApplication.appContext.getString(R.string.post_processing_preset_locked)
|
|
||||||
}
|
|
||||||
if (NativePostProcessing.isPresetModified()) {
|
|
||||||
summary = summary + "\n" +
|
|
||||||
YuzuApplication.appContext.getString(R.string.post_processing_preset_modified)
|
|
||||||
}
|
|
||||||
|
|
||||||
addPresetBand(container, preset, summary)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val labels = mutableListOf<String>()
|
|
||||||
val files = mutableListOf<String>()
|
|
||||||
val techniques = mutableListOf<String>()
|
|
||||||
|
|
||||||
for (effect in usable) {
|
|
||||||
for (technique in effect.techniques) {
|
|
||||||
var label = effect.label
|
|
||||||
if (effect.techniques.size > 1) {
|
|
||||||
label = effect.label + " \u00b7 " + technique
|
|
||||||
}
|
|
||||||
labels.add(label)
|
|
||||||
files.add(effect.file)
|
|
||||||
techniques.add(technique)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addDivider(container)
|
|
||||||
|
|
||||||
val chain = NativePostProcessing.chain()
|
|
||||||
chain.forEachIndexed { index, entry ->
|
|
||||||
val effect = usable.firstOrNull { it.file == entry.file }
|
|
||||||
|
|
||||||
var title = entry.file
|
|
||||||
var summary = ""
|
|
||||||
if (effect != null) {
|
|
||||||
title = effect.label
|
|
||||||
if (effect.techniques.size > 1) {
|
|
||||||
title = effect.label + " \u00b7 " + entry.technique
|
|
||||||
}
|
|
||||||
summary = effect.description
|
|
||||||
}
|
|
||||||
|
|
||||||
val body = addShaderCard(index, title, summary, container) {
|
|
||||||
emulationFragment.editPostProcessing {
|
|
||||||
NativePostProcessing.remove(index)
|
|
||||||
}
|
|
||||||
forgetShaderSlot(index)
|
|
||||||
onStructureChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effect != null) {
|
|
||||||
for (uniform in effect.uniforms) {
|
|
||||||
addUniformSliders(body, index, uniform)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addEffectPicker(
|
|
||||||
container,
|
|
||||||
labels,
|
|
||||||
chain.isNotEmpty(),
|
|
||||||
{
|
|
||||||
emulationFragment.editPostProcessing {
|
|
||||||
NativePostProcessing.clearChain()
|
|
||||||
}
|
|
||||||
expandedShaders.clear()
|
|
||||||
onStructureChanged()
|
|
||||||
}
|
|
||||||
) { picked ->
|
|
||||||
emulationFragment.editPostProcessing {
|
|
||||||
NativePostProcessing.append(files[picked], techniques[picked])
|
|
||||||
}
|
|
||||||
onStructureChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addUniformSliders(
|
|
||||||
container: ViewGroup,
|
|
||||||
index: Int,
|
|
||||||
uniform: NativePostProcessing.Uniform
|
|
||||||
) {
|
|
||||||
if (uniform.uiType == NativePostProcessing.UI_HIDDEN) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for (component in 0 until uniform.components) {
|
|
||||||
var title = uniform.label
|
|
||||||
if (uniform.components > 1) {
|
|
||||||
title = uniform.label + " [" + component + "]"
|
|
||||||
}
|
|
||||||
|
|
||||||
var value = uniform.defaultAt(component)
|
|
||||||
if (NativePostProcessing.hasValue(index, uniform.name)) {
|
|
||||||
value = NativePostProcessing.getValue(index, uniform.name, component)
|
|
||||||
}
|
|
||||||
|
|
||||||
val steps = uniform.steps
|
|
||||||
val step = Math.round((value - uniform.min) / uniform.step)
|
|
||||||
|
|
||||||
addStepSlider(
|
|
||||||
title,
|
|
||||||
container,
|
|
||||||
steps,
|
|
||||||
step,
|
|
||||||
{ position -> uniform.describe(position) },
|
|
||||||
{ emulationFragment.persistPostProcessing() }
|
|
||||||
) { position ->
|
|
||||||
NativePostProcessing.setValue(
|
|
||||||
index,
|
|
||||||
uniform.name,
|
|
||||||
component,
|
|
||||||
uniform.min + position * uniform.step
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addDivider(container: ViewGroup) {
|
fun addDivider(container: ViewGroup) {
|
||||||
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
||||||
val dividerView = inflater.inflate(R.layout.item_quick_settings_divider, container, false)
|
val dividerView = inflater.inflate(R.layout.item_quick_settings_divider, container, false)
|
||||||
|
|||||||
-36
@@ -1,36 +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
|
|
||||||
|
|
||||||
class FxPresetNameSetting(private val onNamed: (String) -> Unit) : AbstractStringSetting {
|
|
||||||
override val key: String
|
|
||||||
get() = "fx_preset_name"
|
|
||||||
|
|
||||||
override val defaultValue: Any
|
|
||||||
get() = ""
|
|
||||||
|
|
||||||
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(_) {}
|
|
||||||
|
|
||||||
override fun getString(needsGlobal: Boolean): String = ""
|
|
||||||
|
|
||||||
override fun setString(value: String) = onNamed(value)
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String = ""
|
|
||||||
|
|
||||||
override fun reset() {}
|
|
||||||
}
|
|
||||||
-67
@@ -1,67 +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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,6 @@ object Settings {
|
|||||||
SECTION_SYSTEM(R.string.preferences_system),
|
SECTION_SYSTEM(R.string.preferences_system),
|
||||||
SECTION_RENDERER(R.string.preferences_graphics),
|
SECTION_RENDERER(R.string.preferences_graphics),
|
||||||
SECTION_FRAME_GEN(R.string.frame_gen),
|
SECTION_FRAME_GEN(R.string.frame_gen),
|
||||||
SECTION_POST_PROCESSING(R.string.post_processing),
|
|
||||||
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
|
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
|
||||||
SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
|
SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
|
||||||
SECTION_SOC_OVERLAY(R.string.soc_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"),
|
DEVICE_NAME("device_name"),
|
||||||
LOG_FILTER("log_filter"),
|
LOG_FILTER("log_filter"),
|
||||||
PROGRAM_ARGS("program_args"),
|
PROGRAM_ARGS("program_args"),
|
||||||
POST_SHADER_CHAIN("post_shader_chain"),
|
|
||||||
|
|
||||||
WEB_TOKEN("eden_token"),
|
WEB_TOKEN("eden_token"),
|
||||||
WEB_USERNAME("eden_username")
|
WEB_USERNAME("eden_username")
|
||||||
|
|||||||
-13
@@ -1,13 +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.view
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
|
|
||||||
class FxButtonSetting(
|
|
||||||
@StringRes titleId: Int,
|
|
||||||
val onClick: () -> Unit
|
|
||||||
) : SettingsItem(emptySetting, titleId, "", 0, "") {
|
|
||||||
override val type = TYPE_FX_BUTTON
|
|
||||||
}
|
|
||||||
-14
@@ -1,14 +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.view
|
|
||||||
|
|
||||||
class FxPresetSetting(
|
|
||||||
titleString: String,
|
|
||||||
descriptionString: String = "",
|
|
||||||
val deletable: Boolean = false,
|
|
||||||
val onApply: () -> Unit,
|
|
||||||
val onDelete: () -> Unit = {}
|
|
||||||
) : SettingsItem(emptySetting, 0, titleString, 0, descriptionString) {
|
|
||||||
override val type = TYPE_FX_PRESET
|
|
||||||
}
|
|
||||||
-19
@@ -1,19 +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.view
|
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
|
||||||
|
|
||||||
class FxShaderCardSetting(
|
|
||||||
titleString: String,
|
|
||||||
descriptionString: String,
|
|
||||||
val index: Int,
|
|
||||||
val expanded: Boolean,
|
|
||||||
val uniforms: List<NativePostProcessing.Uniform>,
|
|
||||||
val onToggle: () -> Unit,
|
|
||||||
val onRemove: () -> Unit,
|
|
||||||
val onReset: () -> Unit
|
|
||||||
) : SettingsItem(emptySetting, 0, titleString, 0, descriptionString) {
|
|
||||||
override val type = TYPE_FX_SHADER
|
|
||||||
}
|
|
||||||
-19
@@ -1,19 +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.view
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
|
|
||||||
class FxToolbarSetting(
|
|
||||||
@StringRes val addLabelId: Int,
|
|
||||||
val listOpen: Boolean,
|
|
||||||
val presetLabel: String,
|
|
||||||
val hasEffects: Boolean,
|
|
||||||
val createPreset: StringInputSetting,
|
|
||||||
val onAdd: () -> Unit,
|
|
||||||
val onPresets: () -> Unit,
|
|
||||||
val onRemoveAll: () -> Unit
|
|
||||||
) : SettingsItem(emptySetting, 0, "", 0, "") {
|
|
||||||
override val type = TYPE_FX_TOOLBAR
|
|
||||||
}
|
|
||||||
-4
@@ -144,10 +144,6 @@ abstract class SettingsItem(
|
|||||||
const val TYPE_LAUNCHABLE = 13
|
const val TYPE_LAUNCHABLE = 13
|
||||||
const val TYPE_PATH = 14
|
const val TYPE_PATH = 14
|
||||||
const val TYPE_GPU_UNSWIZZLE = 15
|
const val TYPE_GPU_UNSWIZZLE = 15
|
||||||
const val TYPE_FX_TOOLBAR = 16
|
|
||||||
const val TYPE_FX_PRESET = 17
|
|
||||||
const val TYPE_FX_SHADER = 18
|
|
||||||
const val TYPE_FX_BUTTON = 19
|
|
||||||
|
|
||||||
const val FASTMEM_COMBINED = "fastmem_combined"
|
const val FASTMEM_COMBINED = "fastmem_combined"
|
||||||
const val GPU_UNSWIZZLE_COMBINED = "gpu_unswizzle_combined"
|
const val GPU_UNSWIZZLE_COMBINED = "gpu_unswizzle_combined"
|
||||||
|
|||||||
-32
@@ -23,10 +23,6 @@ import com.google.android.material.timepicker.TimeFormat
|
|||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.SettingsNavigationDirections
|
import org.yuzu.yuzu_emu.SettingsNavigationDirections
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxButtonBinding
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxPresetBinding
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxShaderBinding
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxToolbarBinding
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingInputBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingInputBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
|
||||||
@@ -110,34 +106,6 @@ class SettingsAdapter(
|
|||||||
GpuUnswizzleViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
GpuUnswizzleViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem.TYPE_FX_TOOLBAR -> {
|
|
||||||
FxToolbarViewHolder(
|
|
||||||
ListItemSettingFxToolbarBinding.inflate(inflater, parent, false),
|
|
||||||
this
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsItem.TYPE_FX_PRESET -> {
|
|
||||||
FxPresetViewHolder(
|
|
||||||
ListItemSettingFxPresetBinding.inflate(inflater, parent, false),
|
|
||||||
this
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsItem.TYPE_FX_SHADER -> {
|
|
||||||
FxShaderCardViewHolder(
|
|
||||||
ListItemSettingFxShaderBinding.inflate(inflater, parent, false),
|
|
||||||
this
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsItem.TYPE_FX_BUTTON -> {
|
|
||||||
FxButtonViewHolder(
|
|
||||||
ListItemSettingFxButtonBinding.inflate(inflater, parent, false),
|
|
||||||
this
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
|
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
|
||||||
}
|
}
|
||||||
|
|||||||
-214
@@ -18,7 +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.AbstractBooleanSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
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.BooleanSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.FxPresetNameSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
|
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.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||||
@@ -31,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.InputHandler
|
||||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
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.DirectoryInitialization
|
||||||
import org.yuzu.yuzu_emu.utils.FullscreenHelper
|
import org.yuzu.yuzu_emu.utils.FullscreenHelper
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
@@ -46,14 +44,6 @@ class SettingsFragmentPresenter(
|
|||||||
) {
|
) {
|
||||||
private var settingsList = ArrayList<SettingsItem>()
|
private var settingsList = ArrayList<SettingsItem>()
|
||||||
|
|
||||||
private val expandedShaderSlots = mutableSetOf<Int>()
|
|
||||||
|
|
||||||
private var shaderPickerOpen = false
|
|
||||||
|
|
||||||
private var presetPickerOpen = false
|
|
||||||
|
|
||||||
private var postProcessingSynced = false
|
|
||||||
|
|
||||||
private val context get() = YuzuApplication.appContext
|
private val context get() = YuzuApplication.appContext
|
||||||
|
|
||||||
// Extension for altering settings list based on each setting's properties
|
// Extension for altering settings list based on each setting's properties
|
||||||
@@ -173,7 +163,6 @@ class SettingsFragmentPresenter(
|
|||||||
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||||
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||||
MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl)
|
MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl)
|
||||||
MenuTag.SECTION_POST_PROCESSING -> addPostProcessingSettings(sl)
|
|
||||||
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
|
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
|
||||||
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
|
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
|
||||||
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
|
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
|
||||||
@@ -201,209 +190,6 @@ class SettingsFragmentPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addPostProcessingSettings(sl: ArrayList<SettingsItem>) {
|
|
||||||
if (!postProcessingSynced) {
|
|
||||||
postProcessingSynced = true
|
|
||||||
NativePostProcessing.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
val usable = NativePostProcessing.catalog().filter { it.valid }
|
|
||||||
|
|
||||||
sl.apply {
|
|
||||||
if (usable.isEmpty()) {
|
|
||||||
add(
|
|
||||||
RunnableSetting(
|
|
||||||
titleId = R.string.post_processing_empty,
|
|
||||||
descriptionString = NativePostProcessing.getShaderDirectory(),
|
|
||||||
isRunnable = false
|
|
||||||
) {}
|
|
||||||
)
|
|
||||||
return@apply
|
|
||||||
}
|
|
||||||
|
|
||||||
val labels = mutableListOf<String>()
|
|
||||||
val summaries = 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.label)
|
|
||||||
} else {
|
|
||||||
labels.add(effect.label + " \u00b7 " + technique)
|
|
||||||
}
|
|
||||||
summaries.add(effect.description)
|
|
||||||
files.add(effect.file)
|
|
||||||
techniques.add(technique)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val chain = NativePostProcessing.chain()
|
|
||||||
val active = NativePostProcessing.getActivePreset()
|
|
||||||
|
|
||||||
var addLabel = R.string.post_processing_add
|
|
||||||
if (chain.isNotEmpty()) {
|
|
||||||
addLabel = R.string.post_processing_open_list
|
|
||||||
}
|
|
||||||
if (shaderPickerOpen) {
|
|
||||||
addLabel = R.string.post_processing_close_list
|
|
||||||
}
|
|
||||||
|
|
||||||
var presetLabel = context.getString(R.string.post_processing_presets)
|
|
||||||
if (active.isNotEmpty()) {
|
|
||||||
presetLabel = active
|
|
||||||
}
|
|
||||||
|
|
||||||
val createPreset = StringInputSetting(
|
|
||||||
setting = FxPresetNameSetting { name ->
|
|
||||||
NativePostProcessing.savePreset(name, "")
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
},
|
|
||||||
titleId = R.string.post_processing_preset_new,
|
|
||||||
descriptionId = R.string.post_processing_preset_new_description,
|
|
||||||
validator = { it != null && it.isNotBlank() && !it.contains('=') },
|
|
||||||
errorId = R.string.post_processing_preset_name_invalid
|
|
||||||
)
|
|
||||||
|
|
||||||
add(
|
|
||||||
FxToolbarSetting(
|
|
||||||
addLabelId = addLabel,
|
|
||||||
listOpen = shaderPickerOpen,
|
|
||||||
presetLabel = presetLabel,
|
|
||||||
hasEffects = chain.isNotEmpty(),
|
|
||||||
createPreset = createPreset,
|
|
||||||
onAdd = {
|
|
||||||
shaderPickerOpen = !shaderPickerOpen
|
|
||||||
presetPickerOpen = false
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
},
|
|
||||||
onPresets = {
|
|
||||||
presetPickerOpen = !presetPickerOpen
|
|
||||||
shaderPickerOpen = false
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
},
|
|
||||||
onRemoveAll = {
|
|
||||||
NativePostProcessing.clearChain()
|
|
||||||
NativePostProcessing.clearPreset()
|
|
||||||
NativePostProcessing.store()
|
|
||||||
expandedShaderSlots.clear()
|
|
||||||
shaderPickerOpen = false
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (shaderPickerOpen) {
|
|
||||||
for (choice in labels.indices) {
|
|
||||||
add(
|
|
||||||
RunnableSetting(
|
|
||||||
titleString = labels[choice],
|
|
||||||
descriptionString = summaries[choice],
|
|
||||||
isRunnable = true
|
|
||||||
) {
|
|
||||||
NativePostProcessing.append(files[choice], techniques[choice])
|
|
||||||
NativePostProcessing.store()
|
|
||||||
shaderPickerOpen = false
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (presetPickerOpen) {
|
|
||||||
add(
|
|
||||||
FxPresetSetting(
|
|
||||||
titleString = context.getString(R.string.post_processing_preset_none),
|
|
||||||
onApply = {
|
|
||||||
NativePostProcessing.clearPreset()
|
|
||||||
presetPickerOpen = false
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for (preset in NativePostProcessing.presets()) {
|
|
||||||
add(
|
|
||||||
FxPresetSetting(
|
|
||||||
titleString = preset.name,
|
|
||||||
descriptionString = preset.description,
|
|
||||||
deletable = !preset.bundled,
|
|
||||||
onApply = {
|
|
||||||
NativePostProcessing.applyPreset(preset.name)
|
|
||||||
NativePostProcessing.store()
|
|
||||||
presetPickerOpen = false
|
|
||||||
expandedShaderSlots.clear()
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
},
|
|
||||||
onDelete = {
|
|
||||||
NativePostProcessing.deletePreset(preset.name)
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (active.isNotEmpty()) {
|
|
||||||
add(
|
|
||||||
FxButtonSetting(titleId = R.string.post_processing_preset_reset) {
|
|
||||||
NativePostProcessing.applyPreset(active)
|
|
||||||
NativePostProcessing.store()
|
|
||||||
expandedShaderSlots.clear()
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (index in chain.indices) {
|
|
||||||
val entry = chain[index]
|
|
||||||
val effect = usable.firstOrNull { it.file == entry.file }
|
|
||||||
|
|
||||||
var header = entry.file
|
|
||||||
var summary = ""
|
|
||||||
var uniforms = emptyList<NativePostProcessing.Uniform>()
|
|
||||||
if (effect != null) {
|
|
||||||
header = effect.label
|
|
||||||
if (effect.techniques.size > 1) {
|
|
||||||
header = effect.label + " \u00b7 " + entry.technique
|
|
||||||
}
|
|
||||||
summary = effect.description
|
|
||||||
uniforms = effect.uniforms
|
|
||||||
}
|
|
||||||
|
|
||||||
val isOpen = expandedShaderSlots.contains(index)
|
|
||||||
|
|
||||||
add(
|
|
||||||
FxShaderCardSetting(
|
|
||||||
titleString = header,
|
|
||||||
descriptionString = summary,
|
|
||||||
index = index,
|
|
||||||
expanded = isOpen,
|
|
||||||
uniforms = uniforms,
|
|
||||||
onToggle = {
|
|
||||||
if (isOpen) {
|
|
||||||
expandedShaderSlots.remove(index)
|
|
||||||
} else {
|
|
||||||
expandedShaderSlots.add(index)
|
|
||||||
}
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
},
|
|
||||||
onRemove = {
|
|
||||||
NativePostProcessing.remove(index)
|
|
||||||
NativePostProcessing.store()
|
|
||||||
expandedShaderSlots.clear()
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
},
|
|
||||||
onReset = {
|
|
||||||
NativePostProcessing.resetValues(index)
|
|
||||||
NativePostProcessing.store()
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(
|
add(
|
||||||
|
|||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxButtonBinding
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.FxButtonSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
|
||||||
|
|
||||||
class FxButtonViewHolder(
|
|
||||||
val binding: ListItemSettingFxButtonBinding,
|
|
||||||
adapter: SettingsAdapter
|
|
||||||
) : SettingViewHolder(binding.root, adapter) {
|
|
||||||
private lateinit var setting: FxButtonSetting
|
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
|
||||||
setting = item as FxButtonSetting
|
|
||||||
|
|
||||||
binding.fxButton.text = item.title
|
|
||||||
binding.fxButton.setOnClickListener { setting.onClick.invoke() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(clicked: View) {}
|
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean = true
|
|
||||||
}
|
|
||||||
-35
@@ -1,35 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxPresetBinding
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.FxPresetSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
|
|
||||||
class FxPresetViewHolder(
|
|
||||||
val binding: ListItemSettingFxPresetBinding,
|
|
||||||
adapter: SettingsAdapter
|
|
||||||
) : SettingViewHolder(binding.root, adapter) {
|
|
||||||
private lateinit var setting: FxPresetSetting
|
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
|
||||||
setting = item as FxPresetSetting
|
|
||||||
|
|
||||||
binding.presetName.text = item.title
|
|
||||||
binding.presetDescription.text = item.description
|
|
||||||
binding.presetDescription.setVisible(item.description.isNotEmpty())
|
|
||||||
|
|
||||||
binding.presetRow.setOnClickListener { setting.onApply.invoke() }
|
|
||||||
|
|
||||||
binding.presetDelete.setVisible(setting.deletable)
|
|
||||||
binding.presetDelete.setOnClickListener { setting.onDelete.invoke() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(clicked: View) {}
|
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean = true
|
|
||||||
}
|
|
||||||
-100
@@ -1,100 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ItemSettingFxActionsBinding
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ItemSettingFxSliderBinding
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxShaderBinding
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.FxUniformSliderSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.FxShaderCardSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
|
||||||
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
|
|
||||||
class FxShaderCardViewHolder(
|
|
||||||
val binding: ListItemSettingFxShaderBinding,
|
|
||||||
adapter: SettingsAdapter
|
|
||||||
) : SettingViewHolder(binding.root, adapter) {
|
|
||||||
private lateinit var setting: FxShaderCardSetting
|
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
|
||||||
setting = item as FxShaderCardSetting
|
|
||||||
|
|
||||||
binding.shaderTitle.text = item.title
|
|
||||||
binding.shaderSummary.text = item.description
|
|
||||||
binding.shaderSummary.setVisible(item.description.isNotEmpty())
|
|
||||||
|
|
||||||
var rotation = 0f
|
|
||||||
if (setting.expanded) {
|
|
||||||
rotation = 180f
|
|
||||||
}
|
|
||||||
binding.shaderExpand.rotation = rotation
|
|
||||||
|
|
||||||
binding.shaderHeader.setOnClickListener { setting.onToggle.invoke() }
|
|
||||||
binding.shaderRemove.setOnClickListener { setting.onRemove.invoke() }
|
|
||||||
|
|
||||||
binding.shaderBody.removeAllViews()
|
|
||||||
binding.shaderBody.setVisible(setting.expanded)
|
|
||||||
if (!setting.expanded) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val inflater = LayoutInflater.from(binding.root.context)
|
|
||||||
for (uniform in setting.uniforms) {
|
|
||||||
if (uniform.uiType == NativePostProcessing.UI_HIDDEN) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for (component in 0 until uniform.components) {
|
|
||||||
addSlider(inflater, uniform, component)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addActions(inflater)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addSlider(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
uniform: NativePostProcessing.Uniform,
|
|
||||||
component: Int
|
|
||||||
) {
|
|
||||||
val row = ItemSettingFxSliderBinding.inflate(inflater, binding.shaderBody, false)
|
|
||||||
val value = FxUniformSliderSetting(setting.index, uniform, component)
|
|
||||||
|
|
||||||
var title = uniform.label
|
|
||||||
if (uniform.components > 1) {
|
|
||||||
title = uniform.label + " [" + component + "]"
|
|
||||||
}
|
|
||||||
row.fxSliderTitle.text = title
|
|
||||||
|
|
||||||
val steps = uniform.steps.toFloat()
|
|
||||||
row.fxSlider.valueFrom = 0f
|
|
||||||
row.fxSlider.valueTo = steps
|
|
||||||
row.fxSlider.stepSize = 1f
|
|
||||||
row.fxSlider.value = value.getInt(false).toFloat().coerceIn(0f, steps)
|
|
||||||
row.fxSliderValue.text = uniform.describe(row.fxSlider.value.toInt())
|
|
||||||
|
|
||||||
row.fxSlider.addOnChangeListener { _, position, fromUser ->
|
|
||||||
if (fromUser) {
|
|
||||||
value.setInt(position.toInt())
|
|
||||||
row.fxSliderValue.text = uniform.describe(position.toInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.shaderBody.addView(row.root)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addActions(inflater: LayoutInflater) {
|
|
||||||
val row = ItemSettingFxActionsBinding.inflate(inflater, binding.shaderBody, false)
|
|
||||||
|
|
||||||
row.fxReset.setOnClickListener { setting.onReset.invoke() }
|
|
||||||
|
|
||||||
binding.shaderBody.addView(row.root)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(clicked: View) {}
|
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean = true
|
|
||||||
}
|
|
||||||
-57
@@ -1,57 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
|
||||||
|
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.view.View
|
|
||||||
import com.google.android.material.color.MaterialColors
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxToolbarBinding
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.FxToolbarSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
|
||||||
|
|
||||||
class FxToolbarViewHolder(
|
|
||||||
val binding: ListItemSettingFxToolbarBinding,
|
|
||||||
adapter: SettingsAdapter
|
|
||||||
) : SettingViewHolder(binding.root, adapter) {
|
|
||||||
private lateinit var setting: FxToolbarSetting
|
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
|
||||||
setting = item as FxToolbarSetting
|
|
||||||
|
|
||||||
binding.fxAdd.setText(setting.addLabelId)
|
|
||||||
var addIcon = R.drawable.ic_add
|
|
||||||
if (setting.listOpen) {
|
|
||||||
addIcon = R.drawable.ic_clear
|
|
||||||
}
|
|
||||||
binding.fxAdd.setIconResource(addIcon)
|
|
||||||
binding.fxAdd.setOnClickListener { setting.onAdd.invoke() }
|
|
||||||
|
|
||||||
binding.fxPresets.text = setting.presetLabel
|
|
||||||
binding.fxPresets.setOnClickListener { setting.onPresets.invoke() }
|
|
||||||
|
|
||||||
binding.fxCreatePreset.isEnabled = setting.hasEffects
|
|
||||||
binding.fxCreatePreset.setOnClickListener {
|
|
||||||
adapter.onStringInputClick(setting.createPreset, bindingAdapterPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
val removeBackground = MaterialColors.getColor(
|
|
||||||
binding.fxRemoveAll,
|
|
||||||
com.google.android.material.R.attr.colorErrorContainer
|
|
||||||
)
|
|
||||||
val removeForeground = MaterialColors.getColor(
|
|
||||||
binding.fxRemoveAll,
|
|
||||||
com.google.android.material.R.attr.colorOnErrorContainer
|
|
||||||
)
|
|
||||||
binding.fxRemoveAll.backgroundTintList = ColorStateList.valueOf(removeBackground)
|
|
||||||
binding.fxRemoveAll.iconTint = ColorStateList.valueOf(removeForeground)
|
|
||||||
binding.fxRemoveAll.isEnabled = setting.hasEffects
|
|
||||||
binding.fxRemoveAll.setOnClickListener { setting.onRemoveAll.invoke() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(clicked: View) {}
|
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean = true
|
|
||||||
}
|
|
||||||
@@ -94,7 +94,6 @@ import org.yuzu.yuzu_emu.utils.InputHandler
|
|||||||
import org.yuzu.yuzu_emu.utils.Log
|
import org.yuzu.yuzu_emu.utils.Log
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
|
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
|
||||||
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
import org.yuzu.yuzu_emu.utils.collect
|
||||||
@@ -884,8 +883,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
if (shouldUseCustom) {
|
if (shouldUseCustom) {
|
||||||
SettingsFile.loadCustomConfig(game!!)
|
SettingsFile.loadCustomConfig(game!!)
|
||||||
}
|
}
|
||||||
refreshPostProcessing()
|
|
||||||
addQuickSettings()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1090,34 +1087,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun withPerGameConfig(create: Boolean, action: () -> Unit) {
|
|
||||||
val target = game
|
|
||||||
var owned = false
|
|
||||||
if (target != null && !NativeConfig.isPerGameConfigLoaded()) {
|
|
||||||
if (create || SettingsFile.getCustomSettingsFile(target).exists()) {
|
|
||||||
SettingsFile.loadCustomConfig(target)
|
|
||||||
owned = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
action()
|
|
||||||
if (owned) {
|
|
||||||
NativeConfig.unloadPerGameConfig()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun refreshPostProcessing() = withPerGameConfig(false) {
|
|
||||||
NativePostProcessing.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun persistPostProcessing() = withPerGameConfig(true) {
|
|
||||||
NativePostProcessing.persist()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun editPostProcessing(action: () -> Unit) = withPerGameConfig(true) {
|
|
||||||
action()
|
|
||||||
NativePostProcessing.persist()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addQuickSettings() {
|
private fun addQuickSettings() {
|
||||||
binding.quickSettingsSheet.apply {
|
binding.quickSettingsSheet.apply {
|
||||||
val container = binding.quickSettingsSheet.findViewById<ViewGroup>(R.id.quick_settings_container)
|
val container = binding.quickSettingsSheet.findViewById<ViewGroup>(R.id.quick_settings_container)
|
||||||
@@ -1225,10 +1194,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
R.array.rendererAntiAliasingNames,
|
R.array.rendererAntiAliasingNames,
|
||||||
R.array.rendererAntiAliasingValues
|
R.array.rendererAntiAliasingValues
|
||||||
)
|
)
|
||||||
|
|
||||||
quickSettings.addPostProcessing(container) {
|
|
||||||
addQuickSettings()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()) {
|
if (GpuDriverHelper.isAdrenoGpu()) {
|
||||||
add(
|
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(
|
add(
|
||||||
HomeSetting(
|
HomeSetting(
|
||||||
R.string.lossless_scaling,
|
R.string.lossless_scaling,
|
||||||
|
|||||||
@@ -1,242 +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 clearChain()
|
|
||||||
|
|
||||||
external fun getPresetsJson(): String
|
|
||||||
|
|
||||||
external fun getActivePreset(): String
|
|
||||||
|
|
||||||
external fun isPresetModified(): Boolean
|
|
||||||
|
|
||||||
external fun applyPreset(name: String): Boolean
|
|
||||||
|
|
||||||
external fun savePreset(name: String, description: String): Boolean
|
|
||||||
|
|
||||||
external fun deletePreset(name: String): Boolean
|
|
||||||
|
|
||||||
external fun clearPreset()
|
|
||||||
|
|
||||||
external fun isEnabled(): Boolean
|
|
||||||
|
|
||||||
external fun setEnabled(enabled: Boolean)
|
|
||||||
|
|
||||||
external fun getPresetDirectory(): String
|
|
||||||
|
|
||||||
fun persist() {
|
|
||||||
val perGame = NativeConfig.isPerGameConfigLoaded()
|
|
||||||
store()
|
|
||||||
if (perGame) {
|
|
||||||
NativeConfig.savePerGameConfig()
|
|
||||||
} else {
|
|
||||||
NativeConfig.saveGlobalConfig()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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 describe(position: Int): String {
|
|
||||||
val value = min + position * step
|
|
||||||
if (kind == NativePostProcessing.KIND_FLOAT) {
|
|
||||||
return String.format("%.3f", value)
|
|
||||||
}
|
|
||||||
return Math.round(value).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun defaultAt(component: Int): Float {
|
|
||||||
if (component < defaults.size) {
|
|
||||||
return defaults[component]
|
|
||||||
}
|
|
||||||
return 0f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Effect(
|
|
||||||
val file: String,
|
|
||||||
val name: String,
|
|
||||||
val label: String,
|
|
||||||
val description: 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)
|
|
||||||
|
|
||||||
data class Preset(
|
|
||||||
val name: String,
|
|
||||||
val description: String,
|
|
||||||
val bundled: Boolean
|
|
||||||
)
|
|
||||||
|
|
||||||
fun presets(): List<Preset> {
|
|
||||||
val out = mutableListOf<Preset>()
|
|
||||||
val array = JSONArray(getPresetsJson())
|
|
||||||
for (i in 0 until array.length()) {
|
|
||||||
val obj = array.getJSONObject(i)
|
|
||||||
out.add(
|
|
||||||
Preset(
|
|
||||||
name = obj.optString("name"),
|
|
||||||
description = obj.optString("description"),
|
|
||||||
bundled = obj.optBoolean("bundled")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
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"),
|
|
||||||
label = obj.optString("label"),
|
|
||||||
description = obj.optString("description"),
|
|
||||||
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.cpp
|
||||||
android_config.h
|
android_config.h
|
||||||
native_input.cpp
|
native_input.cpp
|
||||||
native_post_processing.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
||||||
|
|||||||
@@ -15,22 +15,10 @@
|
|||||||
#include "frontend_common/config.h"
|
#include "frontend_common/config.h"
|
||||||
#include "frontend_common/settings_generator.h"
|
#include "frontend_common/settings_generator.h"
|
||||||
#include "native.h"
|
#include "native.h"
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
#include "video_core/post_processing/fx_chain.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::unique_ptr<AndroidConfig> global_config;
|
std::unique_ptr<AndroidConfig> global_config;
|
||||||
std::unique_ptr<AndroidConfig> per_game_config;
|
std::unique_ptr<AndroidConfig> per_game_config;
|
||||||
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
static void ResetFxChainToGlobal() {
|
|
||||||
VideoCore::UseGlobalFxSettings();
|
|
||||||
VideoCore::FxChain::Instance().LoadFromSettings();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
static void ResetFxChainToGlobal() {}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
|
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
|
||||||
auto key = Common::Android::GetJString(env, jkey);
|
auto key = Common::Android::GetJString(env, jkey);
|
||||||
@@ -51,7 +39,6 @@ extern "C" {
|
|||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
global_config = std::make_unique<AndroidConfig>();
|
global_config = std::make_unique<AndroidConfig>();
|
||||||
FrontendCommon::GenerateSettings();
|
FrontendCommon::GenerateSettings();
|
||||||
ResetFxChainToGlobal();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
@@ -60,7 +47,6 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env,
|
|||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadGlobalConfig(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
global_config->AndroidConfig::ReloadAllValues();
|
global_config->AndroidConfig::ReloadAllValues();
|
||||||
ResetFxChainToGlobal();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
|
|||||||
@@ -1,350 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
#include "common/android/android_common.h"
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
#include "android_config.h"
|
|
||||||
#include "video_core/post_processing/fx_chain.h"
|
|
||||||
#include "common/settings.h"
|
|
||||||
#include "video_core/post_processing/fx_effect.h"
|
|
||||||
#include "video_core/post_processing/fx_preset.h"
|
|
||||||
|
|
||||||
extern std::unique_ptr<AndroidConfig> per_game_config;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
bool EditingPerGame() {
|
|
||||||
return per_game_config != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BeginFxEdit() {
|
|
||||||
if (!EditingPerGame()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
VideoCore::UsePerGameFxSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<f32, 4> DefaultValueOf(size_t index, const std::string& uniform) {
|
|
||||||
const auto entries = VideoCore::FxChain::Instance().Entries();
|
|
||||||
if (index >= entries.size()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const VideoCore::FxEffectDesc* effect = VideoCore::FindFxEffect(entries[index].file);
|
|
||||||
if (effect == nullptr) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const VideoCore::FxUniformDesc* desc = VideoCore::FindFxUniform(*effect, uniform);
|
|
||||||
if (desc == nullptr) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return desc->default_value;
|
|
||||||
}
|
|
||||||
#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
|
|
||||||
VideoCore::FxChain::Instance().DropUnknownEntries();
|
|
||||||
|
|
||||||
for (const auto& effect : VideoCore::GetFxCatalog()) {
|
|
||||||
nlohmann::json entry;
|
|
||||||
entry["file"] = effect.file;
|
|
||||||
entry["name"] = effect.name;
|
|
||||||
entry["label"] = effect.label;
|
|
||||||
entry["description"] = effect.description;
|
|
||||||
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();
|
|
||||||
const auto slot = static_cast<size_t>(index);
|
|
||||||
|
|
||||||
auto current = chain.GetValue(slot, uniform);
|
|
||||||
if (!chain.HasValue(slot, uniform)) {
|
|
||||||
current = DefaultValueOf(slot, uniform);
|
|
||||||
}
|
|
||||||
|
|
||||||
current[static_cast<size_t>(component)] = value;
|
|
||||||
chain.SetValue(slot, uniform, current);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_store(JNIEnv* env, jobject obj) {
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
BeginFxEdit();
|
|
||||||
VideoCore::FxChain::Instance().StoreToSettings();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_reload(JNIEnv* env, jobject obj) {
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
if (!EditingPerGame()) {
|
|
||||||
VideoCore::UseGlobalFxSettings();
|
|
||||||
}
|
|
||||||
VideoCore::FxChain::Instance().LoadFromSettings();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_clearChain(JNIEnv* env, jobject obj) {
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
VideoCore::FxChain::Instance().Clear();
|
|
||||||
#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
|
|
||||||
}
|
|
||||||
|
|
||||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getPresetsJson(JNIEnv* env,
|
|
||||||
jobject obj) {
|
|
||||||
nlohmann::json out = nlohmann::json::array();
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
VideoCore::ReloadFxPresetCatalog();
|
|
||||||
|
|
||||||
for (const auto& preset : VideoCore::GetFxPresetCatalog()) {
|
|
||||||
nlohmann::json entry;
|
|
||||||
entry["name"] = preset.name;
|
|
||||||
entry["description"] = preset.description;
|
|
||||||
entry["bundled"] = preset.bundled;
|
|
||||||
out.push_back(entry);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return Common::Android::ToJString(env, out.dump());
|
|
||||||
}
|
|
||||||
|
|
||||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getActivePreset(JNIEnv* env,
|
|
||||||
jobject obj) {
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
return Common::Android::ToJString(env, VideoCore::GetActiveFxPreset());
|
|
||||||
#else
|
|
||||||
return Common::Android::ToJString(env, "");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_isPresetModified(JNIEnv* env,
|
|
||||||
jobject obj) {
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
return static_cast<jboolean>(VideoCore::IsActiveFxPresetModified());
|
|
||||||
#else
|
|
||||||
return static_cast<jboolean>(false);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_applyPreset(JNIEnv* env, jobject obj,
|
|
||||||
jstring jname) {
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
BeginFxEdit();
|
|
||||||
return static_cast<jboolean>(
|
|
||||||
VideoCore::ApplyFxPreset(Common::Android::GetJString(env, jname)));
|
|
||||||
#else
|
|
||||||
return static_cast<jboolean>(false);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_savePreset(JNIEnv* env, jobject obj,
|
|
||||||
jstring jname,
|
|
||||||
jstring jdescription) {
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
BeginFxEdit();
|
|
||||||
return static_cast<jboolean>(
|
|
||||||
VideoCore::SaveFxPreset(Common::Android::GetJString(env, jname),
|
|
||||||
Common::Android::GetJString(env, jdescription)));
|
|
||||||
#else
|
|
||||||
return static_cast<jboolean>(false);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_deletePreset(JNIEnv* env, jobject obj,
|
|
||||||
jstring jname) {
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
BeginFxEdit();
|
|
||||||
return static_cast<jboolean>(
|
|
||||||
VideoCore::DeleteFxPreset(Common::Android::GetJString(env, jname)));
|
|
||||||
#else
|
|
||||||
return static_cast<jboolean>(false);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_clearPreset(JNIEnv* env, jobject obj) {
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
BeginFxEdit();
|
|
||||||
VideoCore::SetActiveFxPreset(std::string_view());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_isEnabled(JNIEnv* env, jobject obj) {
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
return static_cast<jboolean>(Settings::values.post_shader_enabled.GetValue());
|
|
||||||
#else
|
|
||||||
return static_cast<jboolean>(false);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_setEnabled(JNIEnv* env, jobject obj,
|
|
||||||
jboolean enabled) {
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
BeginFxEdit();
|
|
||||||
Settings::values.post_shader_enabled.SetValue(enabled != JNI_FALSE);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getPresetDirectory(JNIEnv* env,
|
|
||||||
jobject obj) {
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
return Common::Android::ToJString(env, VideoCore::GetFxPresetDirectory().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>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
|
|
||||||
<solid android:color="?attr/colorSurfaceVariant" />
|
|
||||||
|
|
||||||
<corners android:radius="16dp" />
|
|
||||||
|
|
||||||
<stroke
|
|
||||||
android:width="1dp"
|
|
||||||
android:color="?attr/colorOutline" />
|
|
||||||
|
|
||||||
</shape>
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:baselineAligned="false"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/add_button"
|
|
||||||
style="@style/Widget.Material3.Button.TonalButton"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:minHeight="48dp"
|
|
||||||
android:paddingStart="12dp"
|
|
||||||
android:paddingEnd="12dp"
|
|
||||||
android:text="@string/post_processing_add"
|
|
||||||
app:icon="@drawable/ic_add"
|
|
||||||
app:iconPadding="6dp" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/remove_all_button"
|
|
||||||
style="@style/Widget.Material3.Button.TonalButton"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:minHeight="48dp"
|
|
||||||
android:paddingStart="12dp"
|
|
||||||
android:paddingEnd="12dp"
|
|
||||||
android:text="@string/post_processing_remove_all"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:icon="@drawable/ic_delete"
|
|
||||||
app:iconPadding="6dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<RadioGroup
|
|
||||||
android:id="@+id/add_choices"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="24dp"
|
|
||||||
android:paddingEnd="24dp"
|
|
||||||
android:paddingTop="4dp"
|
|
||||||
android:paddingBottom="8dp"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:layout_marginBottom="6dp"
|
|
||||||
android:background="@drawable/shader_card_background"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingStart="20dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:paddingTop="16dp"
|
|
||||||
android:paddingBottom="16dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/preset_title"
|
|
||||||
style="@style/TextAppearance.Material3.TitleMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/preset_summary"
|
|
||||||
style="@style/TextAppearance.Material3.BodySmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.materialswitch.MaterialSwitch
|
|
||||||
android:id="@+id/preset_switch"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:minHeight="48dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:layout_marginBottom="6dp"
|
|
||||||
android:background="@drawable/shader_card_background"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/shader_header"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:paddingTop="12dp"
|
|
||||||
android:paddingBottom="12dp"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/shader_title"
|
|
||||||
style="@style/TextAppearance.Material3.TitleSmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/shader_summary"
|
|
||||||
style="@style/TextAppearance.Material3.BodySmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/shader_remove"
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:src="@drawable/ic_delete"
|
|
||||||
android:contentDescription="@string/post_processing_remove"
|
|
||||||
app:tint="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/shader_expand"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:src="@drawable/ic_dropdown_arrow"
|
|
||||||
android:contentDescription=""
|
|
||||||
app:tint="?attr/colorPrimary" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/shader_body"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingBottom="8dp"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingStart="20dp"
|
|
||||||
android:paddingEnd="20dp"
|
|
||||||
android:paddingTop="8dp"
|
|
||||||
android:paddingBottom="8dp">
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/fx_reset"
|
|
||||||
style="@style/Widget.Material3.Button.TonalButton"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:text="@string/post_processing_reset" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingStart="20dp"
|
|
||||||
android:paddingEnd="20dp"
|
|
||||||
android:paddingTop="8dp"
|
|
||||||
android:paddingBottom="4dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/fx_slider_title"
|
|
||||||
style="@style/TextAppearance.Material3.TitleSmall"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/fx_slider_value"
|
|
||||||
style="@style/TextAppearance.Material3.BodySmall"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.slider.Slider
|
|
||||||
android:id="@+id/fx_slider"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:stepSize="1"
|
|
||||||
android:valueFrom="0"
|
|
||||||
android:valueTo="100" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:layout_marginBottom="6dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/fx_button"
|
|
||||||
style="@style/Widget.Material3.Button.TonalButton"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="1" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:layout_marginBottom="6dp"
|
|
||||||
android:background="@drawable/shader_card_background"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/preset_row"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:paddingTop="12dp"
|
|
||||||
android:paddingBottom="12dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/preset_name"
|
|
||||||
style="@style/TextAppearance.Material3.TitleSmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/preset_description"
|
|
||||||
style="@style/TextAppearance.Material3.BodySmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/preset_delete"
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:clickable="true"
|
|
||||||
android:contentDescription="@string/post_processing_preset_delete"
|
|
||||||
android:focusable="true"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:src="@drawable/ic_delete"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:tint="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:layout_marginBottom="6dp"
|
|
||||||
android:background="@drawable/shader_card_background"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/shader_header"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingStart="20dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:paddingTop="14dp"
|
|
||||||
android:paddingBottom="14dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/shader_title"
|
|
||||||
style="@style/TextAppearance.Material3.TitleMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/shader_summary"
|
|
||||||
style="@style/TextAppearance.Material3.BodySmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/shader_remove"
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:clickable="true"
|
|
||||||
android:contentDescription="@string/post_processing_remove"
|
|
||||||
android:focusable="true"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:src="@drawable/ic_delete"
|
|
||||||
app:tint="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/shader_expand"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:contentDescription=""
|
|
||||||
android:src="@drawable/ic_dropdown_arrow"
|
|
||||||
app:tint="?attr/colorPrimary" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/shader_body"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingBottom="8dp"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginTop="6dp"
|
|
||||||
android:layout_marginBottom="6dp"
|
|
||||||
android:baselineAligned="false"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/fx_add"
|
|
||||||
style="@style/Widget.Material3.Button.TonalButton"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:minHeight="48dp"
|
|
||||||
android:paddingStart="12dp"
|
|
||||||
android:paddingEnd="12dp"
|
|
||||||
android:text="@string/post_processing_add"
|
|
||||||
app:icon="@drawable/ic_add"
|
|
||||||
app:iconPadding="6dp" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/fx_presets"
|
|
||||||
style="@style/Widget.Material3.Button.TonalButton"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_weight="1.4"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:minHeight="48dp"
|
|
||||||
android:paddingStart="12dp"
|
|
||||||
android:paddingEnd="12dp"
|
|
||||||
android:text="@string/post_processing_presets"
|
|
||||||
app:icon="@drawable/ic_dropdown_arrow"
|
|
||||||
app:iconGravity="textEnd"
|
|
||||||
app:iconPadding="6dp" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/fx_create_preset"
|
|
||||||
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:contentDescription="@string/post_processing_preset_new"
|
|
||||||
app:icon="@drawable/ic_add" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/fx_remove_all"
|
|
||||||
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:contentDescription="@string/post_processing_remove_all"
|
|
||||||
app:icon="@drawable/ic_delete" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -298,25 +298,6 @@
|
|||||||
<string name="gpu_driver_fetcher">GPU driver fetcher</string>
|
<string name="gpu_driver_fetcher">GPU driver fetcher</string>
|
||||||
<string name="gpu_driver_manager">GPU driver manager</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="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_add">Add effect</string>
|
|
||||||
<string name="post_processing_remove">Remove</string>
|
|
||||||
<string name="post_processing_open_list">Open list</string>
|
|
||||||
<string name="post_processing_close_list">Close list</string>
|
|
||||||
<string name="post_processing_remove_all">Remove effects</string>
|
|
||||||
<string name="post_processing_preset_locked">Preset active. Edit it in Post-Processing Effects before starting a game.</string>
|
|
||||||
<string name="post_processing_preset_modified">Values changed from the original preset.</string>
|
|
||||||
<string name="post_processing_presets">Presets</string>
|
|
||||||
<string name="post_processing_preset_new">New preset</string>
|
|
||||||
<string name="post_processing_preset_new_description">Saves the effects you have loaded, with their current values, as a preset you can pick later.</string>
|
|
||||||
<string name="post_processing_preset_name_invalid">Give the preset a name without an equals sign.</string>
|
|
||||||
<string name="post_processing_preset_none">No preset</string>
|
|
||||||
<string name="post_processing_preset_delete">Delete preset</string>
|
|
||||||
<string name="post_processing_preset_reset">Reset preset values</string>
|
|
||||||
<string name="post_processing_reset">Reset to default</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">Frame generation</string>
|
||||||
<string name="frame_gen_per_game_description">Configure frame generation for this game</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>
|
<string name="frame_gen_description">Insert interpolated frames between rendered ones using Lossless Scaling. Forces FIFO presentation while enabled.</string>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "audio_core/opus/hardware_opus.h"
|
#include "audio_core/opus/hardware_opus.h"
|
||||||
#include "audio_core/opus/parameters.h"
|
#include "audio_core/opus/parameters.h"
|
||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
|
#include "common/scope_exit.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
|
||||||
@@ -28,10 +29,18 @@ OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_)
|
|||||||
OpusDecoder::~OpusDecoder() {
|
OpusDecoder::~OpusDecoder() {
|
||||||
if (decode_object_initialized) {
|
if (decode_object_initialized) {
|
||||||
hardware_opus.ShutdownDecodeObject(shared_buffer.data(), shared_buffer.size());
|
hardware_opus.ShutdownDecodeObject(shared_buffer.data(), shared_buffer.size());
|
||||||
|
hardware_opus.UnregisterDecoder(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Result OpusDecoder::Initialize(const OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
|
Result OpusDecoder::Initialize(const OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
|
||||||
|
R_TRY(hardware_opus.RegisterDecoder(this));
|
||||||
|
SCOPE_EXIT {
|
||||||
|
if (!decode_object_initialized) {
|
||||||
|
hardware_opus.UnregisterDecoder(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
|
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
|
||||||
shared_buffer.resize(transfer_memory_size);
|
shared_buffer.resize(transfer_memory_size);
|
||||||
shared_memory_mapped = true;
|
shared_memory_mapped = true;
|
||||||
@@ -61,6 +70,13 @@ Result OpusDecoder::Initialize(const OpusParametersEx& params, Kernel::KTransfer
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result OpusDecoder::Initialize(const OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
|
Result OpusDecoder::Initialize(const OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
|
||||||
|
R_TRY(hardware_opus.RegisterDecoder(this));
|
||||||
|
SCOPE_EXIT {
|
||||||
|
if (!decode_object_initialized) {
|
||||||
|
hardware_opus.UnregisterDecoder(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
|
auto frame_size{params.use_large_frame_size ? 5760 : 1920};
|
||||||
shared_buffer.resize(transfer_memory_size, 0);
|
shared_buffer.resize(transfer_memory_size, 0);
|
||||||
shared_memory_mapped = true;
|
shared_memory_mapped = true;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/audio_core.h"
|
||||||
@@ -45,6 +46,25 @@ HardwareOpus::HardwareOpus(Core::System& system_)
|
|||||||
opus_decoder.SetSharedMemory(shared_memory);
|
opus_decoder.SetSharedMemory(shared_memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result HardwareOpus::RegisterDecoder(OpusDecoder* decoder) {
|
||||||
|
std::scoped_lock l{mutex};
|
||||||
|
const auto slot = std::ranges::find(decoders, nullptr);
|
||||||
|
if (slot == decoders.end()) {
|
||||||
|
R_THROW(ResultOutOfOpusDecoders);
|
||||||
|
}
|
||||||
|
*slot = decoder;
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareOpus::UnregisterDecoder(OpusDecoder* decoder) {
|
||||||
|
std::scoped_lock l{mutex};
|
||||||
|
const auto slot = std::ranges::find(decoders, decoder);
|
||||||
|
if (slot == decoders.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*slot = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
u32 HardwareOpus::GetWorkBufferSize(u32 channel) {
|
u32 HardwareOpus::GetWorkBufferSize(u32 channel) {
|
||||||
if (!opus_decoder.IsRunning()) {
|
if (!opus_decoder.IsRunning()) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <opus.h>
|
#include <opus.h>
|
||||||
|
|
||||||
@@ -12,9 +13,12 @@
|
|||||||
#include "core/hle/service/audio/errors.h"
|
#include "core/hle/service/audio/errors.h"
|
||||||
|
|
||||||
namespace AudioCore::OpusDecoder {
|
namespace AudioCore::OpusDecoder {
|
||||||
|
class OpusDecoder;
|
||||||
class HardwareOpus {
|
class HardwareOpus {
|
||||||
public:
|
public:
|
||||||
HardwareOpus(Core::System& system);
|
HardwareOpus(Core::System& system);
|
||||||
|
Result RegisterDecoder(OpusDecoder* decoder);
|
||||||
|
void UnregisterDecoder(OpusDecoder* decoder);
|
||||||
|
|
||||||
u32 GetWorkBufferSize(u32 channel);
|
u32 GetWorkBufferSize(u32 channel);
|
||||||
u32 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count);
|
u32 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count);
|
||||||
@@ -39,6 +43,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
|
std::array<OpusDecoder*, 24> decoders{};
|
||||||
ADSP::OpusDecoder::OpusDecoder& opus_decoder;
|
ADSP::OpusDecoder::OpusDecoder& opus_decoder;
|
||||||
ADSP::OpusDecoder::SharedMemory shared_memory;
|
ADSP::OpusDecoder::SharedMemory shared_memory;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,8 +23,6 @@
|
|||||||
#define LOSSLESS_DIR "lossless"
|
#define LOSSLESS_DIR "lossless"
|
||||||
#define NAND_DIR "nand"
|
#define NAND_DIR "nand"
|
||||||
#define PLAY_TIME_DIR "play_time"
|
#define PLAY_TIME_DIR "play_time"
|
||||||
#define POST_PRESET_DIR "post_presets"
|
|
||||||
#define POST_SHADER_DIR "post_shaders"
|
|
||||||
#define SCREENSHOTS_DIR "screenshots"
|
#define SCREENSHOTS_DIR "screenshots"
|
||||||
#define SDMC_DIR "sdmc"
|
#define SDMC_DIR "sdmc"
|
||||||
#define SHADER_DIR "shader"
|
#define SHADER_DIR "shader"
|
||||||
|
|||||||
@@ -160,8 +160,6 @@ public:
|
|||||||
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
|
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
|
||||||
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
|
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
|
||||||
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
|
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
|
||||||
GenerateEdenPath(EdenPath::PostPresetDir, eden_path / POST_PRESET_DIR);
|
|
||||||
GenerateEdenPath(EdenPath::PostShaderDir, eden_path / POST_SHADER_DIR);
|
|
||||||
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
|
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
|
||||||
GenerateEdenPath(EdenPath::ScreenshotsDir, eden_path / SCREENSHOTS_DIR);
|
GenerateEdenPath(EdenPath::ScreenshotsDir, eden_path / SCREENSHOTS_DIR);
|
||||||
GenerateEdenPath(EdenPath::SDMCDir, eden_path / SDMC_DIR);
|
GenerateEdenPath(EdenPath::SDMCDir, eden_path / SDMC_DIR);
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ enum class EdenPath {
|
|||||||
LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
|
LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
|
||||||
NANDDir, // Where the emulated NAND is stored.
|
NANDDir, // Where the emulated NAND is stored.
|
||||||
PlayTimeDir, // Where play time data is stored.
|
PlayTimeDir, // Where play time data is stored.
|
||||||
PostPresetDir,
|
|
||||||
PostShaderDir, // Where user post-processing shaders are stored.
|
|
||||||
SaveDir, // Where save data is stored.
|
SaveDir, // Where save data is stored.
|
||||||
ScreenshotsDir, // Where yuzu screenshots are stored.
|
ScreenshotsDir, // Where yuzu screenshots are stored.
|
||||||
SDMCDir, // Where the emulated SDMC is stored.
|
SDMCDir, // Where the emulated SDMC is stored.
|
||||||
|
|||||||
@@ -388,30 +388,6 @@ struct Values {
|
|||||||
true,
|
true,
|
||||||
true};
|
true};
|
||||||
|
|
||||||
SwitchableSetting<std::string> post_shader_chain{linkage,
|
|
||||||
std::string(),
|
|
||||||
"post_shader_chain",
|
|
||||||
Category::Renderer,
|
|
||||||
Specialization::Default,
|
|
||||||
true,
|
|
||||||
true};
|
|
||||||
|
|
||||||
SwitchableSetting<std::string> post_shader_preset{linkage,
|
|
||||||
std::string(),
|
|
||||||
"post_shader_preset",
|
|
||||||
Category::Renderer,
|
|
||||||
Specialization::Default,
|
|
||||||
true,
|
|
||||||
true};
|
|
||||||
|
|
||||||
SwitchableSetting<bool> post_shader_enabled{linkage,
|
|
||||||
true,
|
|
||||||
"post_shader_enabled",
|
|
||||||
Category::Renderer,
|
|
||||||
Specialization::Default,
|
|
||||||
true,
|
|
||||||
true};
|
|
||||||
|
|
||||||
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
|
SwitchableSetting<bool> frame_gen{linkage, false, "frame_gen", Category::Renderer,
|
||||||
Specialization::Default, true, false};
|
Specialization::Default, true, false};
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4};
|
|||||||
constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3};
|
constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3};
|
||||||
constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2};
|
constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2};
|
||||||
constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259};
|
constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259};
|
||||||
|
constexpr Result ResultOutOfOpusDecoders{ErrorModule::HwOpus, 385};
|
||||||
constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001};
|
constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001};
|
||||||
constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002};
|
constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002};
|
||||||
|
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
|||||||
INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"),
|
INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"),
|
||||||
tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA "
|
tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA "
|
||||||
"can produce a more stable picture in lower resolutions."));
|
"can produce a more stable picture in lower resolutions."));
|
||||||
INSERT(Settings, post_shader_chain, QString(), QString());
|
|
||||||
INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"),
|
INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"),
|
||||||
tr("The method used to render the window in fullscreen.\nBorderless offers the best "
|
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 "
|
"compatibility with the on-screen keyboard that some games request for "
|
||||||
|
|||||||
@@ -302,50 +302,6 @@ if (ENABLE_LSFG)
|
|||||||
target_compile_definitions(video_core PUBLIC HAS_LSFG)
|
target_compile_definitions(video_core PUBLIC HAS_LSFG)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_RESHADE)
|
|
||||||
set(BUNDLED_FX_DIR ${CMAKE_SOURCE_DIR}/dist/post_shaders)
|
|
||||||
set(BUNDLED_FX_HEADER ${CMAKE_CURRENT_BINARY_DIR}/bundled_fx_effects.h)
|
|
||||||
file(GLOB BUNDLED_FX_FILES ${BUNDLED_FX_DIR}/*.fx)
|
|
||||||
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT ${BUNDLED_FX_HEADER}
|
|
||||||
COMMAND ${CMAKE_COMMAND} -P
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/post_processing/GenerateBundledEffects.cmake
|
|
||||||
${BUNDLED_FX_DIR} ${BUNDLED_FX_HEADER}
|
|
||||||
DEPENDS ${BUNDLED_FX_FILES}
|
|
||||||
)
|
|
||||||
|
|
||||||
set(BUNDLED_PRESET_DIR ${CMAKE_SOURCE_DIR}/dist/post_presets)
|
|
||||||
set(BUNDLED_PRESET_HEADER ${CMAKE_CURRENT_BINARY_DIR}/bundled_fx_presets.h)
|
|
||||||
file(GLOB BUNDLED_PRESET_FILES ${BUNDLED_PRESET_DIR}/*.fxp)
|
|
||||||
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT ${BUNDLED_PRESET_HEADER}
|
|
||||||
COMMAND ${CMAKE_COMMAND} -P
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/post_processing/GenerateBundledPresets.cmake
|
|
||||||
${BUNDLED_PRESET_DIR} ${BUNDLED_PRESET_HEADER}
|
|
||||||
DEPENDS ${BUNDLED_PRESET_FILES}
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
post_processing/fx_preset.cpp
|
|
||||||
post_processing/fx_preset.h
|
|
||||||
renderer_vulkan/present/post_process.cpp
|
|
||||||
renderer_vulkan/present/post_process.h
|
|
||||||
${BUNDLED_FX_HEADER}
|
|
||||||
${BUNDLED_PRESET_HEADER}
|
|
||||||
)
|
|
||||||
target_include_directories(video_core PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
|
||||||
target_link_libraries(video_core PRIVATE reshadefx::reshadefx)
|
|
||||||
target_compile_definitions(video_core PUBLIC HAS_RESHADE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (ENABLE_OPENGL)
|
if (ENABLE_OPENGL)
|
||||||
target_sources(video_core PRIVATE
|
target_sources(video_core PRIVATE
|
||||||
renderer_opengl/present/filters.cpp
|
renderer_opengl/present/filters.cpp
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
set(EFFECT_DIR ${CMAKE_ARGV3})
|
|
||||||
set(HEADER_FILE ${CMAKE_ARGV4})
|
|
||||||
|
|
||||||
file(GLOB EFFECT_FILES ${EFFECT_DIR}/*.fx)
|
|
||||||
list(SORT EFFECT_FILES)
|
|
||||||
|
|
||||||
set(ENTRIES "")
|
|
||||||
foreach(EFFECT_FILE IN LISTS EFFECT_FILES)
|
|
||||||
get_filename_component(EFFECT_NAME ${EFFECT_FILE} NAME)
|
|
||||||
file(READ ${EFFECT_FILE} EFFECT_BODY)
|
|
||||||
|
|
||||||
string(REGEX REPLACE ";" "{{SEMICOLON}}" EFFECT_BODY "${EFFECT_BODY}")
|
|
||||||
string(REGEX REPLACE "\n" ";" EFFECT_BODY "${EFFECT_BODY}")
|
|
||||||
|
|
||||||
set(EFFECT_TEXT "")
|
|
||||||
foreach(LINE IN LISTS EFFECT_BODY)
|
|
||||||
string(CONCAT EFFECT_TEXT "${EFFECT_TEXT}" " R\"(${LINE}\n)\"\n")
|
|
||||||
endforeach()
|
|
||||||
string(REGEX REPLACE "{{SEMICOLON}}" ";" EFFECT_TEXT "${EFFECT_TEXT}")
|
|
||||||
|
|
||||||
string(CONCAT ENTRIES "${ENTRIES}"
|
|
||||||
" {\n \"${EFFECT_NAME}\",\n${EFFECT_TEXT} },\n")
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
get_filename_component(OUTPUT_DIR ${HEADER_FILE} DIRECTORY)
|
|
||||||
make_directory(${OUTPUT_DIR})
|
|
||||||
|
|
||||||
file(WRITE ${HEADER_FILE}
|
|
||||||
"// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace VideoCore {
|
|
||||||
|
|
||||||
struct BundledFxEffect {
|
|
||||||
std::string_view name;
|
|
||||||
std::string_view source;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr BundledFxEffect BUNDLED_FX_EFFECTS[]{
|
|
||||||
${ENTRIES}};
|
|
||||||
|
|
||||||
} // namespace VideoCore
|
|
||||||
")
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
set(PRESET_DIR ${CMAKE_ARGV3})
|
|
||||||
set(HEADER_FILE ${CMAKE_ARGV4})
|
|
||||||
|
|
||||||
file(GLOB PRESET_FILES ${PRESET_DIR}/*.fxp)
|
|
||||||
list(SORT PRESET_FILES)
|
|
||||||
|
|
||||||
set(ENTRIES "")
|
|
||||||
foreach(PRESET_FILE IN LISTS PRESET_FILES)
|
|
||||||
get_filename_component(PRESET_NAME ${PRESET_FILE} NAME)
|
|
||||||
file(READ ${PRESET_FILE} PRESET_BODY)
|
|
||||||
|
|
||||||
string(REGEX REPLACE ";" "{{SEMICOLON}}" PRESET_BODY "${PRESET_BODY}")
|
|
||||||
string(REGEX REPLACE "\n" ";" PRESET_BODY "${PRESET_BODY}")
|
|
||||||
|
|
||||||
set(PRESET_TEXT "")
|
|
||||||
foreach(LINE IN LISTS PRESET_BODY)
|
|
||||||
string(CONCAT PRESET_TEXT "${PRESET_TEXT}" " R\"(${LINE}\n)\"\n")
|
|
||||||
endforeach()
|
|
||||||
string(REGEX REPLACE "{{SEMICOLON}}" ";" PRESET_TEXT "${PRESET_TEXT}")
|
|
||||||
|
|
||||||
string(CONCAT ENTRIES "${ENTRIES}"
|
|
||||||
" {\n \"${PRESET_NAME}\",\n${PRESET_TEXT} },\n")
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
get_filename_component(OUTPUT_DIR ${HEADER_FILE} DIRECTORY)
|
|
||||||
make_directory(${OUTPUT_DIR})
|
|
||||||
|
|
||||||
file(WRITE ${HEADER_FILE}
|
|
||||||
"// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace VideoCore {
|
|
||||||
|
|
||||||
struct BundledFxPreset {
|
|
||||||
std::string_view name;
|
|
||||||
std::string_view source;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr BundledFxPreset BUNDLED_FX_PRESETS[]{
|
|
||||||
${ENTRIES}};
|
|
||||||
|
|
||||||
} // namespace VideoCore
|
|
||||||
")
|
|
||||||
@@ -1,321 +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 += fmt::format("{}|{}|", entry.file, entry.technique);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UseGlobalFxSettings() {
|
|
||||||
Settings::values.post_shader_chain.SetGlobal(true);
|
|
||||||
Settings::values.post_shader_preset.SetGlobal(true);
|
|
||||||
Settings::values.post_shader_enabled.SetGlobal(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UsePerGameFxSettings() {
|
|
||||||
const std::string chain = Settings::values.post_shader_chain.GetValue();
|
|
||||||
const std::string preset = Settings::values.post_shader_preset.GetValue();
|
|
||||||
const bool enabled = Settings::values.post_shader_enabled.GetValue();
|
|
||||||
|
|
||||||
Settings::values.post_shader_chain.SetGlobal(false);
|
|
||||||
Settings::values.post_shader_preset.SetGlobal(false);
|
|
||||||
Settings::values.post_shader_enabled.SetGlobal(false);
|
|
||||||
|
|
||||||
Settings::values.post_shader_chain.SetValue(chain);
|
|
||||||
Settings::values.post_shader_preset.SetValue(preset);
|
|
||||||
Settings::values.post_shader_enabled.SetValue(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
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::SetEntries(std::vector<FxChainEntry> next) {
|
|
||||||
{
|
|
||||||
std::scoped_lock lock{mutex};
|
|
||||||
entries = std::move(next);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<std::string, std::array<f32, 4>> FxChain::EntryValues(size_t index) const {
|
|
||||||
std::scoped_lock lock{mutex};
|
|
||||||
if (index >= entries.size()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return entries[index].values;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 parsed = ParseFxChain(Settings::values.post_shader_chain.GetValue());
|
|
||||||
|
|
||||||
std::scoped_lock lock{mutex};
|
|
||||||
if (entries == parsed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
entries = std::move(parsed);
|
|
||||||
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,86 +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;
|
|
||||||
|
|
||||||
bool operator==(const FxChainEntry&) const = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FxChainSnapshot {
|
|
||||||
std::vector<FxChainEntry> entries;
|
|
||||||
u64 generation{};
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<FxChainEntry> ParseFxChain(std::string_view value);
|
|
||||||
|
|
||||||
std::string SerializeFxChain(std::span<const FxChainEntry> entries);
|
|
||||||
|
|
||||||
void UseGlobalFxSettings();
|
|
||||||
|
|
||||||
void UsePerGameFxSettings();
|
|
||||||
|
|
||||||
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 SetEntries(std::vector<FxChainEntry> next);
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
std::map<std::string, std::array<f32, 4>> EntryValues(size_t index) 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__", "60800");
|
|
||||||
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, true));
|
|
||||||
|
|
||||||
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,307 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "bundled_fx_effects.h"
|
|
||||||
#include "common/fs/file.h"
|
|
||||||
#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;
|
|
||||||
bool catalog_scanned = false;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!compiled.module.techniques.empty()) {
|
|
||||||
const auto& annotations = compiled.module.techniques.front().annotations;
|
|
||||||
desc.label = AnnotationString(annotations, "ui_label");
|
|
||||||
desc.description = AnnotationString(annotations, "ui_tooltip");
|
|
||||||
}
|
|
||||||
if (desc.label.empty()) {
|
|
||||||
desc.label = desc.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();
|
|
||||||
catalog_scanned = true;
|
|
||||||
|
|
||||||
const auto root = GetFxRootDirectory();
|
|
||||||
if (!Common::FS::Exists(root) && !Common::FS::CreateDirs(root)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& bundled : BUNDLED_FX_EFFECTS) {
|
|
||||||
const auto path = root / bundled.name;
|
|
||||||
if (Common::FS::Exists(path)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
void(Common::FS::WriteStringToFile(path, Common::FS::FileType::TextFile, bundled.source));
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
if (!catalog_scanned) {
|
|
||||||
ReloadFxCatalog();
|
|
||||||
}
|
|
||||||
return catalog;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FxEffectDesc* FindFxEffect(std::string_view file) {
|
|
||||||
const auto& effects = GetFxCatalog();
|
|
||||||
const auto it = std::find_if(effects.begin(), effects.end(),
|
|
||||||
[&](const FxEffectDesc& d) { return d.file == file; });
|
|
||||||
if (it == effects.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,77 +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::string label;
|
|
||||||
std::string description;
|
|
||||||
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
|
|
||||||
@@ -1,261 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "bundled_fx_presets.h"
|
|
||||||
#include "common/fs/file.h"
|
|
||||||
#include "common/fs/fs.h"
|
|
||||||
#include "common/fs/path_util.h"
|
|
||||||
#include "common/settings.h"
|
|
||||||
#include "video_core/post_processing/fx_chain.h"
|
|
||||||
#include "video_core/post_processing/fx_preset.h"
|
|
||||||
|
|
||||||
namespace VideoCore {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
std::vector<FxPresetDesc> preset_catalog;
|
|
||||||
bool preset_catalog_scanned = false;
|
|
||||||
|
|
||||||
std::string Trim(std::string_view value) {
|
|
||||||
size_t begin = 0;
|
|
||||||
while (begin < value.size() && (value[begin] == ' ' || value[begin] == '\t')) {
|
|
||||||
++begin;
|
|
||||||
}
|
|
||||||
size_t end = value.size();
|
|
||||||
while (end > begin) {
|
|
||||||
const char c = value[end - 1];
|
|
||||||
if (c != ' ' && c != '\t' && c != '\r' && c != '\n') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
--end;
|
|
||||||
}
|
|
||||||
return std::string(value.substr(begin, end - begin));
|
|
||||||
}
|
|
||||||
|
|
||||||
FxPresetDesc ParsePreset(std::string_view text) {
|
|
||||||
FxPresetDesc desc;
|
|
||||||
|
|
||||||
size_t start = 0;
|
|
||||||
while (start <= text.size()) {
|
|
||||||
size_t end = text.find('\n', start);
|
|
||||||
if (end == std::string_view::npos) {
|
|
||||||
end = text.size();
|
|
||||||
}
|
|
||||||
const std::string_view line = text.substr(start, end - start);
|
|
||||||
start = end + 1;
|
|
||||||
|
|
||||||
const size_t equals = line.find('=');
|
|
||||||
if (equals == std::string_view::npos) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string key = Trim(line.substr(0, equals));
|
|
||||||
const std::string value = Trim(line.substr(equals + 1));
|
|
||||||
|
|
||||||
if (key == "name") {
|
|
||||||
desc.name = value;
|
|
||||||
} else if (key == "description") {
|
|
||||||
desc.description = value;
|
|
||||||
} else if (key == "chain") {
|
|
||||||
desc.chain = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return desc;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string PresetFileName(std::string_view name) {
|
|
||||||
std::string out;
|
|
||||||
for (const char c : name) {
|
|
||||||
if (c == ' ') {
|
|
||||||
out += '_';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const bool keep = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
|
||||||
(c >= '0' && c <= '9') || c == '-' || c == '_';
|
|
||||||
if (keep) {
|
|
||||||
out += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (out.empty()) {
|
|
||||||
out = "preset";
|
|
||||||
}
|
|
||||||
return out + ".fxp";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ComposePreset(const FxPresetDesc& desc) {
|
|
||||||
std::string out;
|
|
||||||
out += "name=";
|
|
||||||
out += desc.name;
|
|
||||||
out += '\n';
|
|
||||||
out += "description=";
|
|
||||||
out += desc.description;
|
|
||||||
out += '\n';
|
|
||||||
out += "chain=";
|
|
||||||
out += desc.chain;
|
|
||||||
out += '\n';
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // Anonymous namespace
|
|
||||||
|
|
||||||
std::filesystem::path GetFxPresetDirectory() {
|
|
||||||
return Common::FS::GetEdenPath(Common::FS::EdenPath::PostPresetDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReloadFxPresetCatalog() {
|
|
||||||
preset_catalog.clear();
|
|
||||||
preset_catalog_scanned = true;
|
|
||||||
|
|
||||||
const auto root = GetFxPresetDirectory();
|
|
||||||
if (!Common::FS::Exists(root) && !Common::FS::CreateDirs(root)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> bundled_names;
|
|
||||||
for (const auto& bundled : BUNDLED_FX_PRESETS) {
|
|
||||||
const auto path = root / bundled.name;
|
|
||||||
bundled_names.emplace_back(bundled.name);
|
|
||||||
if (Common::FS::Exists(path)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
void(Common::FS::WriteStringToFile(path, Common::FS::FileType::TextFile, bundled.source));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::filesystem::path> preset_files;
|
|
||||||
Common::FS::IterateDirEntriesRecursively(
|
|
||||||
root,
|
|
||||||
[&](const std::filesystem::directory_entry& entry) {
|
|
||||||
if (entry.path().extension() == ".fxp") {
|
|
||||||
preset_files.push_back(entry.path());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
Common::FS::DirEntryFilter::File);
|
|
||||||
|
|
||||||
std::sort(preset_files.begin(), preset_files.end());
|
|
||||||
|
|
||||||
for (const auto& file : preset_files) {
|
|
||||||
const std::string text =
|
|
||||||
Common::FS::ReadStringFromFile(file, Common::FS::FileType::TextFile);
|
|
||||||
|
|
||||||
FxPresetDesc desc = ParsePreset(text);
|
|
||||||
if (desc.name.empty() || desc.chain.empty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
desc.file = Common::FS::PathToUTF8String(file.filename());
|
|
||||||
desc.bundled = std::find(bundled_names.begin(), bundled_names.end(), desc.file) !=
|
|
||||||
bundled_names.end();
|
|
||||||
preset_catalog.push_back(std::move(desc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<FxPresetDesc>& GetFxPresetCatalog() {
|
|
||||||
if (!preset_catalog_scanned) {
|
|
||||||
ReloadFxPresetCatalog();
|
|
||||||
}
|
|
||||||
return preset_catalog;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FxPresetDesc* FindFxPreset(std::string_view name) {
|
|
||||||
const auto& presets = GetFxPresetCatalog();
|
|
||||||
const auto it = std::find_if(presets.begin(), presets.end(),
|
|
||||||
[&](const FxPresetDesc& p) { return p.name == name; });
|
|
||||||
if (it == presets.end()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return &*it;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ApplyFxPreset(std::string_view name) {
|
|
||||||
const FxPresetDesc* preset = FindFxPreset(name);
|
|
||||||
if (preset == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
FxChain::Instance().SetEntries(ParseFxChain(preset->chain));
|
|
||||||
FxChain::Instance().DropUnknownEntries();
|
|
||||||
FxChain::Instance().StoreToSettings();
|
|
||||||
SetActiveFxPreset(preset->name);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SaveFxPreset(std::string_view name, std::string_view description) {
|
|
||||||
if (!IsFxPresetName(name)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto root = GetFxPresetDirectory();
|
|
||||||
if (!Common::FS::Exists(root) && !Common::FS::CreateDirs(root)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
FxPresetDesc desc;
|
|
||||||
desc.name = std::string(name);
|
|
||||||
desc.description = std::string(description);
|
|
||||||
desc.chain = SerializeFxChain(FxChain::Instance().Entries());
|
|
||||||
|
|
||||||
const std::string body = ComposePreset(desc);
|
|
||||||
const std::string file = PresetFileName(name);
|
|
||||||
if (Common::FS::WriteStringToFile(root / file, Common::FS::FileType::TextFile, body) !=
|
|
||||||
body.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReloadFxPresetCatalog();
|
|
||||||
SetActiveFxPreset(name);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeleteFxPreset(std::string_view name) {
|
|
||||||
const FxPresetDesc* preset = FindFxPreset(name);
|
|
||||||
if (preset == nullptr || preset->bundled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto path = GetFxPresetDirectory() / preset->file;
|
|
||||||
if (!Common::FS::RemoveFile(path)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GetActiveFxPreset() == name) {
|
|
||||||
SetActiveFxPreset(std::string_view());
|
|
||||||
}
|
|
||||||
ReloadFxPresetCatalog();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsFxPresetName(std::string_view name) {
|
|
||||||
if (name.empty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return name.find_first_of("\r\n=") == std::string_view::npos;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GetActiveFxPreset() {
|
|
||||||
return Settings::values.post_shader_preset.GetValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetActiveFxPreset(std::string_view name) {
|
|
||||||
Settings::values.post_shader_preset.SetValue(std::string(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsActiveFxPresetModified() {
|
|
||||||
const std::string active = GetActiveFxPreset();
|
|
||||||
if (active.empty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FxPresetDesc* preset = FindFxPreset(active);
|
|
||||||
if (preset == nullptr) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SerializeFxChain(FxChain::Instance().Entries()) !=
|
|
||||||
SerializeFxChain(ParseFxChain(preset->chain));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace VideoCore
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace VideoCore {
|
|
||||||
|
|
||||||
struct FxPresetDesc {
|
|
||||||
std::string file;
|
|
||||||
std::string name;
|
|
||||||
std::string description;
|
|
||||||
std::string chain;
|
|
||||||
bool bundled{};
|
|
||||||
};
|
|
||||||
|
|
||||||
std::filesystem::path GetFxPresetDirectory();
|
|
||||||
|
|
||||||
void ReloadFxPresetCatalog();
|
|
||||||
|
|
||||||
const std::vector<FxPresetDesc>& GetFxPresetCatalog();
|
|
||||||
|
|
||||||
const FxPresetDesc* FindFxPreset(std::string_view name);
|
|
||||||
|
|
||||||
bool ApplyFxPreset(std::string_view name);
|
|
||||||
|
|
||||||
bool SaveFxPreset(std::string_view name, std::string_view description);
|
|
||||||
|
|
||||||
bool DeleteFxPreset(std::string_view name);
|
|
||||||
|
|
||||||
bool IsFxPresetName(std::string_view name);
|
|
||||||
|
|
||||||
std::string GetActiveFxPreset();
|
|
||||||
|
|
||||||
void SetActiveFxPreset(std::string_view name);
|
|
||||||
|
|
||||||
bool IsActiveFxPresetModified();
|
|
||||||
|
|
||||||
} // namespace VideoCore
|
|
||||||
@@ -18,10 +18,6 @@
|
|||||||
#include "video_core/renderer_vulkan/present/sgsr.h"
|
#include "video_core/renderer_vulkan/present/sgsr.h"
|
||||||
#include "video_core/renderer_vulkan/present/fxaa.h"
|
#include "video_core/renderer_vulkan/present/fxaa.h"
|
||||||
#include "video_core/renderer_vulkan/present/layer.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/present_push_constants.h"
|
||||||
#include "video_core/renderer_vulkan/present/smaa.h"
|
#include "video_core/renderer_vulkan/present/smaa.h"
|
||||||
#include "video_core/renderer_vulkan/present/util.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);
|
RefreshResources(device, framebuffer);
|
||||||
SetAntiAliasPass(device);
|
SetAntiAliasPass(device);
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
SetPostProcessPass(device);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Finish any pending renderpass
|
// Finish any pending renderpass
|
||||||
scheduler.RequestOutsideRenderPassOperationContext();
|
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);
|
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);
|
auto crop_rect = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height);
|
||||||
const VkExtent2D render_extent{
|
const VkExtent2D render_extent{
|
||||||
.width = scaled_width,
|
.width = scaled_width,
|
||||||
@@ -227,43 +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;
|
|
||||||
const bool enabled = Settings::values.post_shader_enabled.GetValue();
|
|
||||||
|
|
||||||
if (post_process_generation == generation && post_process_enabled == enabled &&
|
|
||||||
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_enabled = enabled;
|
|
||||||
post_process_extent = render_area;
|
|
||||||
post_process.reset();
|
|
||||||
|
|
||||||
if (!enabled || 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() {
|
void Layer::ReleaseRawImages() {
|
||||||
for (const u64 tick : resource_ticks) {
|
for (const u64 tick : resource_ticks) {
|
||||||
scheduler.Wait(tick);
|
scheduler.Wait(tick);
|
||||||
|
|||||||
@@ -15,9 +15,6 @@
|
|||||||
#include "video_core/renderer_vulkan/present/fsr.h"
|
#include "video_core/renderer_vulkan/present/fsr.h"
|
||||||
#include "video_core/renderer_vulkan/present/sgsr.h"
|
#include "video_core/renderer_vulkan/present/sgsr.h"
|
||||||
#include "video_core/renderer_vulkan/present/fxaa.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"
|
#include "video_core/renderer_vulkan/present/smaa.h"
|
||||||
|
|
||||||
namespace Layout {
|
namespace Layout {
|
||||||
@@ -69,9 +66,6 @@ private:
|
|||||||
|
|
||||||
void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer);
|
void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer);
|
||||||
void SetAntiAliasPass(const Device& device);
|
void SetAntiAliasPass(const Device& device);
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
void SetPostProcessPass(const Device& device);
|
|
||||||
#endif
|
|
||||||
void ReleaseRawImages();
|
void ReleaseRawImages();
|
||||||
|
|
||||||
u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const;
|
u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const;
|
||||||
@@ -101,12 +95,6 @@ private:
|
|||||||
Settings::AntiAliasing anti_alias_setting{};
|
Settings::AntiAliasing anti_alias_setting{};
|
||||||
std::variant<std::monostate, FXAA, SMAA> anti_alias{};
|
std::variant<std::monostate, FXAA, SMAA> anti_alias{};
|
||||||
std::variant<std::monostate, SGSR, FSR> sr_filter{};
|
std::variant<std::monostate, SGSR, FSR> sr_filter{};
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
std::optional<PostProcessChain> post_process{};
|
|
||||||
u64 post_process_generation{};
|
|
||||||
bool post_process_enabled{};
|
|
||||||
VkExtent2D post_process_extent{};
|
|
||||||
#endif
|
|
||||||
std::vector<u64> resource_ticks{};
|
std::vector<u64> resource_ticks{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,899 +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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<u32>(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();
|
|
||||||
const auto overrides = VideoCore::FxChain::Instance().EntryValues(effect.entry_index);
|
|
||||||
|
|
||||||
for (auto& uniform : effect.uniforms) {
|
|
||||||
std::array<f32, 4> value = uniform.fallback;
|
|
||||||
|
|
||||||
const auto override = overrides.find(uniform.name);
|
|
||||||
if (override != overrides.end()) {
|
|
||||||
value = override->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,9 +24,6 @@
|
|||||||
#include "video_core/gpu.h"
|
#include "video_core/gpu.h"
|
||||||
#include "video_core/present.h"
|
#include "video_core/present.h"
|
||||||
#include "video_core/renderer_vulkan/present/util.h"
|
#include "video_core/renderer_vulkan/present/util.h"
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
#include "video_core/post_processing/fx_chain.h"
|
|
||||||
#endif
|
|
||||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
#include "video_core/renderer_vulkan/vk_blit_screen.h"
|
#include "video_core/renderer_vulkan/vk_blit_screen.h"
|
||||||
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||||
@@ -186,10 +183,6 @@ try
|
|||||||
scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
|
scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
VideoCore::FxChain::Instance().LoadFromSettings();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Report();
|
Report();
|
||||||
} catch (const vk::Exception& exception) {
|
} catch (const vk::Exception& exception) {
|
||||||
LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what());
|
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)
|
target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_RESHADE)
|
|
||||||
target_sources(yuzu PRIVATE
|
|
||||||
configuration/configure_post_processing.cpp
|
|
||||||
configuration/configure_post_processing.h)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (CXX_CLANG)
|
if (CXX_CLANG)
|
||||||
target_compile_definitions(yuzu PRIVATE
|
target_compile_definitions(yuzu PRIVATE
|
||||||
$<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,15>:CANNOT_EXPLICITLY_INSTANTIATE>)
|
$<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,15>:CANNOT_EXPLICITLY_INSTANTIATE>)
|
||||||
|
|||||||
@@ -1,511 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cmath>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include <QAction>
|
|
||||||
#include <QCheckBox>
|
|
||||||
#include <QComboBox>
|
|
||||||
#include <QDialogButtonBox>
|
|
||||||
#include <QFrame>
|
|
||||||
#include <QGridLayout>
|
|
||||||
#include <QInputDialog>
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QMenu>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QScrollArea>
|
|
||||||
#include <QSlider>
|
|
||||||
#include <QToolButton>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
|
|
||||||
#include "common/settings.h"
|
|
||||||
#include "video_core/post_processing/fx_chain.h"
|
|
||||||
#include "video_core/post_processing/fx_effect.h"
|
|
||||||
#include "video_core/post_processing/fx_preset.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.label);
|
|
||||||
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);
|
|
||||||
|
|
||||||
enabled_box = new QCheckBox(tr("Enable post-processing"), this);
|
|
||||||
enabled_box->setChecked(Settings::values.post_shader_enabled.GetValue());
|
|
||||||
connect(enabled_box, &QCheckBox::toggled, this, [](bool checked) {
|
|
||||||
Settings::values.post_shader_enabled.SetValue(checked);
|
|
||||||
});
|
|
||||||
root->addWidget(enabled_box);
|
|
||||||
|
|
||||||
auto* preset_row = new QHBoxLayout();
|
|
||||||
preset_row->addWidget(new QLabel(tr("Preset:"), this));
|
|
||||||
|
|
||||||
preset_combo = new QComboBox(this);
|
|
||||||
preset_row->addWidget(preset_combo, 1);
|
|
||||||
|
|
||||||
auto* apply_button = new QPushButton(tr("Apply"), this);
|
|
||||||
connect(apply_button, &QPushButton::clicked, this, [this]() {
|
|
||||||
const QString name = preset_combo->currentData().toString();
|
|
||||||
if (name.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
VideoCore::ApplyFxPreset(name.toStdString());
|
|
||||||
ApplyStructuralChange();
|
|
||||||
});
|
|
||||||
preset_row->addWidget(apply_button);
|
|
||||||
|
|
||||||
auto* reset_button = new QPushButton(tr("Reset Values"), this);
|
|
||||||
reset_button->setToolTip(tr("Return every effect to the values the preset ships with."));
|
|
||||||
connect(reset_button, &QPushButton::clicked, this, [this]() {
|
|
||||||
const std::string active = VideoCore::GetActiveFxPreset();
|
|
||||||
if (active.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
VideoCore::ApplyFxPreset(active);
|
|
||||||
ApplyStructuralChange();
|
|
||||||
});
|
|
||||||
preset_row->addWidget(reset_button);
|
|
||||||
|
|
||||||
auto* save_button = new QPushButton(tr("Save As..."), this);
|
|
||||||
connect(save_button, &QPushButton::clicked, this, [this]() {
|
|
||||||
bool accepted = false;
|
|
||||||
const QString name =
|
|
||||||
QInputDialog::getText(this, tr("Save Preset"), tr("Preset name:"), QLineEdit::Normal,
|
|
||||||
QString(), &accepted);
|
|
||||||
if (!accepted || name.trimmed().isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
VideoCore::SaveFxPreset(name.trimmed().toStdString(), std::string());
|
|
||||||
PopulatePresetCombo();
|
|
||||||
RefreshPresetStatus();
|
|
||||||
});
|
|
||||||
preset_row->addWidget(save_button);
|
|
||||||
|
|
||||||
auto* delete_button = new QPushButton(tr("Delete"), this);
|
|
||||||
connect(delete_button, &QPushButton::clicked, this, [this]() {
|
|
||||||
const QString name = preset_combo->currentData().toString();
|
|
||||||
if (name.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
VideoCore::DeleteFxPreset(name.toStdString());
|
|
||||||
PopulatePresetCombo();
|
|
||||||
RefreshPresetStatus();
|
|
||||||
});
|
|
||||||
preset_row->addWidget(delete_button);
|
|
||||||
|
|
||||||
root->addLayout(preset_row);
|
|
||||||
|
|
||||||
preset_status = new QLabel(this);
|
|
||||||
preset_status->setWordWrap(true);
|
|
||||||
root->addWidget(preset_status);
|
|
||||||
|
|
||||||
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);
|
|
||||||
auto* add_menu = new QMenu(add_button);
|
|
||||||
for (const auto& effect : VideoCore::GetFxCatalog()) {
|
|
||||||
if (!effect.Valid()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const auto& technique : effect.techniques) {
|
|
||||||
QAction* action = add_menu->addAction(SlotLabel(effect, technique));
|
|
||||||
action->setToolTip(QString::fromStdString(effect.description));
|
|
||||||
const std::string file = effect.file;
|
|
||||||
const std::string name = technique;
|
|
||||||
connect(action, &QAction::triggered, this, [this, file, name]() {
|
|
||||||
VideoCore::FxChain::Instance().Append(file, name);
|
|
||||||
ApplyStructuralChange();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
add_button->setMenu(add_menu);
|
|
||||||
actions->addWidget(add_button);
|
|
||||||
|
|
||||||
actions->addStretch();
|
|
||||||
root->addLayout(actions);
|
|
||||||
|
|
||||||
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, this);
|
|
||||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::close);
|
|
||||||
root->addWidget(buttons);
|
|
||||||
|
|
||||||
VideoCore::ReloadFxCatalog();
|
|
||||||
VideoCore::ReloadFxPresetCatalog();
|
|
||||||
VideoCore::FxChain::Instance().LoadFromSettings();
|
|
||||||
VideoCore::FxChain::Instance().DropUnknownEntries();
|
|
||||||
PopulatePresetCombo();
|
|
||||||
RebuildRows();
|
|
||||||
RefreshPresetStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigurePostProcessing::~ConfigurePostProcessing() = default;
|
|
||||||
|
|
||||||
void ConfigurePostProcessing::ApplyStructuralChange() {
|
|
||||||
VideoCore::FxChain::Instance().StoreToSettings();
|
|
||||||
RebuildRows();
|
|
||||||
RefreshPresetStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurePostProcessing::PopulatePresetCombo() {
|
|
||||||
const QString previous = preset_combo->currentData().toString();
|
|
||||||
preset_combo->clear();
|
|
||||||
|
|
||||||
for (const auto& preset : VideoCore::GetFxPresetCatalog()) {
|
|
||||||
const QString name = QString::fromStdString(preset.name);
|
|
||||||
preset_combo->addItem(name, name);
|
|
||||||
preset_combo->setItemData(preset_combo->count() - 1,
|
|
||||||
QString::fromStdString(preset.description), Qt::ToolTipRole);
|
|
||||||
}
|
|
||||||
|
|
||||||
const int restored = preset_combo->findData(previous);
|
|
||||||
if (restored >= 0) {
|
|
||||||
preset_combo->setCurrentIndex(restored);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int active = preset_combo->findData(QString::fromStdString(VideoCore::GetActiveFxPreset()));
|
|
||||||
if (active >= 0) {
|
|
||||||
preset_combo->setCurrentIndex(active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigurePostProcessing::RefreshPresetStatus() {
|
|
||||||
const std::string active = VideoCore::GetActiveFxPreset();
|
|
||||||
if (active.empty()) {
|
|
||||||
preset_status->setText(tr("Custom chain. Pick a preset above and press Apply to replace it."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (VideoCore::IsActiveFxPresetModified()) {
|
|
||||||
preset_status->setText(
|
|
||||||
tr("Active preset: %1 (modified). Press Reset Values to go back to how it ships.")
|
|
||||||
.arg(QString::fromStdString(active)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
preset_status->setText(tr("Active preset: %1").arg(QString::fromStdString(active)));
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
combo->setItemData(combo->count() - 1, QString::fromStdString(effect.description),
|
|
||||||
Qt::ToolTipRole);
|
|
||||||
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);
|
|
||||||
VideoCore::FxChain::Instance().StoreToSettings();
|
|
||||||
value_label->setText(FormatValue(desc, next[component]));
|
|
||||||
});
|
|
||||||
|
|
||||||
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 QFrame(slots_container);
|
|
||||||
group->setObjectName(QStringLiteral("fxSlot"));
|
|
||||||
group->setStyleSheet(QStringLiteral(
|
|
||||||
"QFrame#fxSlot { background-color: palette(alternate-base);"
|
|
||||||
" border: 1px solid palette(mid); border-radius: 6px; }"));
|
|
||||||
auto* layout = new QVBoxLayout(group);
|
|
||||||
|
|
||||||
auto* header = new QHBoxLayout();
|
|
||||||
|
|
||||||
auto* toggle = new QToolButton(group);
|
|
||||||
toggle->setArrowType(Qt::RightArrow);
|
|
||||||
toggle->setAutoRaise(true);
|
|
||||||
toggle->setCheckable(true);
|
|
||||||
header->addWidget(toggle);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
auto* body = new QWidget(group);
|
|
||||||
auto* body_layout = new QVBoxLayout(body);
|
|
||||||
body_layout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
body->setVisible(false);
|
|
||||||
layout->addWidget(body);
|
|
||||||
|
|
||||||
connect(toggle, &QToolButton::toggled, this, [toggle, body](bool open) {
|
|
||||||
body->setVisible(open);
|
|
||||||
if (open) {
|
|
||||||
toggle->setArrowType(Qt::DownArrow);
|
|
||||||
} else {
|
|
||||||
toggle->setArrowType(Qt::RightArrow);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
body_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);
|
|
||||||
body_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;"));
|
|
||||||
body_layout->addWidget(category);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BuildUniformWidget(body, body_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,42 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QDialog>
|
|
||||||
|
|
||||||
class QCheckBox;
|
|
||||||
class QComboBox;
|
|
||||||
class QLabel;
|
|
||||||
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();
|
|
||||||
void PopulatePresetCombo();
|
|
||||||
void RefreshPresetStatus();
|
|
||||||
|
|
||||||
QVBoxLayout* slots_layout{};
|
|
||||||
QWidget* slots_container{};
|
|
||||||
QComboBox* preset_combo{};
|
|
||||||
QLabel* preset_status{};
|
|
||||||
QCheckBox* enabled_box{};
|
|
||||||
};
|
|
||||||
@@ -148,7 +148,6 @@
|
|||||||
<addaction name="action_Show_Filter_Bar"/>
|
<addaction name="action_Show_Filter_Bar"/>
|
||||||
<addaction name="action_Show_Status_Bar"/>
|
<addaction name="action_Show_Status_Bar"/>
|
||||||
<addaction name="action_Show_Performance_Overlay"/>
|
<addaction name="action_Show_Performance_Overlay"/>
|
||||||
<addaction name="action_Post_Processing_Shaders"/>
|
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="menu_Reset_Window_Size"/>
|
<addaction name="menu_Reset_Window_Size"/>
|
||||||
<addaction name="menu_View_Debugging"/>
|
<addaction name="menu_View_Debugging"/>
|
||||||
@@ -605,11 +604,6 @@
|
|||||||
<string>Show &Performance Overlay</string>
|
<string>Show &Performance Overlay</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="action_Post_Processing_Shaders">
|
|
||||||
<property name="text">
|
|
||||||
<string>Post-Processing &Shaders...</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="action_Carousel_View">
|
<action name="action_Carousel_View">
|
||||||
<property name="checkable">
|
<property name="checkable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
|||||||
@@ -13,9 +13,6 @@
|
|||||||
#include "common/settings_enums.h"
|
#include "common/settings_enums.h"
|
||||||
#include "frontend_common/settings_generator.h"
|
#include "frontend_common/settings_generator.h"
|
||||||
#include "render/performance_overlay.h"
|
#include "render/performance_overlay.h"
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
#include "configuration/configure_post_processing.h"
|
|
||||||
#endif
|
|
||||||
#include "updater/update_dialog.h"
|
#include "updater/update_dialog.h"
|
||||||
|
|
||||||
#include "common/fs/ryujinx_compat.h"
|
#include "common/fs/ryujinx_compat.h"
|
||||||
@@ -1520,11 +1517,6 @@ void MainWindow::ConnectMenuEvents() {
|
|||||||
connect_menu(ui->action_Show_Filter_Bar, &MainWindow::OnToggleFilterBar);
|
connect_menu(ui->action_Show_Filter_Bar, &MainWindow::OnToggleFilterBar);
|
||||||
connect_menu(ui->action_Show_Status_Bar, &MainWindow::OnToggleStatusBar);
|
connect_menu(ui->action_Show_Status_Bar, &MainWindow::OnToggleStatusBar);
|
||||||
connect_menu(ui->action_Show_Performance_Overlay, &MainWindow::OnTogglePerfOverlay);
|
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_720, &MainWindow::ResetWindowSize720);
|
||||||
connect_menu(ui->action_Reset_Window_Size_900, &MainWindow::ResetWindowSize900);
|
connect_menu(ui->action_Reset_Window_Size_900, &MainWindow::ResetWindowSize900);
|
||||||
@@ -3901,22 +3893,6 @@ void MainWindow::OnTogglePerfOverlay() {
|
|||||||
perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked());
|
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() {
|
void MainWindow::OnGameListRefresh() {
|
||||||
// Resets metadata cache and reloads
|
// Resets metadata cache and reloads
|
||||||
QtCommon::Game::ResetMetadata(false);
|
QtCommon::Game::ResetMetadata(false);
|
||||||
|
|||||||
@@ -56,9 +56,6 @@ class QSlider;
|
|||||||
class QHBoxLayout;
|
class QHBoxLayout;
|
||||||
class WaitTreeWidget;
|
class WaitTreeWidget;
|
||||||
class PerformanceOverlay;
|
class PerformanceOverlay;
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
class ConfigurePostProcessing;
|
|
||||||
#endif
|
|
||||||
enum class GameListOpenTarget;
|
enum class GameListOpenTarget;
|
||||||
enum class DumpRomFSTarget;
|
enum class DumpRomFSTarget;
|
||||||
class GameListPlaceholder;
|
class GameListPlaceholder;
|
||||||
@@ -394,9 +391,6 @@ private slots:
|
|||||||
void OnToggleFilterBar();
|
void OnToggleFilterBar();
|
||||||
void OnToggleStatusBar();
|
void OnToggleStatusBar();
|
||||||
void OnTogglePerfOverlay();
|
void OnTogglePerfOverlay();
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
void OnPostProcessingShaders();
|
|
||||||
#endif
|
|
||||||
void OnGameListRefresh();
|
void OnGameListRefresh();
|
||||||
void InitializeHotkeys();
|
void InitializeHotkeys();
|
||||||
void ToggleFullscreen();
|
void ToggleFullscreen();
|
||||||
@@ -501,9 +495,6 @@ private:
|
|||||||
QTimer shutdown_timer;
|
QTimer shutdown_timer;
|
||||||
OverlayDialog* shutdown_dialog{};
|
OverlayDialog* shutdown_dialog{};
|
||||||
PerformanceOverlay* perf_overlay = nullptr;
|
PerformanceOverlay* perf_overlay = nullptr;
|
||||||
#ifdef HAS_RESHADE
|
|
||||||
ConfigurePostProcessing* post_processing_dialog = nullptr;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
GameListPlaceholder* game_list_placeholder = nullptr;
|
GameListPlaceholder* game_list_placeholder = nullptr;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user