Adjustment on the design of UI + new shaders

This commit is contained in:
CamilleLaVey
2026-09-07 17:58:46 -04:00
parent fcaea7d502
commit 7eda109134
29 changed files with 774 additions and 63 deletions
+4 -1
View File
@@ -65,7 +65,10 @@ float4 PS_Bloom(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
return float4(saturate(color + sum * Amount), 1.0);
}
technique Bloom
technique Bloom <
ui_label = "Bloom";
ui_tooltip = "Blooms bright areas into a soft glow.";
>
{
pass
{
+4 -1
View File
@@ -69,7 +69,10 @@ float4 PS_CRT(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
return float4(saturate(color), 1.0);
}
technique CRT
technique CRT <
ui_label = "CRT";
ui_tooltip = "Curved scanlines and the phosphor mask of a CRT television.";
>
{
pass
{
+4 -1
View File
@@ -74,7 +74,10 @@ float4 PS_Cartoon(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
return float4(saturate(color), 1.0);
}
technique Cartoon
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.";
>
{
pass
{
+4 -1
View File
@@ -95,7 +95,10 @@ float4 PS_CartoonSoft(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Targe
return float4(saturate(color), 1.0);
}
technique CartoonSoft
technique CartoonSoft <
ui_label = "Cartoon Soft";
ui_tooltip = "Ink outlines and flat colour bands, with the outline held back in the shadows.";
>
{
pass
{
+4 -1
View File
@@ -48,7 +48,10 @@ float4 PS_ChromaticAberration(float4 pos : SV_Position, float2 uv : TEXCOORD) :
return float4(red, green, blue, 1.0);
}
technique ChromaticAberration
technique ChromaticAberration <
ui_label = "Chromatic Aberration";
ui_tooltip = "Splits the colour channels apart towards the edges of the screen, like a cheap lens.";
>
{
pass
{
+4 -1
View File
@@ -59,7 +59,10 @@ float4 PS_ColorGrade(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
return float4(saturate(rgb), 1.0);
}
technique ColorGrade
technique ColorGrade <
ui_label = "Colour Grade";
ui_tooltip = "Global saturation, brightness, contrast and gamma.";
>
{
pass
{
+4 -1
View File
@@ -86,7 +86,10 @@ float4 PS_Deband(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
return float4(saturate(result + dither), 1.0);
}
technique Deband
technique Deband <
ui_label = "Deband";
ui_tooltip = "Smooths the visible steps in gradients such as skies, then dithers whatever survives.";
>
{
pass
{
+4 -1
View File
@@ -78,7 +78,10 @@ float4 PS_Denoise(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
return float4(sum / total, 1.0);
}
technique Denoise
technique Denoise <
ui_label = "Denoise";
ui_tooltip = "Edge preserving blur that clears dithering and compression noise. Ported from Anime4K.";
>
{
pass
{
+4 -1
View File
@@ -61,7 +61,10 @@ float4 PS_FakeReflections(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_T
return float4(saturate(color + reflection), 1.0);
}
technique FakeReflections
technique FakeReflections <
ui_label = "Fake Reflections";
ui_tooltip = "Adds a wet looking reflection across the lower half of the screen.";
>
{
pass
{
+73
View File
@@ -0,0 +1,73 @@
// 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}
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;
}
}
+68
View File
@@ -0,0 +1,68 @@
// 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}
float4 PS_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;
}
}
+61
View File
@@ -0,0 +1,61 @@
// 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}
float4 PS_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;
}
}
+74
View File
@@ -0,0 +1,74 @@
// 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}
float4 PS_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;
}
}
+4 -1
View File
@@ -55,7 +55,10 @@ float4 PS_Natural(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
return float4(saturate(mul(YIQtoRGB, yiq)), 1.0);
}
technique NaturalColors
technique NaturalColors <
ui_label = "Natural Colours";
ui_tooltip = "Warmer and more saturated look. Ported from the PPSSPP shader.";
>
{
pass
{
+4 -1
View File
@@ -61,7 +61,10 @@ float4 PS_Scanlines(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
return float4(saturate(color * saturate(gate)), 1.0);
}
technique Scanlines
technique Scanlines <
ui_label = "Scanlines";
ui_tooltip = "Horizontal scanlines of a CRT display.";
>
{
pass
{
+4 -1
View File
@@ -38,7 +38,10 @@ float4 PS_Sharpen(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
return float4(centre + (centre - blur) * Amount, 1.0);
}
technique Sharpen
technique Sharpen <
ui_label = "Sharpen";
ui_tooltip = "Unsharp mask that brings back edge detail lost to scaling.";
>
{
pass
{
+83
View File
@@ -0,0 +1,83 @@
// 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}
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;
}
}
+4 -1
View File
@@ -47,7 +47,10 @@ float4 PS_Vignette(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
return float4(rgb * falloff, 1.0);
}
technique Vignette
technique Vignette <
ui_label = "Vignette";
ui_tooltip = "Darkens the corners of the screen.";
>
{
pass
{
+84
View File
@@ -0,0 +1,84 @@
// 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(0.0, 0.0);
if (id == 2)
{
uv.x = 2.0;
}
if (id == 1)
{
uv.y = 2.0;
}
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}
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;
}
}
@@ -18,6 +18,7 @@ import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.fragments.EmulationFragment
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.AbstractShortSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
@@ -232,6 +233,250 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
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,
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)
}
}
slider.setOnTouchListener { _, event ->
val drawer = emulationFragment.view?.findViewById<DrawerLayout>(R.id.drawer_layout)
when (event.action) {
MotionEvent.ACTION_DOWN -> {
drawer?.requestDisallowInterceptTouchEvent(true)
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
drawer?.requestDisallowInterceptTouchEvent(false)
}
}
false
}
container.addView(itemView)
}
fun addPostProcessing(container: ViewGroup, onStructureChanged: () -> Unit) {
val usable = NativePostProcessing.catalog().filter { it.valid }
if (usable.isEmpty()) {
return
}
val labels = mutableListOf(
YuzuApplication.appContext.getString(R.string.post_processing_none)
)
val files = mutableListOf("")
val techniques = mutableListOf("")
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()
for (index in 0..chain.size) {
var selected = 0
var effect: NativePostProcessing.Effect? = null
if (index < chain.size) {
val entry = chain[index]
effect = usable.firstOrNull { it.file == entry.file }
for (i in files.indices) {
if (files[i] == entry.file && techniques[i] == entry.technique) {
selected = i
}
}
}
var title = YuzuApplication.appContext.getString(R.string.post_processing_add)
if (index < chain.size) {
title = YuzuApplication.appContext.getString(R.string.post_processing_effect)
}
addChoice(title, container, labels, selected) { picked ->
applyEffectPick(index, picked, files, techniques, chain.size)
onStructureChanged()
}
if (effect != null) {
for (uniform in effect.uniforms) {
addUniformSliders(container, index, uniform)
}
}
}
}
private fun applyEffectPick(
index: Int,
picked: Int,
files: List<String>,
techniques: List<String>,
chainSize: Int
) {
if (index >= chainSize) {
if (picked > 0) {
NativePostProcessing.append(files[picked], techniques[picked])
NativePostProcessing.persist()
}
return
}
if (picked == 0) {
NativePostProcessing.remove(index)
NativePostProcessing.persist()
return
}
NativePostProcessing.replace(index, files[picked], techniques[picked])
NativePostProcessing.persist()
}
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 -> describeUniform(uniform, position) }
) { position ->
NativePostProcessing.setValue(
index,
uniform.name,
component,
uniform.min + position * uniform.step
)
NativePostProcessing.persist()
}
}
}
private fun describeUniform(
uniform: NativePostProcessing.Uniform,
position: Int
): String {
val value = uniform.min + position * uniform.step
if (uniform.kind == NativePostProcessing.KIND_FLOAT) {
return String.format("%.3f", value)
}
return Math.round(value).toString()
}
fun addDivider(container: ViewGroup) {
val inflater = LayoutInflater.from(emulationFragment.requireContext())
val dividerView = inflater.inflate(R.layout.item_quick_settings_divider, container, false)
@@ -199,17 +199,6 @@ class SettingsFragmentPresenter(
val usable = NativePostProcessing.catalog().filter { it.valid }
sl.apply {
add(
RunnableSetting(
titleId = R.string.post_processing_reload,
descriptionId = R.string.post_processing_reload_description,
isRunnable = true
) {
NativePostProcessing.reload()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
if (usable.isEmpty()) {
add(
RunnableSetting(
@@ -227,9 +216,9 @@ class SettingsFragmentPresenter(
for (effect in usable) {
for (technique in effect.techniques) {
if (effect.techniques.size == 1) {
labels.add(effect.name)
labels.add(effect.label)
} else {
labels.add(effect.name + " \u00b7 " + technique)
labels.add(effect.label + " \u00b7 " + technique)
}
files.add(effect.file)
techniques.add(technique)
@@ -242,8 +231,10 @@ class SettingsFragmentPresenter(
val effect = usable.firstOrNull { it.file == entry.file }
var header = entry.file
var summary = ""
if (effect != null) {
header = effect.name
header = effect.label
summary = effect.description
}
add(HeaderSetting(titleString = header))
@@ -251,6 +242,7 @@ class SettingsFragmentPresenter(
IntSingleChoiceSetting(
buildSlotSelector(index, entry, files, techniques),
titleId = R.string.post_processing_effect,
descriptionString = summary,
choices = labels.toTypedArray(),
values = labels.indices.toList().toTypedArray()
)
@@ -1194,6 +1194,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.array.rendererAntiAliasingNames,
R.array.rendererAntiAliasingValues
)
quickSettings.addPostProcessing(container) {
addQuickSettings()
}
}
}
@@ -47,7 +47,6 @@ object NativePostProcessing {
NativeConfig.saveGlobalConfig()
}
external fun reload()
external fun getShaderDirectory(): String
@@ -89,6 +88,8 @@ object NativePostProcessing {
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>
@@ -108,6 +109,8 @@ object NativePostProcessing {
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()
@@ -65,10 +65,14 @@ jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getCatalogJson(JNIEnv
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;
@@ -199,13 +203,6 @@ void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_store(JNIEnv* env, jobje
#endif
}
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_reload(JNIEnv* env, jobject obj) {
#ifdef HAS_RESHADE
VideoCore::ReloadFxCatalog();
VideoCore::FxChain::Instance().DropUnknownEntries();
#endif
}
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getShaderDirectory(JNIEnv* env,
jobject obj) {
#ifdef HAS_RESHADE
@@ -302,13 +302,12 @@
<string name="post_processing_description">ReShade FX effects applied after rendering</string>
<string name="post_processing_per_game_description">Configure the effect chain for this game</string>
<string name="post_processing_effect">Effect</string>
<string name="post_processing_none">None</string>
<string name="post_processing_add">Add effect</string>
<string name="post_processing_remove">Remove</string>
<string name="post_processing_move_up">Move up</string>
<string name="post_processing_move_down">Move down</string>
<string name="post_processing_reset">Reset to defaults</string>
<string name="post_processing_reload">Reload from disk</string>
<string name="post_processing_reload_description">Rescan the shader folder for .fx files</string>
<string name="post_processing_empty">No effects found. Place .fx files in this folder:</string>
<string name="frame_gen">Frame generation</string>
<string name="frame_gen_per_game_description">Configure frame generation for this game</string>
+1 -4
View File
@@ -94,10 +94,7 @@ std::string SerializeFxChain(std::span<const FxChainEntry> entries) {
if (!out.empty()) {
out += ';';
}
out += entry.file;
out += '|';
out += entry.technique;
out += '|';
out += fmt::format("{}|{}|", entry.file, entry.technique);
bool first = true;
for (const auto& [name, value] : entry.values) {
@@ -176,6 +176,15 @@ FxEffectDesc DescribeEffect(const std::filesystem::path& path, const std::filesy
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) {
@@ -48,6 +48,8 @@ struct FxUniformDesc {
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;
@@ -7,7 +7,6 @@
#include <QCheckBox>
#include <QComboBox>
#include <QDesktopServices>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
@@ -17,11 +16,8 @@
#include <QScrollArea>
#include <QSlider>
#include <QToolButton>
#include <QUrl>
#include <QVBoxLayout>
#include "common/fs/fs.h"
#include "common/fs/fs_util.h"
#include "video_core/post_processing/fx_chain.h"
#include "video_core/post_processing/fx_effect.h"
#include "yuzu/configuration/configure_post_processing.h"
@@ -53,7 +49,7 @@ QString FormatValue(const VideoCore::FxUniformDesc& uniform, float value) {
}
QString SlotLabel(const VideoCore::FxEffectDesc& effect, const std::string& technique) {
const QString name = QString::fromStdString(effect.name);
const QString name = QString::fromStdString(effect.label);
if (effect.techniques.size() == 1) {
return name;
}
@@ -99,23 +95,6 @@ ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(pare
});
actions->addWidget(add_button);
auto* reload_button = new QPushButton(tr("Reload From Disk"), this);
connect(reload_button, &QPushButton::clicked, this, [this]() {
VideoCore::ReloadFxCatalog();
VideoCore::FxChain::Instance().DropUnknownEntries();
ApplyStructuralChange();
});
actions->addWidget(reload_button);
auto* open_button = new QPushButton(tr("Open Folder"), this);
connect(open_button, &QPushButton::clicked, this, []() {
const auto path = VideoCore::GetFxRootDirectory();
void(Common::FS::CreateDirs(path));
QDesktopServices::openUrl(
QUrl::fromLocalFile(QString::fromStdString(Common::FS::PathToUTF8String(path))));
});
actions->addWidget(open_button);
actions->addStretch();
root->addLayout(actions);
@@ -147,6 +126,8 @@ void ConfigurePostProcessing::PopulateEffectCombo(QComboBox* combo,
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;
}