postfx analític: nou shader + estructures chroma_min/max + scan_*
- Substitueix postfx.frag per la versió analítica amb smoothstep
- PostFXUniforms 12→16 floats (64B, 4×vec4): afegeix chroma_min/max,
scan_dark_ratio, scan_dark_floor, scan_edge_soft
- PostFXParams i PostFXPreset adopten els nous camps amb defaults d'AEE
- MSL extret a source/core/rendering/sdl3gpu/msl/{postfx_vert,postfx_frag,
crtpi_frag}.msl.h (estil Rendering::Msl::kXxx)
- SPIR-V regenerat (postfx_frag_spv.h: 13648 bytes)
- options.cpp llegeix 'chroma' antic com compat (assigna a min i max);
escriu els 6 presets per defecte (CRT/NTSC/CURVED/SCANLINES/SUBTLE/CRT LIVE)
amb els valors d'aee_arcade
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,13 +26,10 @@ namespace Defaults::Video {
|
||||
constexpr Screen::Filter FILTER = Screen::Filter::NEAREST; // Filtro por defecto
|
||||
constexpr bool VERTICAL_SYNC = true; // Vsync activado por defecto
|
||||
constexpr bool SHADER_ENABLED = false; // Shaders de post-procesado desactivados por defecto
|
||||
constexpr bool SUPERSAMPLING = false; // Supersampling desactivado por defecto
|
||||
constexpr bool INTEGER_SCALE = true; // Escalado entero activado por defecto
|
||||
constexpr bool KEEP_ASPECT = true; // Mantener aspecto activado por defecto
|
||||
constexpr const char* PALETTE_NAME = "zx-spectrum"; // Paleta por defecto
|
||||
constexpr const char* PALETTE_SORT = "original"; // Modo de ordenación de paleta por defecto
|
||||
constexpr bool LINEAR_UPSCALE = false; // Upscale NEAREST por defecto
|
||||
constexpr int DOWNSCALE_ALGO = 1; // Downscale Lanczos2 por defecto
|
||||
constexpr bool GPU_ACCELERATION = true; // Aceleración GPU activada por defecto
|
||||
} // namespace Defaults::Video
|
||||
|
||||
|
||||
+44
-99
@@ -325,31 +325,6 @@ namespace Options {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: carga la sección supersampling desde YAML
|
||||
void loadSupersamplingConfigFromYaml(const fkyaml::node& ss_node) {
|
||||
if (ss_node.contains("enabled")) {
|
||||
try {
|
||||
video.supersampling.enabled = ss_node["enabled"].get_value<bool>();
|
||||
} catch (...) {
|
||||
video.supersampling.enabled = Defaults::Video::SUPERSAMPLING;
|
||||
}
|
||||
}
|
||||
if (ss_node.contains("linear_upscale")) {
|
||||
try {
|
||||
video.supersampling.linear_upscale = ss_node["linear_upscale"].get_value<bool>();
|
||||
} catch (...) {
|
||||
video.supersampling.linear_upscale = Defaults::Video::LINEAR_UPSCALE;
|
||||
}
|
||||
}
|
||||
if (ss_node.contains("downscale_algo")) {
|
||||
try {
|
||||
video.supersampling.downscale_algo = ss_node["downscale_algo"].get_value<int>();
|
||||
} catch (...) {
|
||||
video.supersampling.downscale_algo = Defaults::Video::DOWNSCALE_ALGO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: carga la sección shader desde YAML
|
||||
void loadShaderConfigFromYaml(const fkyaml::node& sh_node) {
|
||||
if (sh_node.contains("enabled")) {
|
||||
@@ -439,9 +414,6 @@ namespace Options {
|
||||
if (vid.contains("gpu")) {
|
||||
loadGPUConfigFromYaml(vid["gpu"]);
|
||||
}
|
||||
if (vid.contains("supersampling")) {
|
||||
loadSupersamplingConfigFromYaml(vid["supersampling"]);
|
||||
}
|
||||
if (vid.contains("shader")) {
|
||||
loadShaderConfigFromYaml(vid["shader"]);
|
||||
}
|
||||
@@ -787,10 +759,6 @@ namespace Options {
|
||||
file << " gpu:\n";
|
||||
file << " acceleration: " << (video.gpu.acceleration ? "true" : "false") << " # Usar aceleración hardware GPU (false = SDL fallback)\n";
|
||||
file << " preferred_driver: \"" << video.gpu.preferred_driver << "\" # Driver GPU específico (empty = auto, aplica solo si gpu_acceleration: true)\n";
|
||||
file << " supersampling:\n";
|
||||
file << " enabled: " << (video.supersampling.enabled ? "true" : "false") << "\n";
|
||||
file << " linear_upscale: " << (video.supersampling.linear_upscale ? "true" : "false") << "\n";
|
||||
file << " downscale_algo: " << video.supersampling.downscale_algo << " # 0=bilinear, 1=Lanczos2, 2=Lanczos3\n";
|
||||
file << " shader:\n";
|
||||
file << " enabled: " << (video.shader.enabled ? "true" : "false") << "\n";
|
||||
file << " current_shader: " << (video.shader.current_shader == Rendering::ShaderType::CRTPI ? "crtpi" : "postfx") << "\n";
|
||||
@@ -922,14 +890,26 @@ namespace Options {
|
||||
}
|
||||
parseFloatField(p, "vignette", preset.vignette);
|
||||
parseFloatField(p, "scanlines", preset.scanlines);
|
||||
parseFloatField(p, "chroma", preset.chroma);
|
||||
// Compat: 'chroma' antic → assignar a min i max
|
||||
if (p.contains("chroma")) {
|
||||
try {
|
||||
const auto LEGACY = p["chroma"].get_value<float>();
|
||||
preset.chroma_min = LEGACY;
|
||||
preset.chroma_max = LEGACY;
|
||||
} catch (...) { /* @INTENTIONAL: camp malformat → conservem defaults */
|
||||
}
|
||||
}
|
||||
parseFloatField(p, "chroma_min", preset.chroma_min);
|
||||
parseFloatField(p, "chroma_max", preset.chroma_max);
|
||||
parseFloatField(p, "mask", preset.mask);
|
||||
parseFloatField(p, "gamma", preset.gamma);
|
||||
parseFloatField(p, "curvature", preset.curvature);
|
||||
parseFloatField(p, "bleeding", preset.bleeding);
|
||||
parseFloatField(p, "flicker", preset.flicker);
|
||||
// Nota: 'supersampling' era un campo por-preset (eliminado). Si existe
|
||||
// en el fichero del usuario se ignora silenciosamente (compatible).
|
||||
parseFloatField(p, "scan_dark_ratio", preset.scan_dark_ratio);
|
||||
parseFloatField(p, "scan_dark_floor", preset.scan_dark_floor);
|
||||
parseFloatField(p, "scan_edge_soft", preset.scan_edge_soft);
|
||||
// Nota: 'supersampling' era un camp obsolet — s'ignora silenciosament.
|
||||
postfx_presets.push_back(preset);
|
||||
}
|
||||
}
|
||||
@@ -967,82 +947,47 @@ namespace Options {
|
||||
file << "# Each preset defines the intensity of post-processing effects (0.0 to 1.0).\n";
|
||||
file << "# vignette: screen darkening at the edges\n";
|
||||
file << "# scanlines: horizontal scanline effect\n";
|
||||
file << "# chroma: chromatic aberration (RGB color fringing)\n";
|
||||
file << "# chroma_min / chroma_max: chromatic aberration. Si min == max, queda estàtic.\n";
|
||||
file << "# mask: phosphor dot mask (RGB subpixel pattern)\n";
|
||||
file << "# gamma: gamma correction input 2.4 / output 2.2\n";
|
||||
file << "# curvature: CRT barrel distortion\n";
|
||||
file << "# bleeding: NTSC horizontal colour bleeding\n";
|
||||
file << "# flicker: phosphor CRT flicker ~50 Hz (0.0 = off, 1.0 = max)\n";
|
||||
file << "# Note: supersampling is a global toggle in config.yaml, not per-preset.\n";
|
||||
file << "# flicker: phosphor CRT flicker ~50 Hz\n";
|
||||
file << "# scan_dark_ratio / scan_dark_floor / scan_edge_soft: forma de les scanlines.\n";
|
||||
file << "\n";
|
||||
|
||||
const std::vector<PostFXPreset> DEFAULTS = {
|
||||
{.name = "CRT", .vignette = 0.6F, .scanlines = 0.7F, .chroma_min = 0.15F, .chroma_max = 0.15F, .mask = 0.6F, .gamma = 0.8F, .curvature = 0.0F, .bleeding = 0.0F, .flicker = 0.0F},
|
||||
{.name = "NTSC", .vignette = 0.4F, .scanlines = 0.5F, .chroma_min = 0.2F, .chroma_max = 0.2F, .mask = 0.4F, .gamma = 0.5F, .curvature = 0.0F, .bleeding = 0.6F, .flicker = 0.0F},
|
||||
{.name = "CURVED", .vignette = 0.5F, .scanlines = 0.6F, .chroma_min = 0.1F, .chroma_max = 0.1F, .mask = 0.5F, .gamma = 0.7F, .curvature = 0.8F, .bleeding = 0.0F, .flicker = 0.0F},
|
||||
{.name = "SCANLINES", .vignette = 0.0F, .scanlines = 0.8F, .chroma_min = 0.0F, .chroma_max = 0.0F, .mask = 0.0F, .gamma = 0.0F, .curvature = 0.0F, .bleeding = 0.0F, .flicker = 0.0F},
|
||||
{.name = "SUBTLE", .vignette = 0.3F, .scanlines = 0.4F, .chroma_min = 0.05F, .chroma_max = 0.05F, .mask = 0.0F, .gamma = 0.3F, .curvature = 0.0F, .bleeding = 0.0F, .flicker = 0.0F},
|
||||
{.name = "CRT LIVE", .vignette = 0.5F, .scanlines = 0.6F, .chroma_min = 0.1F, .chroma_max = 0.3F, .mask = 0.3F, .gamma = 0.4F, .curvature = 0.3F, .bleeding = 0.4F, .flicker = 0.8F},
|
||||
};
|
||||
|
||||
file << "presets:\n";
|
||||
file << " - name: \"CRT\"\n";
|
||||
file << " vignette: 0.6\n";
|
||||
file << " scanlines: 0.7\n";
|
||||
file << " chroma: 0.15\n";
|
||||
file << " mask: 0.6\n";
|
||||
file << " gamma: 0.8\n";
|
||||
file << " curvature: 0.0\n";
|
||||
file << " bleeding: 0.0\n";
|
||||
file << " flicker: 0.0\n";
|
||||
file << " - name: \"NTSC\"\n";
|
||||
file << " vignette: 0.4\n";
|
||||
file << " scanlines: 0.5\n";
|
||||
file << " chroma: 0.2\n";
|
||||
file << " mask: 0.4\n";
|
||||
file << " gamma: 0.5\n";
|
||||
file << " curvature: 0.0\n";
|
||||
file << " bleeding: 0.6\n";
|
||||
file << " flicker: 0.0\n";
|
||||
file << " - name: \"CURVED\"\n";
|
||||
file << " vignette: 0.5\n";
|
||||
file << " scanlines: 0.6\n";
|
||||
file << " chroma: 0.1\n";
|
||||
file << " mask: 0.5\n";
|
||||
file << " gamma: 0.7\n";
|
||||
file << " curvature: 0.8\n";
|
||||
file << " bleeding: 0.0\n";
|
||||
file << " flicker: 0.0\n";
|
||||
file << " - name: \"SCANLINES\"\n";
|
||||
file << " vignette: 0.0\n";
|
||||
file << " scanlines: 0.8\n";
|
||||
file << " chroma: 0.0\n";
|
||||
file << " mask: 0.0\n";
|
||||
file << " gamma: 0.0\n";
|
||||
file << " curvature: 0.0\n";
|
||||
file << " bleeding: 0.0\n";
|
||||
file << " flicker: 0.0\n";
|
||||
file << " - name: \"SUBTLE\"\n";
|
||||
file << " vignette: 0.3\n";
|
||||
file << " scanlines: 0.4\n";
|
||||
file << " chroma: 0.05\n";
|
||||
file << " mask: 0.0\n";
|
||||
file << " gamma: 0.3\n";
|
||||
file << " curvature: 0.0\n";
|
||||
file << " bleeding: 0.0\n";
|
||||
file << " flicker: 0.0\n";
|
||||
file << " - name: \"CRT LIVE\"\n";
|
||||
file << " vignette: 0.5\n";
|
||||
file << " scanlines: 0.6\n";
|
||||
file << " chroma: 0.3\n";
|
||||
file << " mask: 0.3\n";
|
||||
file << " gamma: 0.4\n";
|
||||
file << " curvature: 0.3\n";
|
||||
file << " bleeding: 0.4\n";
|
||||
file << " flicker: 0.8\n";
|
||||
for (const auto& preset : DEFAULTS) {
|
||||
file << " - name: \"" << preset.name << "\"\n";
|
||||
file << " vignette: " << preset.vignette << "\n";
|
||||
file << " scanlines: " << preset.scanlines << "\n";
|
||||
file << " chroma_min: " << preset.chroma_min << "\n";
|
||||
file << " chroma_max: " << preset.chroma_max << "\n";
|
||||
file << " mask: " << preset.mask << "\n";
|
||||
file << " gamma: " << preset.gamma << "\n";
|
||||
file << " curvature: " << preset.curvature << "\n";
|
||||
file << " bleeding: " << preset.bleeding << "\n";
|
||||
file << " flicker: " << preset.flicker << "\n";
|
||||
file << " scan_dark_ratio: " << preset.scan_dark_ratio << "\n";
|
||||
file << " scan_dark_floor: " << preset.scan_dark_floor << "\n";
|
||||
file << " scan_edge_soft: " << preset.scan_edge_soft << "\n";
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
std::cout << "PostFX file created with defaults: " << postfx_file_path << '\n';
|
||||
|
||||
// Cargar los presets recién creados
|
||||
postfx_presets.clear();
|
||||
postfx_presets.push_back({"CRT", 0.6F, 0.7F, 0.3F, 0.6F, 0.8F, 0.0F, 0.0F, 0.0F});
|
||||
postfx_presets.push_back({"NTSC", 0.4F, 0.5F, 0.2F, 0.4F, 0.5F, 0.0F, 0.6F, 0.0F});
|
||||
postfx_presets.push_back({"CURVED", 0.5F, 0.6F, 0.1F, 0.5F, 0.7F, 0.8F, 0.0F, 0.0F});
|
||||
postfx_presets.push_back({"SCANLINES", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F});
|
||||
postfx_presets.push_back({"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.3F, 0.0F, 0.0F, 0.0F});
|
||||
postfx_presets.push_back({"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F});
|
||||
postfx_presets = DEFAULTS;
|
||||
video.shader.current_postfx_preset = 0;
|
||||
|
||||
return true;
|
||||
|
||||
+14
-17
@@ -81,13 +81,6 @@ namespace Options {
|
||||
std::string preferred_driver; // Driver GPU preferido; vacío = auto. Aplica en el próximo arranque.
|
||||
};
|
||||
|
||||
// Estructura para las opciones de supersampling
|
||||
struct Supersampling {
|
||||
bool enabled{Defaults::Video::SUPERSAMPLING}; // Indica si el supersampling 3× está activo
|
||||
bool linear_upscale{Defaults::Video::LINEAR_UPSCALE}; // Upscale LINEAR (true) o NEAREST (false)
|
||||
int downscale_algo{Defaults::Video::DOWNSCALE_ALGO}; // 0=bilinear, 1=Lanczos2, 2=Lanczos3
|
||||
};
|
||||
|
||||
// Estructura para las opciones de shader (dentro de Video)
|
||||
struct ShaderConfig {
|
||||
bool enabled{Defaults::Video::SHADER_ENABLED}; // Indica si se usan shaders de post-procesado
|
||||
@@ -110,7 +103,6 @@ namespace Options {
|
||||
std::string info; // Información sobre el modo de vídeo
|
||||
Border border{}; // Borde de la pantalla
|
||||
GPU gpu{}; // Opciones de aceleración GPU
|
||||
Supersampling supersampling{}; // Opciones de supersampling
|
||||
ShaderConfig shader{}; // Opciones de shader post-procesado
|
||||
};
|
||||
|
||||
@@ -151,15 +143,20 @@ namespace Options {
|
||||
|
||||
// Estructura para un preset de PostFX
|
||||
struct PostFXPreset {
|
||||
std::string name; // Nombre del preset
|
||||
float vignette{0.6F}; // Intensidad de la viñeta (0.0 = ninguna, 1.0 = máxima)
|
||||
float scanlines{0.7F}; // Intensidad de las scanlines (0.0 = desactivadas, 1.0 = máximas)
|
||||
float chroma{0.15F}; // Intensidad de la aberración cromática (0.0 = ninguna, 1.0 = máxima)
|
||||
float mask{0.0F}; // Intensidad de la máscara de fósforo RGB (0.0 = desactivada, 1.0 = máxima)
|
||||
float gamma{0.0F}; // Corrección gamma input 2.4 / output 2.2 (0.0 = off, 1.0 = plena)
|
||||
float curvature{0.0F}; // Distorsión barrel CRT (0.0 = plana, 1.0 = máxima curvatura)
|
||||
float bleeding{0.0F}; // Sangrado de color NTSC horizontal Y/C (0.0 = off, 1.0 = máximo)
|
||||
float flicker{0.0F}; // Parpadeo de fósforo CRT ~50 Hz (0.0 = off, 1.0 = máximo)
|
||||
std::string name; // Nombre del preset
|
||||
float vignette{0.6F}; // Intensidad de la viñeta (0.0 = ninguna, 1.0 = máxima)
|
||||
float scanlines{0.7F}; // Intensidad de las scanlines
|
||||
float chroma_min{0.15F}; // Aberració cromàtica mínima (sempre present)
|
||||
float chroma_max{0.15F}; // Si != chroma_min → pulsa sinusoidalment
|
||||
float mask{0.0F}; // Máscara de fósforo RGB
|
||||
float gamma{0.0F}; // Corrección gamma (0=off, 1=full)
|
||||
float curvature{0.0F}; // Distorsión barrel CRT
|
||||
float bleeding{0.0F}; // Sangrado de color NTSC
|
||||
float flicker{0.0F}; // Parpadeo de fósforo ~50 Hz
|
||||
// Forma de les scanlines — 3 subpíxels per fila lògica per defecte.
|
||||
float scan_dark_ratio{0.333F};
|
||||
float scan_dark_floor{0.42F};
|
||||
float scan_edge_soft{1.0F};
|
||||
};
|
||||
|
||||
// Estructura para un preset del shader CRT-Pi
|
||||
|
||||
@@ -54,81 +54,6 @@ static auto boolToggle(
|
||||
|
||||
// ── Command handlers ─────────────────────────────────────────────────────────
|
||||
|
||||
// SS [ON|OFF|SIZE|UPSCALE [NEAREST|LINEAR]|DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3]]
|
||||
// SS SIZE — dimensions de la textura supersampling activa
|
||||
static auto cmdSsSize() -> std::string {
|
||||
if (!Options::video.supersampling.enabled) { return "Supersampling is OFF: no texture"; }
|
||||
const auto [w, h] = Screen::get()->getSsTextureSize();
|
||||
if (w == 0) { return "SS texture: not active"; }
|
||||
return "SS texture: " + std::to_string(w) + "x" + std::to_string(h);
|
||||
}
|
||||
|
||||
// SS UPSCALE [NEAREST|LINEAR] — toggle o estableix mode upscale
|
||||
static auto cmdSsUpscale(const std::vector<std::string>& args) -> std::string {
|
||||
if (args.size() == 1) {
|
||||
Screen::get()->setLinearUpscale(!Options::video.supersampling.linear_upscale);
|
||||
return std::string("Upscale: ") + (Options::video.supersampling.linear_upscale ? "Linear" : "Nearest");
|
||||
}
|
||||
if (args[1] == "NEAREST") {
|
||||
if (!Options::video.supersampling.linear_upscale) { return "Upscale already Nearest"; }
|
||||
Screen::get()->setLinearUpscale(false);
|
||||
return "Upscale: Nearest";
|
||||
}
|
||||
if (args[1] == "LINEAR") {
|
||||
if (Options::video.supersampling.linear_upscale) { return "Upscale already Linear"; }
|
||||
Screen::get()->setLinearUpscale(true);
|
||||
return "Upscale: Linear";
|
||||
}
|
||||
return "usage: ss upscale [nearest|linear]";
|
||||
}
|
||||
|
||||
// SS DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3] — consulta o estableix algorisme
|
||||
static auto cmdSsDownscale(const std::vector<std::string>& args) -> std::string {
|
||||
static const std::array<std::string_view, 3> DOWNSCALE_NAMES = {"Bilinear", "Lanczos2", "Lanczos3"};
|
||||
if (args.size() == 1) {
|
||||
return std::string("Downscale: ") + std::string(DOWNSCALE_NAMES[static_cast<size_t>(Options::video.supersampling.downscale_algo)]);
|
||||
}
|
||||
int algo = -1;
|
||||
if (args[1] == "BILINEAR") { algo = 0; }
|
||||
if (args[1] == "LANCZOS2") { algo = 1; }
|
||||
if (args[1] == "LANCZOS3") { algo = 2; }
|
||||
if (algo == -1) { return "usage: ss downscale [bilinear|lanczos2|lanczos3]"; }
|
||||
if (Options::video.supersampling.downscale_algo == algo) {
|
||||
return std::string("Downscale already ") + std::string(DOWNSCALE_NAMES[static_cast<size_t>(algo)]);
|
||||
}
|
||||
Screen::get()->setDownscaleAlgo(algo);
|
||||
return std::string("Downscale: ") + std::string(DOWNSCALE_NAMES[static_cast<size_t>(algo)]);
|
||||
}
|
||||
|
||||
// SS ON — activa supersampling si encara no ho està
|
||||
static auto cmdSsOn() -> std::string {
|
||||
if (Options::video.supersampling.enabled) { return "Supersampling already ON"; }
|
||||
Screen::get()->toggleSupersampling();
|
||||
return "PostFX Supersampling ON";
|
||||
}
|
||||
|
||||
// SS OFF — desactiva supersampling si encara està actiu
|
||||
static auto cmdSsOff() -> std::string {
|
||||
if (!Options::video.supersampling.enabled) { return "Supersampling already OFF"; }
|
||||
Screen::get()->toggleSupersampling();
|
||||
return "PostFX Supersampling OFF";
|
||||
}
|
||||
|
||||
// SS — toggle (sense args) o dispatch a subcomandes
|
||||
static auto cmdSs(const std::vector<std::string>& args) -> std::string {
|
||||
if (!Screen::get()->isHardwareAccelerated()) { return "No GPU acceleration"; }
|
||||
if (args.empty()) {
|
||||
Screen::get()->toggleSupersampling();
|
||||
return std::string("PostFX Supersampling ") + (Options::video.supersampling.enabled ? "ON" : "OFF");
|
||||
}
|
||||
if (args[0] == "SIZE") { return cmdSsSize(); }
|
||||
if (args[0] == "UPSCALE") { return cmdSsUpscale(args); }
|
||||
if (args[0] == "DOWNSCALE") { return cmdSsDownscale(args); }
|
||||
if (args[0] == "ON") { return cmdSsOn(); }
|
||||
if (args[0] == "OFF") { return cmdSsOff(); }
|
||||
return "usage: ss [on|off|size|upscale [nearest|linear]|downscale [bilinear|lanczos2|lanczos3]]";
|
||||
}
|
||||
|
||||
// Helper: aplica un preset por dirección (NEXT/PREV) o nombre; devuelve mensaje
|
||||
static auto applyPreset(const std::vector<std::string>& args) -> std::string {
|
||||
const bool IS_CRTPI = Options::video.shader.current_shader == Rendering::ShaderType::CRTPI;
|
||||
@@ -990,7 +915,6 @@ static auto cmdSize(const std::vector<std::string>& /*unused*/) -> std::string {
|
||||
// ── CommandRegistry ──────────────────────────────────────────────────────────
|
||||
|
||||
void CommandRegistry::registerHandlers() {
|
||||
handlers_["cmd_ss"] = cmdSs;
|
||||
handlers_["cmd_shader"] = cmdShader;
|
||||
handlers_["cmd_border"] = cmdBorder;
|
||||
handlers_["cmd_fullscreen"] = cmdFullscreen;
|
||||
|
||||
Reference in New Issue
Block a user