eliminades referencies a opengl

This commit is contained in:
2026-03-23 17:04:21 +01:00
parent 58512840a4
commit 270cd1d487
19 changed files with 104 additions and 80 deletions

View File

@@ -75,14 +75,6 @@ SOUND|/data/sound/voice_recover.wav
SOUND|/data/sound/voice_thankyou.wav
SOUND|/data/sound/walk.wav
# Shaders OpenGL Desktop 3.3 (Windows/Linux)
DATA|/data/shaders/crtpi_vertex.glsl
DATA|/data/shaders/crtpi_fragment.glsl
# Shaders OpenGL ES 3.0 (Raspberry Pi) - opcionales
DATA|/data/shaders/crtpi_vertex_es.glsl|optional
DATA|/data/shaders/crtpi_fragment_es.glsl|optional
# Texturas - Balloons
ANIMATION|/data/gfx/balloon/balloon0.ani
ANIMATION|/data/gfx/balloon/balloon1.ani

View File

@@ -79,7 +79,9 @@
"[SERVICE_MENU] SHUTDOWN": "Apagar el sistema",
"[SERVICE_MENU] FULLSCREEN": "Pantalla completa",
"[SERVICE_MENU] WINDOW_SIZE": "Tamany de la finestra",
"[SERVICE_MENU] SHADERS": "Filtre",
"[SERVICE_MENU] POSTFX": "PostFX",
"[SERVICE_MENU] POSTFX_PRESET": "Preset PostFX",
"[SERVICE_MENU] SUPERSAMPLING": "Supermostreig",
"[SERVICE_MENU] VSYNC": "Sincronisme vertical",
"[SERVICE_MENU] INTEGER_SCALE": "Escalat sencer",
"[SERVICE_MENU] MAIN_VOLUME": "Volumen general",

View File

@@ -78,7 +78,9 @@
"[SERVICE_MENU] SHUTDOWN": "Shutdown System",
"[SERVICE_MENU] FULLSCREEN": "Fullscreen",
"[SERVICE_MENU] WINDOW_SIZE": "Window Zoom",
"[SERVICE_MENU] SHADERS": "Shaders",
"[SERVICE_MENU] POSTFX": "PostFX",
"[SERVICE_MENU] POSTFX_PRESET": "PostFX Preset",
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
"[SERVICE_MENU] VSYNC": "V-Sync",
"[SERVICE_MENU] INTEGER_SCALE": "Integer Scale",
"[SERVICE_MENU] MAIN_VOLUME": "Main Volume",

View File

@@ -78,7 +78,9 @@
"[SERVICE_MENU] SHUTDOWN": "Apagar el sistema",
"[SERVICE_MENU] FULLSCREEN": "Pantalla completa",
"[SERVICE_MENU] WINDOW_SIZE": "Zoom de ventana",
"[SERVICE_MENU] SHADERS": "Filtro grafico",
"[SERVICE_MENU] POSTFX": "PostFX",
"[SERVICE_MENU] POSTFX_PRESET": "Preset PostFX",
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
"[SERVICE_MENU] VSYNC": "Sincronismo vertical",
"[SERVICE_MENU] INTEGER_SCALE": "Escalado proporcional",
"[SERVICE_MENU] MAIN_VOLUME": "Volumen general",

View File

@@ -97,16 +97,14 @@ void main() {
}
// Scanlines — 1 pixel físico oscuro por fila lógica.
// Usa uv.y (independiente del offset de letterbox) con pixel_scale para
// calcular la posición dentro de la fila en coordenadas físicas.
// 3x: 1 dark + 2 bright. 4x: 1 dark + 3 bright.
// bright=3.5×, dark floor=0.42 (mantiene aspecto CRT original).
// Modelo sustractivo: las filas de scanline se oscurecen, las demás no cambian.
// Esto evita el efecto de sobrebrillo en contenido con colores vivos.
if (u.scanline_strength > 0.0) {
float ps = max(1.0, round(u.pixel_scale));
float frac_in_row = fract(uv.y * u.screen_height);
float row_pos = floor(frac_in_row * ps);
float is_dark = step(ps - 1.0, row_pos);
float scan = mix(3.5, 0.42, is_dark);
float scan = mix(1.0, 0.0, is_dark);
colour *= mix(1.0, scan, u.scanline_strength);
}

View File

@@ -220,9 +220,8 @@ namespace GameDefaults {
constexpr bool VIDEO_FULLSCREEN = false;
constexpr bool VIDEO_VSYNC = true;
constexpr bool VIDEO_INTEGER_SCALE = true;
constexpr bool VIDEO_SHADERS = false;
constexpr bool VIDEO_POSTFX = false;
constexpr bool VIDEO_SUPERSAMPLING = false;
constexpr int VIDEO_SUPERSAMPLING = 1; // 1 = off, 2 = 2×, 3 = 3× SS
// Music
constexpr bool MUSIC_ENABLED = true;

View File

@@ -54,7 +54,7 @@ namespace GlobalEvents {
break;
case SDL_EVENT_WINDOW_RESIZED:
Screen::initShaders();
Screen::initPostFX();
break;
default:

View File

@@ -78,7 +78,9 @@ namespace GlobalInputs {
// Activa o desactiva el supersampling 3x
void toggleSupersampling() {
Screen::toggleSupersampling();
Notifier::get()->show({"3x SS: " + boolToOnOff(Options::video.supersampling)});
const int SS = Options::video.supersampling;
const std::string SS_LABEL = (SS <= 1) ? "OFF" : (std::to_string(SS) + "\xC3\x97");
Notifier::get()->show({"SS: " + SS_LABEL});
}
// Cambia al siguiente idioma
@@ -199,15 +201,16 @@ namespace GlobalInputs {
return true;
}
// F4 con modificadores: Ctrl+F4 = supersampling, Shift+F4 = siguiente preset, F4 = toggle PostFX
if (Input::get()->checkAction(Input::Action::TOGGLE_VIDEO_POSTFX, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
if ((SDL_GetModState() & SDL_KMOD_CTRL) != 0U) {
toggleSupersampling();
} else if (Options::video.postfx && ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0U)) {
nextPostFXPreset();
} else {
togglePostFX();
}
togglePostFX();
return true;
}
if (Input::get()->checkAction(Input::Action::NEXT_POSTFX_PRESET, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
nextPostFXPreset();
return true;
}
if (Input::get()->checkAction(Input::Action::TOGGLE_SUPERSAMPLING, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
toggleSupersampling();
return true;
}

View File

@@ -81,8 +81,9 @@ class Input {
{Action::WINDOW_DEC_SIZE, KeyState(SDL_SCANCODE_F1)},
{Action::WINDOW_INC_SIZE, KeyState(SDL_SCANCODE_F2)},
{Action::WINDOW_FULLSCREEN, KeyState(SDL_SCANCODE_F3)},
{Action::TOGGLE_VIDEO_SHADERS, KeyState(SDL_SCANCODE_F4)},
{Action::TOGGLE_VIDEO_POSTFX, KeyState(SDL_SCANCODE_F4)},
{Action::NEXT_POSTFX_PRESET, KeyState(SDL_SCANCODE_9)},
{Action::TOGGLE_SUPERSAMPLING, KeyState(SDL_SCANCODE_0)},
{Action::TOGGLE_VIDEO_INTEGER_SCALE, KeyState(SDL_SCANCODE_F5)},
{Action::TOGGLE_VIDEO_VSYNC, KeyState(SDL_SCANCODE_F6)},

View File

@@ -21,8 +21,9 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
{InputAction::WINDOW_FULLSCREEN, "WINDOW_FULLSCREEN"},
{InputAction::WINDOW_INC_SIZE, "WINDOW_INC_SIZE"},
{InputAction::WINDOW_DEC_SIZE, "WINDOW_DEC_SIZE"},
{InputAction::TOGGLE_VIDEO_SHADERS, "TOGGLE_VIDEO_SHADERS"},
{InputAction::TOGGLE_VIDEO_POSTFX, "TOGGLE_VIDEO_POSTFX"},
{InputAction::NEXT_POSTFX_PRESET, "NEXT_POSTFX_PRESET"},
{InputAction::TOGGLE_SUPERSAMPLING, "TOGGLE_SUPERSAMPLING"},
{InputAction::TOGGLE_VIDEO_INTEGER_SCALE, "TOGGLE_VIDEO_INTEGER_SCALE"},
{InputAction::TOGGLE_VIDEO_VSYNC, "TOGGLE_VIDEO_VSYNC"},
{InputAction::RESET, "RESET"},
@@ -52,8 +53,9 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
{"WINDOW_FULLSCREEN", InputAction::WINDOW_FULLSCREEN},
{"WINDOW_INC_SIZE", InputAction::WINDOW_INC_SIZE},
{"WINDOW_DEC_SIZE", InputAction::WINDOW_DEC_SIZE},
{"TOGGLE_VIDEO_SHADERS", InputAction::TOGGLE_VIDEO_SHADERS},
{"TOGGLE_VIDEO_POSTFX", InputAction::TOGGLE_VIDEO_POSTFX},
{"NEXT_POSTFX_PRESET", InputAction::NEXT_POSTFX_PRESET},
{"TOGGLE_SUPERSAMPLING", InputAction::TOGGLE_SUPERSAMPLING},
{"TOGGLE_VIDEO_INTEGER_SCALE", InputAction::TOGGLE_VIDEO_INTEGER_SCALE},
{"TOGGLE_VIDEO_VSYNC", InputAction::TOGGLE_VIDEO_VSYNC},
{"RESET", InputAction::RESET},

View File

@@ -31,8 +31,9 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
WINDOW_FULLSCREEN,
WINDOW_INC_SIZE,
WINDOW_DEC_SIZE,
TOGGLE_VIDEO_SHADERS, // backward compat alias
TOGGLE_VIDEO_POSTFX,
NEXT_POSTFX_PRESET,
TOGGLE_SUPERSAMPLING,
TOGGLE_VIDEO_INTEGER_SCALE,
TOGGLE_VIDEO_VSYNC,
RESET,

View File

@@ -245,19 +245,41 @@ namespace Options {
if (vid.contains("integer_scale")) {
try { video.integer_scale = vid["integer_scale"].get_value<bool>(); } catch (...) {}
}
if (vid.contains("shaders")) {
try { video.shaders = vid["shaders"].get_value<bool>(); } catch (...) {}
}
if (vid.contains("postfx")) {
try { video.postfx = vid["postfx"].get_value<bool>(); } catch (...) {}
}
if (vid.contains("supersampling")) {
try { video.supersampling = vid["supersampling"].get_value<bool>(); } catch (...) {}
// Nuevo formato: supersampling (bool) + supersampling_amount (int)
// Backward compat: si solo existe supersampling como int, también funciona
{
bool ss_enabled = false;
int ss_amount = 3;
if (vid.contains("supersampling")) {
try {
const auto& node = vid["supersampling"];
if (node.is_boolean()) {
ss_enabled = node.get_value<bool>();
} else {
// Formato antiguo: int directamente
int factor = node.get_value<int>();
ss_enabled = factor >= 2;
ss_amount = (factor >= 2) ? factor : 3;
}
} catch (...) {}
}
if (vid.contains("supersampling_amount")) {
try {
int amount = vid["supersampling_amount"].get_value<int>();
if (amount >= 2) { ss_amount = amount; }
} catch (...) {}
}
video.supersampling = ss_enabled ? ss_amount : 1;
}
if (vid.contains("postfx_preset")) {
try {
int preset = vid["postfx_preset"].get_value<int>();
if (preset >= 0 && preset < static_cast<int>(postfx_presets.size())) {
// No validamos contra postfx_presets.size() aquí porque postfx.yaml
// aún no se ha cargado. El clamp se hace en loadPostFXFromFile().
if (preset >= 0) {
current_postfx_preset = preset;
}
} catch (...) {}
@@ -430,10 +452,10 @@ namespace Options {
file << " scale_mode: " << static_cast<int>(video.scale_mode) << " # " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_NEAREST) << ": nearest, " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_LINEAR) << ": linear\n";
file << " vsync: " << boolToString(video.vsync) << "\n";
file << " integer_scale: " << boolToString(video.integer_scale) << "\n";
file << " shaders: " << boolToString(video.shaders) << "\n";
file << " postfx: " << boolToString(video.postfx) << "\n";
file << " supersampling: " << boolToString(video.supersampling) << "\n";
file << " postfx_preset: " << current_postfx_preset << "\n";
file << " supersampling: " << boolToString(video.supersampling > 1) << "\n";
file << " supersampling_amount: " << std::max(2, video.supersampling) << "\n";
file << "\n";
// AUDIO

View File

@@ -13,7 +13,7 @@
#include <utility> // Para move
#include <vector> // Para vector
#include "defaults.hpp" // for AUDIO_ENABLED, AUDIO_VOLUME, MUSIC_ENABLED, MUSIC_VOLUME, PARAMS_FILE, SETTINGS_AUTOFIRE, SETTINGS_SHUTDOWN_ENABLED, SOUND_ENABLED, SOUND_VOLUME, VIDEO_FULLSCREEN, VIDEO_INTEGER_SCALE, VIDEO_SCALE_MODE, VIDEO_SHADERS, VIDEO_VSYNC, WINDOW_CAPTION, WINDOW_MAX_ZOOM, WINDOW_ZOOM
#include "defaults.hpp" // for AUDIO_ENABLED, AUDIO_VOLUME, MUSIC_ENABLED, MUSIC_VOLUME, PARAMS_FILE, SETTINGS_AUTOFIRE, SETTINGS_SHUTDOWN_ENABLED, SOUND_ENABLED, SOUND_VOLUME, VIDEO_FULLSCREEN, VIDEO_INTEGER_SCALE, VIDEO_SCALE_MODE, VIDEO_VSYNC, WINDOW_CAPTION, WINDOW_MAX_ZOOM, WINDOW_ZOOM
#include "difficulty.hpp" // for Code
#include "input.hpp" // for Input
#include "lang.hpp" // for Code
@@ -47,9 +47,8 @@ namespace Options {
bool fullscreen = GameDefaults::Options::VIDEO_FULLSCREEN; // Indica si se usa pantalla completa
bool vsync = GameDefaults::Options::VIDEO_VSYNC; // Indica si se usa vsync
bool integer_scale = GameDefaults::Options::VIDEO_INTEGER_SCALE; // Indica si se usa escalado entero
bool shaders = GameDefaults::Options::VIDEO_SHADERS; // Indica si se usan shaders para los filtros de vídeo (backward compat)
bool postfx = GameDefaults::Options::VIDEO_POSTFX; // Indica si se usan efectos PostFX
bool supersampling = GameDefaults::Options::VIDEO_SUPERSAMPLING; // Indica si se usa supersampling 3x
int supersampling = GameDefaults::Options::VIDEO_SUPERSAMPLING; // Factor de supersampling: 1=off, 2=2×, 3=3×
std::string info; // Información sobre el modo de vídeo
};

View File

@@ -131,16 +131,14 @@ fragment float4 postfx_fs(PostVOut in [[stage_in]],
}
// Scanlines — 1 pixel físico oscuro por fila lógica.
// Usa uv.y (independiente del offset de letterbox) con pixel_scale para
// calcular la posición dentro de la fila en coordenadas físicas.
// 3x: 1 dark + 2 bright. 4x: 1 dark + 3 bright.
// bright=3.5×, dark floor=0.42 (mantiene aspecto CRT original).
// Modelo sustractivo: las filas de scanline se oscurecen, las demás no cambian.
// Esto evita el efecto de sobrebrillo en contenido con colores vivos.
if (u.scanline_strength > 0.0f) {
float ps = max(1.0f, round(u.pixel_scale));
float frac_in_row = fract(uv.y * u.screen_height);
float row_pos = floor(frac_in_row * ps);
float is_dark = step(ps - 1.0f, row_pos);
float scan = mix(3.5f, 0.42f, is_dark);
float scan = mix(1.0f, 0.0f, is_dark);
colour *= mix(1.0f, scan, u.scanline_strength);
}
@@ -388,11 +386,11 @@ namespace Rendering {
std::memcpy(mapped, pixels, static_cast<size_t>(width * height * 4));
} else {
// Path con supersampling: expande cada pixel a OS×OS, oscurece última fila.
// Replica la fórmula del shader: mix(3.5, 0.42, scanline_strength).
// Modelo sustractivo: filas normales sin cambio, fila de scanline oscurecida a 0.
auto* out = static_cast<Uint32*>(mapped);
const int OS = oversample_;
const float BRIGHT_MUL = 1.0F + (baked_scanline_strength_ * 2.5F); // rows 0..OS-2
const float DARK_MUL = 1.0F - (baked_scanline_strength_ * 0.58F); // row OS-1
const float BRIGHT_MUL = 1.0F; // rows 0..OS-2: sin cambio
const float DARK_MUL = 1.0F - baked_scanline_strength_; // row OS-1: hasta negro
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {

View File

@@ -28,7 +28,7 @@ namespace Rendering {
/**
* @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
*
* Reemplaza el backend OpenGL para que los shaders PostFX funcionen en macOS.
* Backend de shaders PostFX para macOS (Metal) y Win/Linux (Vulkan/SPIR-V).
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
* → PostFX render pass → swapchain → present
*/

View File

@@ -24,7 +24,7 @@ namespace Rendering {
* @brief Interfaz abstracta para backends de renderizado con shaders
*
* Esta interfaz define el contrato que todos los backends de shaders
* deben cumplir (OpenGL, Metal, Vulkan, etc.)
* deben cumplir (Metal, Vulkan, etc.)
*/
class ShaderBackend {
public:
@@ -62,7 +62,7 @@ namespace Rendering {
/**
* @brief Verifica si el backend está usando aceleración por hardware
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
* @return true si usa aceleración por hardware
*/
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;

View File

@@ -25,7 +25,7 @@ Screen* Screen::instance = nullptr;
// Inicializa la instancia única del singleton
void Screen::init() {
Screen::instance = new Screen();
Screen::initShaders(); // Llamar aquí para que Screen::get() ya devuelva la instancia
Screen::initPostFX(); // Llamar aquí para que Screen::get() ya devuelva la instancia
}
// Libera la instancia
@@ -256,12 +256,12 @@ void Screen::renderInfo() const {
}
}
#endif
// Inicializa los shaders (SDL3GPU)
void Screen::initShaders() {
// Inicializa PostFX (SDL3GPU)
void Screen::initPostFX() {
#ifndef NO_SHADERS
auto* self = Screen::get();
if (self == nullptr) {
SDL_Log("Screen::initShaders: instance is null, skipping");
SDL_Log("Screen::initPostFX: instance is null, skipping");
return;
}
if (!self->shader_backend_) {
@@ -269,9 +269,9 @@ void Screen::initShaders() {
}
if (!self->shader_backend_->isHardwareAccelerated()) {
const bool ok = self->shader_backend_->init(self->window_, self->game_canvas_, "", "");
SDL_Log("Screen::initShaders: SDL3GPUShader::init() = %s", ok ? "OK" : "FAILED");
SDL_Log("Screen::initPostFX: SDL3GPUShader::init() = %s", ok ? "OK" : "FAILED");
}
SDL_Log("Screen::initShaders: presets=%d current=%d postfx=%s",
SDL_Log("Screen::initPostFX: presets=%d current=%d postfx=%s",
static_cast<int>(Options::postfx_presets.size()),
Options::current_postfx_preset,
Options::video.postfx ? "ON" : "OFF");
@@ -279,11 +279,6 @@ void Screen::initShaders() {
#endif
}
// Inicializa PostFX (alias de initShaders)
void Screen::initPostFX() {
initShaders();
}
// Calcula el tamaño de la ventana
void Screen::adjustWindowSize() {
if (!Options::video.fullscreen) {
@@ -443,11 +438,6 @@ void Screen::getDisplayInfo() {
}
}
// Alterna entre activar y desactivar los shaders (backward compat)
void Screen::toggleShaders() {
Screen::togglePostFX();
}
// Alterna entre activar y desactivar los efectos PostFX
void Screen::togglePostFX() {
Options::video.postfx = !Options::video.postfx;
@@ -469,16 +459,19 @@ void Screen::nextPostFXPreset() {
// Alterna entre activar y desactivar el supersampling 3x
void Screen::toggleSupersampling() {
Options::video.supersampling = !Options::video.supersampling;
Options::video.supersampling = (Options::video.supersampling % 3) + 1;
auto* self = Screen::get();
if (self != nullptr && self->shader_backend_) {
self->shader_backend_->setOversample(Options::video.supersampling ? 3 : 1);
if (self != nullptr && self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
self->applyCurrentPostFXPreset();
}
}
// Aplica el preset PostFX activo al backend
void Screen::applyCurrentPostFXPreset() {
if (!shader_backend_) { return; }
// setOversample PRIMERO: puede recrear texturas antes de que setPostFXParams
// decida si hornear scanlines en CPU o aplicarlas en GPU.
shader_backend_->setOversample(Options::video.supersampling);
Rendering::PostFXParams p{};
if (Options::video.postfx && !Options::postfx_presets.empty()) {
const auto& preset = Options::postfx_presets.at(static_cast<size_t>(Options::current_postfx_preset));
@@ -490,8 +483,8 @@ void Screen::applyCurrentPostFXPreset() {
p.curvature = preset.curvature;
p.bleeding = preset.bleeding;
p.flicker = preset.flicker;
SDL_Log("Screen::applyCurrentPostFXPreset: preset='%s' scan=%.2f vign=%.2f chroma=%.2f",
preset.name.c_str(), p.scanlines, p.vignette, p.chroma);
SDL_Log("Screen::applyCurrentPostFXPreset: preset='%s' scan=%.2f vign=%.2f chroma=%.2f ss=%d×",
preset.name.c_str(), p.scanlines, p.vignette, p.chroma, Options::video.supersampling);
} else {
SDL_Log("Screen::applyCurrentPostFXPreset: PostFX=%s presets=%d → passthrough",
Options::video.postfx ? "ON" : "OFF",

View File

@@ -41,13 +41,11 @@ class Screen {
auto decWindowSize() -> bool; // Reduce el tamaño de la ventana
auto incWindowSize() -> bool; // Aumenta el tamaño de la ventana
void applySettings(); // Aplica los valores de las opciones
static void initShaders(); // Inicializa los shaders (SDL3GPU)
static void initPostFX(); // Inicializa PostFX (alias de initShaders)
static void initPostFX(); // Inicializa PostFX (SDL3GPU)
// --- Efectos visuales ---
void shake(int desp = 2, float delay_s = 0.05F, float duration_s = 0.133F) { shake_effect_.enable(src_rect_, dst_rect_, desp, delay_s, duration_s); } // Agita la pantalla (tiempo en segundos)
void flash(Color color, float duration_s = 0.167F, float delay_s = 0.0F) { flash_effect_ = FlashEffect(true, duration_s, delay_s, color); } // Pone la pantalla de color (tiempo en segundos)
static void toggleShaders(); // Alterna entre activar y desactivar los shaders (backward compat)
static void togglePostFX(); // Alterna entre activar y desactivar los efectos PostFX
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
static void toggleSupersampling(); // Alterna entre activar y desactivar el supersampling 3x

View File

@@ -364,9 +364,21 @@ void ServiceMenu::initializeOptions() {
1));
options_.push_back(std::make_unique<BoolOption>(
Lang::getText("[SERVICE_MENU] SHADERS"),
Lang::getText("[SERVICE_MENU] POSTFX"),
SettingsGroup::VIDEO,
&Options::video.shaders));
&Options::video.postfx));
options_.push_back(std::make_unique<IntOption>(
Lang::getText("[SERVICE_MENU] POSTFX_PRESET"),
SettingsGroup::VIDEO,
&Options::current_postfx_preset,
0, static_cast<int>(Options::postfx_presets.size()) - 1, 1));
options_.push_back(std::make_unique<IntOption>(
Lang::getText("[SERVICE_MENU] SUPERSAMPLING"),
SettingsGroup::VIDEO,
&Options::video.supersampling,
1, 3, 1));
options_.push_back(std::make_unique<BoolOption>(
Lang::getText("[SERVICE_MENU] VSYNC"),