mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-09 13:36:35 +00:00
Refine quick settings fx shader + add presets + polish UI bugs :'v
This commit is contained in:
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
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
@@ -0,0 +1,3 @@
|
||||
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
@@ -0,0 +1,3 @@
|
||||
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
@@ -0,0 +1,3 @@
|
||||
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
@@ -0,0 +1,3 @@
|
||||
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
@@ -0,0 +1,3 @@
|
||||
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
+1
-1
@@ -59,7 +59,7 @@ float4 PS_Bloom(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
|
||||
technique Bloom <
|
||||
ui_label = "Bloom";
|
||||
ui_tooltip = "Blooms bright areas into a soft glow.";
|
||||
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
|
||||
|
||||
Vendored
+1
-1
@@ -63,7 +63,7 @@ float4 PS_CRT(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
|
||||
technique CRT <
|
||||
ui_label = "CRT";
|
||||
ui_tooltip = "Curved scanlines and the phosphor mask of a CRT television.";
|
||||
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
|
||||
|
||||
Vendored
+1
-1
@@ -68,7 +68,7 @@ float4 PS_Cartoon(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
|
||||
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.";
|
||||
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
|
||||
|
||||
Vendored
+1
-1
@@ -89,7 +89,7 @@ float4 PS_CartoonSoft(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Targe
|
||||
|
||||
technique CartoonSoft <
|
||||
ui_label = "Cartoon Soft";
|
||||
ui_tooltip = "Ink outlines and flat colour bands, with the outline held back in the shadows.";
|
||||
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
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ float4 PS_ChromaticAberration(float4 pos : SV_Position, float2 uv : TEXCOORD) :
|
||||
|
||||
technique ChromaticAberration <
|
||||
ui_label = "Chromatic Aberration";
|
||||
ui_tooltip = "Splits the colour channels apart towards the edges of the screen, like a cheap lens.";
|
||||
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
|
||||
|
||||
Vendored
+1
-1
@@ -53,7 +53,7 @@ float4 PS_ColorGrade(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
|
||||
technique ColorGrade <
|
||||
ui_label = "Colour Grade";
|
||||
ui_tooltip = "Global saturation, brightness, contrast and gamma.";
|
||||
ui_tooltip = "The four basic colour controls in one pass: saturation, brightness, contrast and gamma. Reach for this before anything more specialised.";
|
||||
>
|
||||
{
|
||||
pass
|
||||
|
||||
Vendored
+1
-1
@@ -80,7 +80,7 @@ float4 PS_Deband(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
|
||||
technique Deband <
|
||||
ui_label = "Deband";
|
||||
ui_tooltip = "Smooths the visible steps in gradients such as skies, then dithers whatever survives.";
|
||||
ui_tooltip = "Smooths the visible steps in gradients such as skies, then dithers whatever survives. Worth adding whenever large flat areas show rings.";
|
||||
>
|
||||
{
|
||||
pass
|
||||
|
||||
Vendored
+1
-1
@@ -72,7 +72,7 @@ float4 PS_Denoise(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
|
||||
technique Denoise <
|
||||
ui_label = "Denoise";
|
||||
ui_tooltip = "Edge preserving blur that clears dithering and compression noise. Ported from Anime4K.";
|
||||
ui_tooltip = "Edge preserving blur that clears dithering and compression noise while leaving outlines sharp. Ported from Anime4K.";
|
||||
>
|
||||
{
|
||||
pass
|
||||
|
||||
Vendored
+1
-1
@@ -49,7 +49,7 @@ float4 PS_Natural(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
|
||||
technique NaturalColors <
|
||||
ui_label = "Natural Colours";
|
||||
ui_tooltip = "Warmer and more saturated look. Ported from the PPSSPP shader.";
|
||||
ui_tooltip = "Warms the picture and lifts saturation for a less washed out look. Ported from the PPSSPP shader.";
|
||||
>
|
||||
{
|
||||
pass
|
||||
|
||||
Vendored
+1
-1
@@ -55,7 +55,7 @@ float4 PS_Scanlines(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
|
||||
technique Scanlines <
|
||||
ui_label = "Scanlines";
|
||||
ui_tooltip = "Horizontal scanlines of a CRT display.";
|
||||
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
|
||||
|
||||
Vendored
+1
-1
@@ -32,7 +32,7 @@ float4 PS_Sharpen(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
|
||||
technique Sharpen <
|
||||
ui_label = "Sharpen";
|
||||
ui_tooltip = "Unsharp mask that brings back edge detail lost to scaling.";
|
||||
ui_tooltip = "Unsharp mask that brings back edge detail lost to scaling. Most useful after upscaling a low internal resolution.";
|
||||
>
|
||||
{
|
||||
pass
|
||||
|
||||
Vendored
+1
-1
@@ -41,7 +41,7 @@ float4 PS_Vignette(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target
|
||||
|
||||
technique Vignette <
|
||||
ui_label = "Vignette";
|
||||
ui_tooltip = "Darkens the corners of the screen.";
|
||||
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
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.dialogs
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Rect
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
@@ -389,7 +391,8 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||
choices: List<String>,
|
||||
onPicked: (Int) -> Unit
|
||||
) {
|
||||
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
||||
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>(
|
||||
@@ -398,9 +401,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||
val choiceGroup = itemView.findViewById<RadioGroup>(R.id.add_choices)
|
||||
|
||||
choices.forEachIndexed { index, name ->
|
||||
val radioButton = com.google.android.material.radiobutton.MaterialRadioButton(
|
||||
emulationFragment.requireContext()
|
||||
)
|
||||
val radioButton = com.google.android.material.radiobutton.MaterialRadioButton(context)
|
||||
radioButton.text = name
|
||||
radioButton.id = View.generateViewId()
|
||||
radioButton.setPadding(16, 8, 16, 8)
|
||||
@@ -412,25 +413,113 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||
choiceGroup.addView(radioButton)
|
||||
}
|
||||
|
||||
val closedBackground = MaterialColors.getColor(
|
||||
button,
|
||||
com.google.android.material.R.attr.colorSecondaryContainer
|
||||
)
|
||||
val closedForeground = MaterialColors.getColor(
|
||||
button,
|
||||
com.google.android.material.R.attr.colorOnSecondaryContainer
|
||||
)
|
||||
val openBackground = MaterialColors.getColor(
|
||||
button,
|
||||
com.google.android.material.R.attr.colorErrorContainer
|
||||
)
|
||||
val openForeground = MaterialColors.getColor(
|
||||
button,
|
||||
com.google.android.material.R.attr.colorOnErrorContainer
|
||||
)
|
||||
|
||||
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.text = context.getString(R.string.post_processing_hide_effects)
|
||||
button.setIconResource(R.drawable.ic_clear)
|
||||
button.backgroundTintList = ColorStateList.valueOf(openBackground)
|
||||
button.setTextColor(openForeground)
|
||||
button.iconTint = ColorStateList.valueOf(openForeground)
|
||||
} else {
|
||||
choiceGroup.visibility = View.GONE
|
||||
choiceGroup.animate()
|
||||
.alpha(0.0f)
|
||||
.translationY(slide)
|
||||
.setDuration(160)
|
||||
.withEndAction {
|
||||
choiceGroup.visibility = View.GONE
|
||||
}
|
||||
.start()
|
||||
|
||||
button.text = context.getString(R.string.post_processing_add)
|
||||
button.setIconResource(R.drawable.ic_add)
|
||||
button.backgroundTintList = ColorStateList.valueOf(closedBackground)
|
||||
button.setTextColor(closedForeground)
|
||||
button.iconTint = ColorStateList.valueOf(closedForeground)
|
||||
}
|
||||
}
|
||||
|
||||
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 ->
|
||||
NativePostProcessing.setEnabled(checked)
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
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 =
|
||||
YuzuApplication.appContext.getString(R.string.post_processing_preset_locked)
|
||||
if (NativePostProcessing.isPresetModified()) {
|
||||
summary =
|
||||
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>()
|
||||
@@ -465,7 +554,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||
|
||||
val body = addShaderCard(title, summary, container) {
|
||||
NativePostProcessing.remove(index)
|
||||
NativePostProcessing.persist()
|
||||
NativePostProcessing.persistFor(emulationFragment.shouldUseCustom)
|
||||
onStructureChanged()
|
||||
}
|
||||
|
||||
@@ -478,7 +567,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||
|
||||
addEffectPicker(container, labels) { picked ->
|
||||
NativePostProcessing.append(files[picked], techniques[picked])
|
||||
NativePostProcessing.persist()
|
||||
NativePostProcessing.persistFor(emulationFragment.shouldUseCustom)
|
||||
onStructureChanged()
|
||||
}
|
||||
}
|
||||
@@ -519,7 +608,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||
component,
|
||||
uniform.min + position * uniform.step
|
||||
)
|
||||
NativePostProcessing.persist()
|
||||
NativePostProcessing.persistFor(emulationFragment.shouldUseCustom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// 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 CardSetting(
|
||||
@StringRes titleId: Int = 0,
|
||||
titleString: String = "",
|
||||
@StringRes descriptionId: Int = 0,
|
||||
descriptionString: String = "",
|
||||
val expanded: Boolean,
|
||||
val runnable: () -> Unit
|
||||
) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) {
|
||||
override val type = TYPE_CARD
|
||||
}
|
||||
+1
@@ -144,6 +144,7 @@ abstract class SettingsItem(
|
||||
const val TYPE_LAUNCHABLE = 13
|
||||
const val TYPE_PATH = 14
|
||||
const val TYPE_GPU_UNSWIZZLE = 15
|
||||
const val TYPE_CARD = 16
|
||||
|
||||
const val FASTMEM_COMBINED = "fastmem_combined"
|
||||
const val GPU_UNSWIZZLE_COMBINED = "gpu_unswizzle_combined"
|
||||
|
||||
+5
@@ -23,6 +23,7 @@ import com.google.android.material.timepicker.TimeFormat
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.SettingsNavigationDirections
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingCardBinding
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingInputBinding
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
|
||||
@@ -106,6 +107,10 @@ class SettingsAdapter(
|
||||
GpuUnswizzleViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_CARD -> {
|
||||
CardViewHolder(ListItemSettingCardBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
else -> {
|
||||
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
+94
-86
@@ -50,6 +50,10 @@ class SettingsFragmentPresenter(
|
||||
|
||||
private val expandedShaderSlots = mutableSetOf<Int>()
|
||||
|
||||
private var shaderPickerOpen = false
|
||||
|
||||
private var presetPickerOpen = false
|
||||
|
||||
private val context get() = YuzuApplication.appContext
|
||||
|
||||
// Extension for altering settings list based on each setting's properties
|
||||
@@ -212,7 +216,70 @@ class SettingsFragmentPresenter(
|
||||
return@apply
|
||||
}
|
||||
|
||||
val active = NativePostProcessing.getActivePreset()
|
||||
var presetSummary = context.getString(R.string.post_processing_preset_none)
|
||||
if (active.isNotEmpty()) {
|
||||
presetSummary = active
|
||||
if (NativePostProcessing.isPresetModified()) {
|
||||
presetSummary = context.getString(R.string.post_processing_preset_modified)
|
||||
}
|
||||
}
|
||||
|
||||
add(
|
||||
CardSetting(
|
||||
titleId = R.string.post_processing_preset,
|
||||
descriptionString = presetSummary,
|
||||
expanded = presetPickerOpen
|
||||
) {
|
||||
presetPickerOpen = !presetPickerOpen
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
|
||||
if (presetPickerOpen) {
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.post_processing_preset_none,
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.clearPreset()
|
||||
presetPickerOpen = false
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
for (preset in NativePostProcessing.presets()) {
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleString = preset.name,
|
||||
descriptionString = preset.description,
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.applyPreset(preset.name)
|
||||
NativePostProcessing.store()
|
||||
presetPickerOpen = false
|
||||
expandedShaderSlots.clear()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (active.isNotEmpty()) {
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleId = R.string.post_processing_preset_reset,
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.applyPreset(active)
|
||||
NativePostProcessing.store()
|
||||
expandedShaderSlots.clear()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val labels = mutableListOf<String>()
|
||||
val summaries = mutableListOf<String>()
|
||||
val files = mutableListOf<String>()
|
||||
val techniques = mutableListOf<String>()
|
||||
for (effect in usable) {
|
||||
@@ -222,6 +289,7 @@ class SettingsFragmentPresenter(
|
||||
} else {
|
||||
labels.add(effect.label + " \u00b7 " + technique)
|
||||
}
|
||||
summaries.add(effect.description)
|
||||
files.add(effect.file)
|
||||
techniques.add(technique)
|
||||
}
|
||||
@@ -243,17 +311,12 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
|
||||
val isOpen = expandedShaderSlots.contains(index)
|
||||
var chevron = R.drawable.ic_arrow_forward
|
||||
if (isOpen) {
|
||||
chevron = R.drawable.ic_dropdown_arrow
|
||||
}
|
||||
|
||||
add(
|
||||
RunnableSetting(
|
||||
CardSetting(
|
||||
titleString = header,
|
||||
descriptionString = summary,
|
||||
isRunnable = true,
|
||||
iconId = chevron
|
||||
expanded = isOpen
|
||||
) {
|
||||
if (isOpen) {
|
||||
expandedShaderSlots.remove(index)
|
||||
@@ -268,15 +331,6 @@ class SettingsFragmentPresenter(
|
||||
continue
|
||||
}
|
||||
|
||||
add(
|
||||
IntSingleChoiceSetting(
|
||||
buildSlotSelector(index, entry, files, techniques),
|
||||
titleId = R.string.post_processing_effect,
|
||||
choices = labels.toTypedArray(),
|
||||
values = labels.indices.toList().toTypedArray()
|
||||
)
|
||||
)
|
||||
|
||||
if (effect != null) {
|
||||
for (uniform in effect.uniforms) {
|
||||
addUniform(this, index, uniform)
|
||||
@@ -290,7 +344,7 @@ class SettingsFragmentPresenter(
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.move(index, -1)
|
||||
NativePostProcessing.persist()
|
||||
NativePostProcessing.store()
|
||||
expandedShaderSlots.clear()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
@@ -303,7 +357,7 @@ class SettingsFragmentPresenter(
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.move(index, 1)
|
||||
NativePostProcessing.persist()
|
||||
NativePostProcessing.store()
|
||||
expandedShaderSlots.clear()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
@@ -315,7 +369,7 @@ class SettingsFragmentPresenter(
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.resetValues(index)
|
||||
NativePostProcessing.persist()
|
||||
NativePostProcessing.store()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
@@ -325,7 +379,7 @@ class SettingsFragmentPresenter(
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.remove(index)
|
||||
NativePostProcessing.persist()
|
||||
NativePostProcessing.store()
|
||||
expandedShaderSlots.clear()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
@@ -333,78 +387,32 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
|
||||
add(
|
||||
IntSingleChoiceSetting(
|
||||
buildAddSelector(files, techniques),
|
||||
CardSetting(
|
||||
titleId = R.string.post_processing_add,
|
||||
choices = labels.toTypedArray(),
|
||||
values = labels.indices.toList().toTypedArray()
|
||||
)
|
||||
expanded = shaderPickerOpen
|
||||
) {
|
||||
shaderPickerOpen = !shaderPickerOpen
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildAddSelector(
|
||||
files: List<String>,
|
||||
techniques: List<String>
|
||||
): AbstractIntSetting = object : AbstractIntSetting {
|
||||
override val key = "fx_add"
|
||||
|
||||
override fun getInt(needsGlobal: Boolean): Int = -1
|
||||
|
||||
override fun setInt(value: Int) {
|
||||
if (value < 0 || value >= files.size) {
|
||||
return
|
||||
}
|
||||
NativePostProcessing.append(files[value], techniques[value])
|
||||
NativePostProcessing.persist()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
|
||||
override val defaultValue = -1
|
||||
override fun getValueAsString(needsGlobal: Boolean): String = ""
|
||||
override fun reset() {}
|
||||
override val isRuntimeModifiable = true
|
||||
override val pairedSettingKey = ""
|
||||
override val isSwitchable = false
|
||||
override val isSaveable = true
|
||||
override var global: Boolean
|
||||
get() = true
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
private fun buildSlotSelector(
|
||||
index: Int,
|
||||
entry: NativePostProcessing.ChainEntry,
|
||||
files: List<String>,
|
||||
techniques: List<String>
|
||||
): AbstractIntSetting = object : AbstractIntSetting {
|
||||
override val key = "fx_slot_$index"
|
||||
|
||||
override fun getInt(needsGlobal: Boolean): Int {
|
||||
for (i in files.indices) {
|
||||
if (files[i] == entry.file && techniques[i] == entry.technique) {
|
||||
return i
|
||||
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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
override fun setInt(value: Int) {
|
||||
NativePostProcessing.replace(index, files[value], techniques[value])
|
||||
NativePostProcessing.persist()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
|
||||
override val defaultValue = 0
|
||||
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
|
||||
override fun reset() {}
|
||||
override val isRuntimeModifiable = true
|
||||
override val pairedSettingKey = ""
|
||||
override val isSwitchable = false
|
||||
override val isSaveable = true
|
||||
override var global: Boolean
|
||||
get() = true
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
private fun addUniform(
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// 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.ListItemSettingCardBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.CardSetting
|
||||
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 CardViewHolder(val binding: ListItemSettingCardBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var setting: CardSetting
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as CardSetting
|
||||
|
||||
binding.textCardName.text = item.title
|
||||
binding.textCardDescription.text = item.description
|
||||
binding.textCardDescription.setVisible(item.description.isNotEmpty())
|
||||
|
||||
var rotation = 0f
|
||||
if (setting.expanded) {
|
||||
rotation = 180f
|
||||
}
|
||||
binding.cardExpand.rotation = rotation
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
val target = binding.cardExpand
|
||||
var rotation = 180f
|
||||
if (setting.expanded) {
|
||||
rotation = 0f
|
||||
}
|
||||
target.animate().rotation(rotation).setDuration(200).start()
|
||||
setting.runnable.invoke()
|
||||
}
|
||||
|
||||
override fun onLongClick(clicked: View): Boolean = true
|
||||
}
|
||||
@@ -42,11 +42,40 @@ object NativePostProcessing {
|
||||
|
||||
external fun store()
|
||||
|
||||
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() {
|
||||
store()
|
||||
NativeConfig.saveGlobalConfig()
|
||||
}
|
||||
|
||||
fun persistFor(perGame: Boolean) {
|
||||
store()
|
||||
if (perGame) {
|
||||
NativeConfig.savePerGameConfig()
|
||||
} else {
|
||||
NativeConfig.saveGlobalConfig()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
external fun getShaderDirectory(): String
|
||||
|
||||
@@ -100,6 +129,28 @@ object NativePostProcessing {
|
||||
|
||||
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())
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
#include "common/android/android_common.h"
|
||||
#ifdef HAS_RESHADE
|
||||
#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"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
@@ -212,4 +214,101 @@ jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getShaderDirectory(JN
|
||||
#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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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"
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?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:clickable="true"
|
||||
android:focusable="true"
|
||||
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/text_card_name"
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_card_description"
|
||||
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/card_expand"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="16dp"
|
||||
android:contentDescription=""
|
||||
android:src="@drawable/ic_dropdown_arrow"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -301,9 +301,14 @@
|
||||
<string name="post_processing">Post-Processing Effects</string>
|
||||
<string name="post_processing_description">ReShade FX effects applied after rendering</string>
|
||||
<string name="post_processing_per_game_description">Configure the effect chain for this game</string>
|
||||
<string name="post_processing_effect">Effect</string>
|
||||
<string name="post_processing_add">Add effect</string>
|
||||
<string name="post_processing_remove">Remove</string>
|
||||
<string name="post_processing_hide_effects">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">Preset active, with your own changes.</string>
|
||||
<string name="post_processing_preset">Preset</string>
|
||||
<string name="post_processing_preset_none">No preset</string>
|
||||
<string name="post_processing_preset_reset">Reset preset values</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>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#define LOSSLESS_DIR "lossless"
|
||||
#define NAND_DIR "nand"
|
||||
#define PLAY_TIME_DIR "play_time"
|
||||
#define POST_PRESET_DIR "post_presets"
|
||||
#define POST_SHADER_DIR "post_shaders"
|
||||
#define SCREENSHOTS_DIR "screenshots"
|
||||
#define SDMC_DIR "sdmc"
|
||||
|
||||
@@ -160,6 +160,7 @@ public:
|
||||
GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR);
|
||||
GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR);
|
||||
GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR);
|
||||
GenerateEdenPath(EdenPath::PostPresetDir, eden_path / POST_PRESET_DIR);
|
||||
GenerateEdenPath(EdenPath::PostShaderDir, eden_path / POST_SHADER_DIR);
|
||||
GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR);
|
||||
GenerateEdenPath(EdenPath::ScreenshotsDir, eden_path / SCREENSHOTS_DIR);
|
||||
|
||||
@@ -26,6 +26,7 @@ enum class EdenPath {
|
||||
LosslessDir, // Where the user-supplied Lossless Scaling library is stored.
|
||||
NANDDir, // Where the emulated NAND is stored.
|
||||
PlayTimeDir, // Where play time data is stored.
|
||||
PostPresetDir,
|
||||
PostShaderDir, // Where user post-processing shaders are stored.
|
||||
SaveDir, // Where save data is stored.
|
||||
ScreenshotsDir, // Where yuzu screenshots are stored.
|
||||
|
||||
+23
-7
@@ -388,13 +388,29 @@ struct Values {
|
||||
true,
|
||||
true};
|
||||
|
||||
Setting<std::string> post_shader_chain{linkage,
|
||||
std::string(),
|
||||
"post_shader_chain",
|
||||
Category::Renderer,
|
||||
Specialization::Default,
|
||||
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,
|
||||
Specialization::Default, true, false};
|
||||
|
||||
@@ -312,6 +312,18 @@ if (ENABLE_RESHADE)
|
||||
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
|
||||
@@ -319,9 +331,12 @@ if (ENABLE_RESHADE)
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# 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
|
||||
")
|
||||
@@ -204,6 +204,14 @@ void FxChain::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()) {
|
||||
|
||||
@@ -51,6 +51,8 @@ public:
|
||||
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
// 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()) != preset->chain;
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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
|
||||
@@ -235,8 +235,10 @@ void Layer::SetPostProcessPass(const Device& device) {
|
||||
};
|
||||
|
||||
const u64 generation = VideoCore::FxChain::Instance().Snapshot().generation;
|
||||
const bool enabled = Settings::values.post_shader_enabled.GetValue();
|
||||
|
||||
if (post_process_generation == generation && post_process_extent.width == render_area.width &&
|
||||
if (post_process_generation == generation && post_process_enabled == enabled &&
|
||||
post_process_extent.width == render_area.width &&
|
||||
post_process_extent.height == render_area.height) {
|
||||
return;
|
||||
}
|
||||
@@ -246,10 +248,11 @@ void Layer::SetPostProcessPass(const Device& device) {
|
||||
}
|
||||
|
||||
post_process_generation = generation;
|
||||
post_process_enabled = enabled;
|
||||
post_process_extent = render_area;
|
||||
post_process.reset();
|
||||
|
||||
if (VideoCore::FxChain::Instance().Size() == 0) {
|
||||
if (!enabled || VideoCore::FxChain::Instance().Size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ private:
|
||||
#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{};
|
||||
|
||||
@@ -187,7 +187,7 @@ try
|
||||
}
|
||||
|
||||
#ifdef HAS_RESHADE
|
||||
VideoCore::FxChain::Instance().EnsureLoadedFromSettings();
|
||||
VideoCore::FxChain::Instance().LoadFromSettings();
|
||||
#endif
|
||||
|
||||
Report();
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFrame>
|
||||
#include <QGridLayout>
|
||||
#include <QInputDialog>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMenu>
|
||||
@@ -20,8 +21,10 @@
|
||||
#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 {
|
||||
@@ -74,6 +77,75 @@ ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(pare
|
||||
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);
|
||||
@@ -112,8 +184,12 @@ ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(pare
|
||||
root->addWidget(buttons);
|
||||
|
||||
VideoCore::ReloadFxCatalog();
|
||||
VideoCore::ReloadFxPresetCatalog();
|
||||
VideoCore::FxChain::Instance().LoadFromSettings();
|
||||
VideoCore::FxChain::Instance().DropUnknownEntries();
|
||||
PopulatePresetCombo();
|
||||
RebuildRows();
|
||||
RefreshPresetStatus();
|
||||
}
|
||||
|
||||
ConfigurePostProcessing::~ConfigurePostProcessing() = default;
|
||||
@@ -121,6 +197,47 @@ 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,
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QLabel;
|
||||
class QVBoxLayout;
|
||||
class QWidget;
|
||||
|
||||
@@ -29,7 +31,12 @@ private:
|
||||
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{};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user