merge: migració PostFX i chroma min/max
This commit is contained in:
@@ -164,15 +164,11 @@ if(NOT APPLE AND NOT EMSCRIPTEN)
|
||||
set(ALL_SHADER_HEADERS
|
||||
"${HEADERS_DIR}/postfx_vert_spv.h"
|
||||
"${HEADERS_DIR}/postfx_frag_spv.h"
|
||||
"${HEADERS_DIR}/upscale_frag_spv.h"
|
||||
"${HEADERS_DIR}/downscale_frag_spv.h"
|
||||
"${HEADERS_DIR}/crtpi_frag_spv.h"
|
||||
)
|
||||
set(ALL_SHADER_SOURCES
|
||||
"${SHADERS_DIR}/postfx.vert"
|
||||
"${SHADERS_DIR}/postfx.frag"
|
||||
"${SHADERS_DIR}/upscale.frag"
|
||||
"${SHADERS_DIR}/downscale.frag"
|
||||
"${SHADERS_DIR}/crtpi_frag.glsl"
|
||||
)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ RESOURCE_FILE := release/windows/coffee.res
|
||||
# ==============================================================================
|
||||
SHADER_CMAKE := $(DIR_ROOT)tools/shaders/compile_spirv.cmake
|
||||
SHADERS_DIR := $(DIR_ROOT)data/shaders
|
||||
HEADERS_DIR := $(DIR_ROOT)source/core/rendering/sdl3gpu
|
||||
HEADERS_DIR := $(DIR_ROOT)source/core/rendering/sdl3gpu/spv
|
||||
ifeq ($(OS),Windows_NT)
|
||||
GLSLC := $(shell where glslc 2>NUL)
|
||||
else
|
||||
|
||||
@@ -83,7 +83,6 @@
|
||||
"[SERVICE_MENU] SHADER": "Shader",
|
||||
"[SERVICE_MENU] SHADER_DISABLED": "Desactivat",
|
||||
"[SERVICE_MENU] SHADER_PRESET": "Preset",
|
||||
"[SERVICE_MENU] SUPERSAMPLING": "Supermostreig",
|
||||
"[SERVICE_MENU] VSYNC": "Sincronisme vertical",
|
||||
"[SERVICE_MENU] INTEGER_SCALE": "Escalat sencer",
|
||||
"[SERVICE_MENU] FILTER": "Filtre",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"[SERVICE_MENU] SHADER": "Shader",
|
||||
"[SERVICE_MENU] SHADER_DISABLED": "Disabled",
|
||||
"[SERVICE_MENU] SHADER_PRESET": "Preset",
|
||||
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
|
||||
"[SERVICE_MENU] VSYNC": "V-Sync",
|
||||
"[SERVICE_MENU] INTEGER_SCALE": "Integer Scale",
|
||||
"[SERVICE_MENU] FILTER": "Filter",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"[SERVICE_MENU] SHADER": "Shader",
|
||||
"[SERVICE_MENU] SHADER_DISABLED": "Desactivado",
|
||||
"[SERVICE_MENU] SHADER_PRESET": "Preset",
|
||||
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
|
||||
"[SERVICE_MENU] VSYNC": "Sincronismo vertical",
|
||||
"[SERVICE_MENU] INTEGER_SCALE": "Escalado proporcional",
|
||||
"[SERVICE_MENU] FILTER": "Filtro",
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
#version 450
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
layout(set = 2, binding = 0) uniform sampler2D source;
|
||||
|
||||
layout(set = 3, binding = 0) uniform DownscaleUniforms {
|
||||
int algorithm; // 0 = Lanczos2 (ventana 2, ±2 taps), 1 = Lanczos3 (ventana 3, ±3 taps)
|
||||
float pad0;
|
||||
float pad1;
|
||||
float pad2;
|
||||
} u;
|
||||
|
||||
// Kernel Lanczos normalizado: sinc(t) * sinc(t/a) para |t| < a, 0 fuera.
|
||||
float lanczos(float t, float a) {
|
||||
t = abs(t);
|
||||
if (t < 0.0001) { return 1.0; }
|
||||
if (t >= a) { return 0.0; }
|
||||
const float PI = 3.14159265358979;
|
||||
float pt = PI * t;
|
||||
return (a * sin(pt) * sin(pt / a)) / (pt * pt);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 src_size = vec2(textureSize(source, 0));
|
||||
// Posición en coordenadas de texel (centros de texel en N+0.5)
|
||||
vec2 p = v_uv * src_size;
|
||||
vec2 p_floor = floor(p);
|
||||
|
||||
float a = (u.algorithm == 0) ? 2.0 : 3.0;
|
||||
int win = int(a);
|
||||
|
||||
vec4 color = vec4(0.0);
|
||||
float weight_sum = 0.0;
|
||||
|
||||
for (int j = -win; j <= win; j++) {
|
||||
for (int i = -win; i <= win; i++) {
|
||||
// Centro del texel (i,j) relativo a p_floor
|
||||
vec2 tap_center = p_floor + vec2(float(i), float(j)) + 0.5;
|
||||
vec2 offset = tap_center - p;
|
||||
float w = lanczos(offset.x, a) * lanczos(offset.y, a);
|
||||
color += texture(source, tap_center / src_size) * w;
|
||||
weight_sum += w;
|
||||
}
|
||||
}
|
||||
|
||||
out_color = (weight_sum > 0.0) ? (color / weight_sum) : vec4(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
+47
-18
@@ -6,7 +6,9 @@
|
||||
// xxd -i postfx.frag.spv > ../../source/core/rendering/sdl3gpu/postfx_frag_spv.h
|
||||
//
|
||||
// PostFXUniforms must match exactly the C++ struct in sdl3gpu_shader.hpp
|
||||
// (8 floats, 32 bytes, std140/scalar layout).
|
||||
// (16 floats = 4 × vec4 = 64 bytes, std140/scalar layout).
|
||||
// IMPORTANT: Qualsevol canvi ací cal replicar-lo a mà a
|
||||
// source/core/rendering/sdl3gpu/msl/postfx_frag.msl.h (no hi ha generador).
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
@@ -15,7 +17,7 @@ layout(set = 2, binding = 0) uniform sampler2D scene;
|
||||
|
||||
layout(set = 3, binding = 0) uniform PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float chroma_min; // intensitat mínima de l'aberració cromàtica
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
float mask_strength;
|
||||
@@ -24,10 +26,28 @@ layout(set = 3, binding = 0) uniform PostFXUniforms {
|
||||
float bleeding;
|
||||
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
||||
float time; // seconds since SDL init
|
||||
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
|
||||
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — 48 bytes total (3 × 16)
|
||||
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz
|
||||
float chroma_max; // intensitat màxima; si == chroma_min → chroma estàtic
|
||||
// vec4 #3 — paràmetres de scanlines (exposats per preset YAML)
|
||||
float scan_dark_ratio; // fracció de subfila fosca per fila lògica (1/3 ≈ 0.333)
|
||||
float scan_dark_floor; // multiplicador de brillantor de la subfila fosca
|
||||
float scan_edge_soft; // 0 = step dur; 1 = suavitzat d'1 píxel físic (estil crtpi)
|
||||
float pad3; // padding per tancar a 64 bytes (4 × vec4)
|
||||
} u;
|
||||
|
||||
// Mostreig bilinear horitzontal d'un canal RGB. Evita el "tic-tac" del sampler
|
||||
// NEAREST quan l'offset de chroma és subpíxel: sense interpolar, l'offset
|
||||
// arrodonia entre 1 i 2 píxels i el drift temporal feia un parpelleig discret.
|
||||
float sampleBilinearX(vec2 uv_target, int channel) {
|
||||
vec2 tex_size = vec2(textureSize(scene, 0));
|
||||
float px = uv_target.x * tex_size.x - 0.5;
|
||||
float p_floor = floor(px);
|
||||
float f = px - p_floor;
|
||||
vec4 c0 = texture(scene, vec2((p_floor + 0.5) / tex_size.x, uv_target.y));
|
||||
vec4 c1 = texture(scene, vec2((p_floor + 1.5) / tex_size.x, uv_target.y));
|
||||
return mix(c0[channel], c1[channel], f);
|
||||
}
|
||||
|
||||
// YCbCr helpers for NTSC bleeding
|
||||
vec3 rgb_to_ycc(vec3 rgb) {
|
||||
return vec3(
|
||||
@@ -69,11 +89,11 @@ void main() {
|
||||
vec3 base = texture(scene, uv).rgb;
|
||||
|
||||
// Sangrado NTSC — difuminado horizontal de crominancia.
|
||||
// step = 1 pixel lógico de juego en UV (corrige SS: textureSize.x = game_w * oversample).
|
||||
// step = 1 pixel lógico de juego en UV.
|
||||
vec3 colour;
|
||||
if (u.bleeding > 0.0) {
|
||||
float tw = float(textureSize(scene, 0).x);
|
||||
float step = u.oversample / tw; // 1 pixel lógico en UV
|
||||
float step = 1.0 / tw; // 1 pixel lógico en UV
|
||||
vec3 ycc = rgb_to_ycc(base);
|
||||
vec3 ycc_l2 = rgb_to_ycc(texture(scene, uv - vec2(2.0*step, 0.0)).rgb);
|
||||
vec3 ycc_l1 = rgb_to_ycc(texture(scene, uv - vec2(1.0*step, 0.0)).rgb);
|
||||
@@ -85,10 +105,14 @@ void main() {
|
||||
colour = base;
|
||||
}
|
||||
|
||||
// Aberración cromática (drift animado con time para efecto NTSC real)
|
||||
float ca = u.chroma_strength * 0.005 * (1.0 + 0.15 * sin(u.time * 7.3));
|
||||
colour.r = texture(scene, uv + vec2(ca, 0.0)).r;
|
||||
colour.b = texture(scene, uv - vec2(ca, 0.0)).b;
|
||||
// Aberración cromática — intensitat varia entre chroma_min i chroma_max amb
|
||||
// una sinusoidal (si min == max, queda estàtica). Mostreig bilinear horitzontal
|
||||
// per evitar el "tic-tac" del NEAREST sampler quan l'offset és subpíxel.
|
||||
if (u.chroma_min > 0.0 || u.chroma_max > 0.0) {
|
||||
float ca = mix(u.chroma_min, u.chroma_max, 0.5 + 0.5 * sin(u.time * 7.3)) * 0.005;
|
||||
colour.r = sampleBilinearX(uv + vec2(ca, 0.0), 0);
|
||||
colour.b = sampleBilinearX(uv - vec2(ca, 0.0), 2);
|
||||
}
|
||||
|
||||
// Corrección gamma (linealizar antes de scanlines, codificar después)
|
||||
if (u.gamma_strength > 0.0) {
|
||||
@@ -96,15 +120,20 @@ void main() {
|
||||
colour = mix(colour, lin, u.gamma_strength);
|
||||
}
|
||||
|
||||
// Scanlines — 1 pixel físico oscuro por fila lógica.
|
||||
// Modelo sustractivo: las filas de scanline se oscurecen, las demás no cambian.
|
||||
// Esto evita el efecto de sobrebrillo en contenido con colores vivos.
|
||||
// Scanlines — tècnica dels 3 subpíxels verticals per píxel lògic (aee/projecte_2026):
|
||||
// franja fosca ocupant `scan_dark_ratio` al final de cada fila lògica. La transició es
|
||||
// suavitza amb smoothstep d'ample ≈ 1 píxel físic (estil crtpi: filtratge analític
|
||||
// continu), controlat per `scan_edge_soft`. A 0 és equivalent al step dur antic.
|
||||
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(1.0, 0.0, is_dark);
|
||||
float ps = max(u.pixel_scale, 1.0);
|
||||
float sub = fract(uv.y * u.screen_height); // [0,1) dins la fila lògica
|
||||
float dark_center = 1.0 - u.scan_dark_ratio * 0.5; // centre de la franja fosca
|
||||
float d = abs(sub - dark_center);
|
||||
d = min(d, 1.0 - d); // wrap a la fila següent
|
||||
float half_width = u.scan_dark_ratio * 0.5;
|
||||
float softness = u.scan_edge_soft * 0.5 / ps; // mig píxel físic a cada costat
|
||||
float band = 1.0 - smoothstep(half_width - softness, half_width + softness, d);
|
||||
float scan = mix(1.0, u.scan_dark_floor, band);
|
||||
colour *= mix(1.0, scan, u.scanline_strength);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#version 450
|
||||
|
||||
// Vulkan GLSL fragment shader — Nearest-neighbour upscale pass
|
||||
// Used as the first render pass when supersampling is active.
|
||||
// Compile: glslc upscale.frag -o upscale.frag.spv
|
||||
// xxd -i upscale.frag.spv > ../../source/core/rendering/sdl3gpu/upscale_frag_spv.h
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
layout(set = 2, binding = 0) uniform sampler2D scene;
|
||||
|
||||
void main() {
|
||||
out_color = texture(scene, v_uv);
|
||||
}
|
||||
@@ -6,15 +6,15 @@
|
||||
#include <utility> // Para pair
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "core/audio/audio.hpp" // Para Audio
|
||||
#include "core/input/input.hpp" // Para Input
|
||||
#include "core/locale/lang.hpp" // Para getText, getLangFile, getLangName, getNextLangCode, loadFromFile
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/system/section.hpp" // Para Name, name, Options, options, AttractMode, attract_mode
|
||||
#include "game/options.hpp" // Para Video, video, Settings, settings, Audio, audio, Window, window
|
||||
#include "game/ui/notifier.hpp" // Para Notifier
|
||||
#include "game/ui/service_menu.hpp" // Para ServiceMenu
|
||||
#include "utils/utils.hpp" // Para boolToOnOff
|
||||
#include "core/audio/audio.hpp" // Para Audio
|
||||
#include "core/input/input.hpp" // Para Input
|
||||
#include "core/locale/lang.hpp" // Para getText, getLangFile, getLangName, getNextLangCode, loadFromFile
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/system/section.hpp" // Para Name, name, Options, options, AttractMode, attract_mode
|
||||
#include "game/options.hpp" // Para Video, video, Settings, settings, Audio, audio, Window, window
|
||||
#include "game/ui/notifier.hpp" // Para Notifier
|
||||
#include "game/ui/service_menu.hpp" // Para ServiceMenu
|
||||
#include "utils/utils.hpp" // Para boolToOnOff
|
||||
|
||||
namespace GlobalInputs {
|
||||
// Termina
|
||||
@@ -91,12 +91,6 @@ namespace GlobalInputs {
|
||||
}
|
||||
}
|
||||
|
||||
// Activa o desactiva el supersampling
|
||||
void toggleSupersampling() {
|
||||
Screen::toggleSupersampling();
|
||||
Notifier::get()->show({"SS: " + std::string(Options::video.supersampling.enabled ? "ON" : "OFF")});
|
||||
}
|
||||
|
||||
// Cambia al siguiente idioma
|
||||
void setNextLang() {
|
||||
const std::string CODE = "LANG";
|
||||
@@ -227,10 +221,6 @@ namespace GlobalInputs {
|
||||
nextPreset();
|
||||
return true;
|
||||
}
|
||||
if (Input::get()->checkAction(Input::Action::TOGGLE_SUPERSAMPLING, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
|
||||
toggleSupersampling();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,6 @@ class Input {
|
||||
{Action::TOGGLE_VIDEO_POSTFX, KeyState(SDL_SCANCODE_F4)},
|
||||
{Action::NEXT_SHADER, KeyState(SDL_SCANCODE_8)},
|
||||
{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)},
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
|
||||
{InputAction::TOGGLE_VIDEO_POSTFX, "TOGGLE_VIDEO_POSTFX"},
|
||||
{InputAction::NEXT_SHADER, "NEXT_SHADER"},
|
||||
{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"},
|
||||
@@ -57,7 +56,6 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
|
||||
{"TOGGLE_VIDEO_POSTFX", InputAction::TOGGLE_VIDEO_POSTFX},
|
||||
{"NEXT_SHADER", InputAction::NEXT_SHADER},
|
||||
{"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},
|
||||
|
||||
@@ -35,7 +35,6 @@ enum class InputAction : std::uint8_t { // Acciones de entrada posibles en el j
|
||||
TOGGLE_VIDEO_POSTFX,
|
||||
NEXT_SHADER,
|
||||
NEXT_POSTFX_PRESET,
|
||||
TOGGLE_SUPERSAMPLING,
|
||||
TOGGLE_VIDEO_INTEGER_SCALE,
|
||||
TOGGLE_VIDEO_VSYNC,
|
||||
RESET,
|
||||
|
||||
@@ -337,7 +337,6 @@ auto Screen::buildDebugInfoText() const -> std::string {
|
||||
} else {
|
||||
const std::string PRESET_NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
|
||||
info_text += " - postfx " + toLower(PRESET_NAME);
|
||||
if (Options::video.supersampling.enabled) { info_text += " (ss)"; }
|
||||
}
|
||||
return info_text;
|
||||
}
|
||||
@@ -381,10 +380,6 @@ void Screen::initShaders() {
|
||||
std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (OK ? "OK" : "FAILED") << '\n';
|
||||
}
|
||||
if (self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
|
||||
self->shader_backend_->setLinearUpscale(Options::video.supersampling.linear_upscale);
|
||||
self->shader_backend_->setDownscaleAlgo(Options::video.supersampling.downscale_algo);
|
||||
self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||
|
||||
if (!Options::video.shader.enabled) {
|
||||
// Passthrough: POSTFX con parámetros a cero
|
||||
self->shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||
@@ -589,36 +584,25 @@ void Screen::nextCrtPiPreset() {
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna supersampling
|
||||
void Screen::toggleSupersampling() {
|
||||
Options::video.supersampling.enabled = !Options::video.supersampling.enabled;
|
||||
auto* self = Screen::get();
|
||||
if (self != nullptr && self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
|
||||
self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
self->applyCurrentCrtPiPreset();
|
||||
} else {
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica el preset PostFX activo al backend
|
||||
void Screen::applyCurrentPostFXPreset() {
|
||||
if (!shader_backend_) { return; }
|
||||
shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||
Rendering::PostFXParams p{};
|
||||
if (Options::video.shader.enabled && !Options::postfx_presets.empty()) {
|
||||
const auto& preset = Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset));
|
||||
p.vignette = preset.vignette;
|
||||
p.scanlines = preset.scanlines;
|
||||
p.chroma = preset.chroma;
|
||||
p.chroma_min = preset.chroma_min;
|
||||
p.chroma_max = preset.chroma_max;
|
||||
p.mask = preset.mask;
|
||||
p.gamma = preset.gamma;
|
||||
p.curvature = preset.curvature;
|
||||
p.bleeding = preset.bleeding;
|
||||
p.flicker = preset.flicker;
|
||||
std::cout << "Screen::applyCurrentPostFXPreset: preset='" << preset.name << "' scan=" << p.scanlines << " vign=" << p.vignette << " chroma=" << p.chroma << '\n';
|
||||
p.scan_dark_ratio = preset.scan_dark_ratio;
|
||||
p.scan_dark_floor = preset.scan_dark_floor;
|
||||
p.scan_edge_soft = preset.scan_edge_soft;
|
||||
std::cout << "Screen::applyCurrentPostFXPreset: preset='" << preset.name << "' scan=" << p.scanlines << " vign=" << p.vignette << " chroma=[" << p.chroma_min << ".." << p.chroma_max << "]\n";
|
||||
}
|
||||
shader_backend_->setPostFXParams(p);
|
||||
}
|
||||
|
||||
@@ -44,13 +44,12 @@ class Screen {
|
||||
// --- 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); }
|
||||
void flash(Color color, float duration_s = 0.167F, float delay_s = 0.0F) { flash_effect_ = FlashEffect(true, duration_s, delay_s, color); }
|
||||
static void toggleShaders(); // Alterna activar/desactivar shaders
|
||||
static void nextShader(); // Cambia entre PostFX y CrtPi
|
||||
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
|
||||
static void nextCrtPiPreset(); // Avanza al siguiente preset CrtPi
|
||||
static void toggleSupersampling(); // Alterna supersampling
|
||||
void toggleFilter(); // Alterna SDL_SCALEMODE_NEAREST ↔ SDL_SCALEMODE_LINEAR
|
||||
void applyFilter(); // Aplica Options::video.scale_mode a game_canvas_
|
||||
static void toggleShaders(); // Alterna activar/desactivar shaders
|
||||
static void nextShader(); // Cambia entre PostFX y CrtPi
|
||||
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
|
||||
static void nextCrtPiPreset(); // Avanza al siguiente preset CrtPi
|
||||
void toggleFilter(); // Alterna SDL_SCALEMODE_NEAREST ↔ SDL_SCALEMODE_LINEAR
|
||||
void applyFilter(); // Aplica Options::video.scale_mode a game_canvas_
|
||||
void toggleIntegerScale();
|
||||
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
|
||||
void setVSync(bool enabled); // Establece el estado del V-Sync
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
// Fragment shader del shader "crtpi" (algoritme CRT-Pi): scanlines amb
|
||||
// pesos gaussians, multisample opcional, gamma i màscara de subpíxels.
|
||||
namespace Rendering::Msl {
|
||||
|
||||
inline constexpr const char* kCrtpiFrag = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
struct CrtPiUniforms {
|
||||
float scanline_weight;
|
||||
float scanline_gap_brightness;
|
||||
float bloom_factor;
|
||||
float input_gamma;
|
||||
float output_gamma;
|
||||
float mask_brightness;
|
||||
float curvature_x;
|
||||
float curvature_y;
|
||||
int mask_type;
|
||||
int enable_scanlines;
|
||||
int enable_multisample;
|
||||
int enable_gamma;
|
||||
int enable_curvature;
|
||||
int enable_sharper;
|
||||
float texture_width;
|
||||
float texture_height;
|
||||
};
|
||||
|
||||
static float2 crtpi_distort(float2 coord, float2 screen_scale, float cx, float cy) {
|
||||
float2 curvature = float2(cx, cy);
|
||||
float2 barrel_scale = 1.0f - (0.23f * curvature);
|
||||
coord *= screen_scale;
|
||||
coord -= 0.5f;
|
||||
float rsq = coord.x * coord.x + coord.y * coord.y;
|
||||
coord += coord * (curvature * rsq);
|
||||
coord *= barrel_scale;
|
||||
if (abs(coord.x) >= 0.5f || abs(coord.y) >= 0.5f) { return float2(-1.0f); }
|
||||
coord += 0.5f;
|
||||
coord /= screen_scale;
|
||||
return coord;
|
||||
}
|
||||
|
||||
static float crtpi_scan_weight(float dist, float sw, float gap) {
|
||||
return max(1.0f - dist * dist * sw, gap);
|
||||
}
|
||||
|
||||
static float crtpi_scan_line(float dy, float filter_w, float sw, float gap, bool ms) {
|
||||
float w = crtpi_scan_weight(dy, sw, gap);
|
||||
if (ms) {
|
||||
w += crtpi_scan_weight(dy - filter_w, sw, gap);
|
||||
w += crtpi_scan_weight(dy + filter_w, sw, gap);
|
||||
w *= 0.3333333f;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
fragment float4 crtpi_fs(PostVOut in [[stage_in]],
|
||||
texture2d<float> tex [[texture(0)]],
|
||||
sampler samp [[sampler(0)]],
|
||||
constant CrtPiUniforms& u [[buffer(0)]]) {
|
||||
float2 tex_size = float2(u.texture_width, u.texture_height);
|
||||
// Amplada del filtre de scanline analític. 768 = alçada de referència
|
||||
// CRT a la qual es va tarar l'algoritme original; 3 = divisió per
|
||||
// subpíxel (R/G/B) del multisample. El resultat escala amb la textura
|
||||
// d'entrada, de manera que més alçada → filtre més fi.
|
||||
const float CRT_REFERENCE_HEIGHT = 768.0f;
|
||||
const float SUBPIXEL_DIV = 3.0f;
|
||||
float filter_width = (CRT_REFERENCE_HEIGHT / u.texture_height) / SUBPIXEL_DIV;
|
||||
float2 texcoord = in.uv;
|
||||
|
||||
if (u.enable_curvature != 0) {
|
||||
texcoord = crtpi_distort(texcoord, float2(1.0f, 1.0f), u.curvature_x, u.curvature_y);
|
||||
if (texcoord.x < 0.0f) { return float4(0.0f, 0.0f, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
float2 coord_in_pixels = texcoord * tex_size;
|
||||
float2 tc;
|
||||
float scan_weight;
|
||||
|
||||
if (u.enable_sharper != 0) {
|
||||
float2 temp = floor(coord_in_pixels) + 0.5f;
|
||||
tc = temp / tex_size;
|
||||
float2 deltas = coord_in_pixels - temp;
|
||||
scan_weight = crtpi_scan_line(deltas.y, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
||||
float2 signs = sign(deltas);
|
||||
deltas.x *= 2.0f;
|
||||
deltas = deltas * deltas;
|
||||
deltas.y = deltas.y * deltas.y;
|
||||
deltas.x *= 0.5f;
|
||||
deltas.y *= 8.0f;
|
||||
deltas /= tex_size;
|
||||
deltas *= signs;
|
||||
tc = tc + deltas;
|
||||
} else {
|
||||
float temp_y = floor(coord_in_pixels.y) + 0.5f;
|
||||
float y_coord = temp_y / tex_size.y;
|
||||
float dy = coord_in_pixels.y - temp_y;
|
||||
scan_weight = crtpi_scan_line(dy, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
||||
float sign_y = sign(dy);
|
||||
dy = dy * dy;
|
||||
dy = dy * dy;
|
||||
dy *= 8.0f;
|
||||
dy /= tex_size.y;
|
||||
dy *= sign_y;
|
||||
tc = float2(texcoord.x, y_coord + dy);
|
||||
}
|
||||
|
||||
float3 colour = tex.sample(samp, tc).rgb;
|
||||
|
||||
if (u.enable_scanlines != 0) {
|
||||
if (u.enable_gamma != 0) { colour = pow(colour, float3(u.input_gamma)); }
|
||||
colour *= scan_weight * u.bloom_factor;
|
||||
if (u.enable_gamma != 0) { colour = pow(colour, float3(1.0f / u.output_gamma)); }
|
||||
}
|
||||
|
||||
if (u.mask_type == 1) {
|
||||
float wm = fract(in.pos.x * 0.5f);
|
||||
float3 mask = (wm < 0.5f) ? float3(u.mask_brightness, 1.0f, u.mask_brightness)
|
||||
: float3(1.0f, u.mask_brightness, 1.0f);
|
||||
colour *= mask;
|
||||
} else if (u.mask_type == 2) {
|
||||
float wm = fract(in.pos.x * 0.3333333f);
|
||||
float3 mask = float3(u.mask_brightness);
|
||||
if (wm < 0.3333333f) mask.x = 1.0f;
|
||||
else if (wm < 0.6666666f) mask.y = 1.0f;
|
||||
else mask.z = 1.0f;
|
||||
colour *= mask;
|
||||
}
|
||||
|
||||
return float4(colour, 1.0f);
|
||||
}
|
||||
)";
|
||||
|
||||
} // namespace Rendering::Msl
|
||||
|
||||
#endif // __APPLE__
|
||||
@@ -0,0 +1,168 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
// Fragment shader del shader "postfx": vignette, chroma, scanlines, mask,
|
||||
// gamma, curvature, bleeding i flicker. Els paràmetres venen via uniforms.
|
||||
//
|
||||
// IMPORTANT: mantenir sincronitzat a mà amb data/shaders/postfx.frag. SDL3 GPU
|
||||
// compila aquest string MSL en runtime; no hi ha generador automàtic. Qualsevol
|
||||
// canvi a la struct d'uniforms o a la lògica del GLSL cal replicar-lo ací al
|
||||
// mateix commit. Mida total = 64 bytes (4 × vec4).
|
||||
namespace Rendering::Msl {
|
||||
|
||||
inline constexpr const char* kPostfxFrag = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_min;
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
float mask_strength;
|
||||
float gamma_strength;
|
||||
float curvature;
|
||||
float bleeding;
|
||||
float pixel_scale;
|
||||
float time;
|
||||
float flicker;
|
||||
float chroma_max;
|
||||
// vec4 #3 — paràmetres de scanlines (exposats per preset YAML)
|
||||
float scan_dark_ratio;
|
||||
float scan_dark_floor;
|
||||
float scan_edge_soft;
|
||||
float pad3;
|
||||
};
|
||||
|
||||
// Mostreig bilinear horitzontal d'un canal RGB. Evita el "tic-tac" del sampler
|
||||
// NEAREST quan l'offset de chroma és subpíxel.
|
||||
static float sampleBilinearX(float2 uv_target, int channel, texture2d<float> scene, sampler samp) {
|
||||
float2 tex_size = float2(scene.get_width(), scene.get_height());
|
||||
float px = uv_target.x * tex_size.x - 0.5f;
|
||||
float p_floor = floor(px);
|
||||
float f = px - p_floor;
|
||||
float4 c0 = scene.sample(samp, float2((p_floor + 0.5f) / tex_size.x, uv_target.y));
|
||||
float4 c1 = scene.sample(samp, float2((p_floor + 1.5f) / tex_size.x, uv_target.y));
|
||||
return mix(c0[channel], c1[channel], f);
|
||||
}
|
||||
|
||||
static float3 rgb_to_ycc(float3 rgb) {
|
||||
return float3(
|
||||
0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b,
|
||||
-0.169f*rgb.r - 0.331f*rgb.g + 0.500f*rgb.b + 0.5f,
|
||||
0.500f*rgb.r - 0.419f*rgb.g - 0.081f*rgb.b + 0.5f
|
||||
);
|
||||
}
|
||||
static float3 ycc_to_rgb(float3 ycc) {
|
||||
float y = ycc.x;
|
||||
float cb = ycc.y - 0.5f;
|
||||
float cr = ycc.z - 0.5f;
|
||||
return clamp(float3(
|
||||
y + 1.402f*cr,
|
||||
y - 0.344f*cb - 0.714f*cr,
|
||||
y + 1.772f*cb
|
||||
), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
fragment float4 postfx_fs(PostVOut in [[stage_in]],
|
||||
texture2d<float> scene [[texture(0)]],
|
||||
sampler samp [[sampler(0)]],
|
||||
constant PostFXUniforms& u [[buffer(0)]]) {
|
||||
float2 uv = in.uv;
|
||||
|
||||
if (u.curvature > 0.0f) {
|
||||
float2 c = uv - 0.5f;
|
||||
float rsq = dot(c, c);
|
||||
float2 dist = float2(0.05f, 0.1f) * u.curvature;
|
||||
float2 barrelScale = 1.0f - 0.23f * dist;
|
||||
c += c * (dist * rsq);
|
||||
c *= barrelScale;
|
||||
if (abs(c.x) >= 0.5f || abs(c.y) >= 0.5f) {
|
||||
return float4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
uv = c + 0.5f;
|
||||
}
|
||||
|
||||
float3 base = scene.sample(samp, uv).rgb;
|
||||
|
||||
float3 colour;
|
||||
if (u.bleeding > 0.0f) {
|
||||
float tw = float(scene.get_width());
|
||||
float step = 1.0f / tw;
|
||||
float3 ycc = rgb_to_ycc(base);
|
||||
float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f*step, 0.0f)).rgb);
|
||||
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f;
|
||||
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
||||
} else {
|
||||
colour = base;
|
||||
}
|
||||
|
||||
// Chroma — varia entre chroma_min i chroma_max via sinusoidal; si min == max
|
||||
// queda estàtic. Mostreig bilinear horitzontal per evitar el "tic-tac" del
|
||||
// NEAREST sampler amb offsets subpíxel.
|
||||
if (u.chroma_min > 0.0f || u.chroma_max > 0.0f) {
|
||||
float ca = mix(u.chroma_min, u.chroma_max, 0.5f + 0.5f * sin(u.time * 7.3f)) * 0.005f;
|
||||
colour.r = sampleBilinearX(uv + float2(ca, 0.0f), 0, scene, samp);
|
||||
colour.b = sampleBilinearX(uv - float2(ca, 0.0f), 2, scene, samp);
|
||||
}
|
||||
|
||||
if (u.gamma_strength > 0.0f) {
|
||||
float3 lin = pow(colour, float3(2.4f));
|
||||
colour = mix(colour, lin, u.gamma_strength);
|
||||
}
|
||||
|
||||
// Scanlines — 3 subpíxels per fila lògica (2 brillants + 1 fosca). Transició
|
||||
// suavitzada amb smoothstep d'ample ≈ 1 píxel físic (estil crtpi: filtratge
|
||||
// analític continu). scan_edge_soft = 0 recupera el step dur de l'original.
|
||||
if (u.scanline_strength > 0.0f) {
|
||||
float ps = max(u.pixel_scale, 1.0f);
|
||||
float sub = fract(uv.y * u.screen_height);
|
||||
float dark_center = 1.0f - u.scan_dark_ratio * 0.5f;
|
||||
float d = abs(sub - dark_center);
|
||||
d = min(d, 1.0f - d);
|
||||
float half_width = u.scan_dark_ratio * 0.5f;
|
||||
float softness = u.scan_edge_soft * 0.5f / ps;
|
||||
float band = 1.0f - smoothstep(half_width - softness, half_width + softness, d);
|
||||
float scan = mix(1.0f, u.scan_dark_floor, band);
|
||||
colour *= mix(1.0f, scan, u.scanline_strength);
|
||||
}
|
||||
|
||||
if (u.gamma_strength > 0.0f) {
|
||||
float3 enc = pow(colour, float3(1.0f/2.2f));
|
||||
colour = mix(colour, enc, u.gamma_strength);
|
||||
}
|
||||
|
||||
float2 d = uv - 0.5f;
|
||||
float vignette = 1.0f - dot(d, d) * u.vignette_strength;
|
||||
colour *= clamp(vignette, 0.0f, 1.0f);
|
||||
|
||||
if (u.mask_strength > 0.0f) {
|
||||
float whichMask = fract(in.pos.x * 0.3333333f);
|
||||
float3 mask = float3(0.80f);
|
||||
if (whichMask < 0.3333333f) mask.x = 1.0f;
|
||||
else if (whichMask < 0.6666667f) mask.y = 1.0f;
|
||||
else mask.z = 1.0f;
|
||||
colour = mix(colour, colour * mask, u.mask_strength);
|
||||
}
|
||||
|
||||
if (u.flicker > 0.0f) {
|
||||
float flicker_wave = sin(u.time * 100.0f) * 0.5f + 0.5f;
|
||||
colour *= 1.0f - u.flicker * 0.04f * flicker_wave;
|
||||
}
|
||||
|
||||
return float4(colour, 1.0f);
|
||||
}
|
||||
)";
|
||||
|
||||
} // namespace Rendering::Msl
|
||||
|
||||
#endif // __APPLE__
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
// Vertex shader compartit per tots els pipelines de post-procés:
|
||||
// fullscreen-triangle que cobreix tota l'àrea del swapchain amb UVs a [0,1].
|
||||
namespace Rendering::Msl {
|
||||
|
||||
inline constexpr const char* kPostfxVert = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
vertex PostVOut postfx_vs(uint vid [[vertex_id]]) {
|
||||
const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} };
|
||||
const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} };
|
||||
PostVOut out;
|
||||
out.pos = float4(positions[vid], 0.0, 1.0);
|
||||
out.uv = uvs[vid];
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
|
||||
} // namespace Rendering::Msl
|
||||
|
||||
#endif // __APPLE__
|
||||
@@ -2,349 +2,22 @@
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#include <algorithm> // std::min, std::max, std::floor
|
||||
#include <cmath> // std::floor, std::ceil
|
||||
#include <algorithm> // std::min, std::max
|
||||
#include <cmath> // std::floor
|
||||
#include <cstring> // memcpy, strlen
|
||||
#include <iostream> // Para std::cout
|
||||
|
||||
#ifndef __APPLE__
|
||||
#include "core/rendering/sdl3gpu/spv/crtpi_frag_spv.h"
|
||||
#include "core/rendering/sdl3gpu/spv/downscale_frag_spv.h"
|
||||
#include "core/rendering/sdl3gpu/spv/postfx_frag_spv.h"
|
||||
#include "core/rendering/sdl3gpu/spv/postfx_vert_spv.h"
|
||||
#include "core/rendering/sdl3gpu/spv/upscale_frag_spv.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
// ============================================================================
|
||||
// MSL shaders (Metal Shading Language) — macOS
|
||||
// ============================================================================
|
||||
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
static const char* POSTFX_VERT_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
vertex PostVOut postfx_vs(uint vid [[vertex_id]]) {
|
||||
const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} };
|
||||
const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} };
|
||||
PostVOut out;
|
||||
out.pos = float4(positions[vid], 0.0, 1.0);
|
||||
out.uv = uvs[vid];
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* POSTFX_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
float mask_strength;
|
||||
float gamma_strength;
|
||||
float curvature;
|
||||
float bleeding;
|
||||
float pixel_scale;
|
||||
float time;
|
||||
float oversample;
|
||||
float flicker;
|
||||
};
|
||||
|
||||
static float3 rgb_to_ycc(float3 rgb) {
|
||||
return float3(
|
||||
0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b,
|
||||
-0.169f*rgb.r - 0.331f*rgb.g + 0.500f*rgb.b + 0.5f,
|
||||
0.500f*rgb.r - 0.419f*rgb.g - 0.081f*rgb.b + 0.5f
|
||||
);
|
||||
}
|
||||
static float3 ycc_to_rgb(float3 ycc) {
|
||||
float y = ycc.x;
|
||||
float cb = ycc.y - 0.5f;
|
||||
float cr = ycc.z - 0.5f;
|
||||
return clamp(float3(
|
||||
y + 1.402f*cr,
|
||||
y - 0.344f*cb - 0.714f*cr,
|
||||
y + 1.772f*cb
|
||||
), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
fragment float4 postfx_fs(PostVOut in [[stage_in]],
|
||||
texture2d<float> scene [[texture(0)]],
|
||||
sampler samp [[sampler(0)]],
|
||||
constant PostFXUniforms& u [[buffer(0)]]) {
|
||||
float2 uv = in.uv;
|
||||
|
||||
if (u.curvature > 0.0f) {
|
||||
float2 c = uv - 0.5f;
|
||||
float rsq = dot(c, c);
|
||||
float2 dist = float2(0.05f, 0.1f) * u.curvature;
|
||||
float2 barrelScale = 1.0f - 0.23f * dist;
|
||||
c += c * (dist * rsq);
|
||||
c *= barrelScale;
|
||||
if (abs(c.x) >= 0.5f || abs(c.y) >= 0.5f) {
|
||||
return float4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
uv = c + 0.5f;
|
||||
}
|
||||
|
||||
float3 base = scene.sample(samp, uv).rgb;
|
||||
|
||||
float3 colour;
|
||||
if (u.bleeding > 0.0f) {
|
||||
float tw = float(scene.get_width());
|
||||
float step = u.oversample / tw;
|
||||
float3 ycc = rgb_to_ycc(base);
|
||||
float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f*step, 0.0f)).rgb);
|
||||
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f;
|
||||
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
||||
} else {
|
||||
colour = base;
|
||||
}
|
||||
|
||||
float ca = u.chroma_strength * 0.005f * (1.0f + 0.15f * sin(u.time * 7.3f));
|
||||
colour.r = scene.sample(samp, uv + float2(ca, 0.0f)).r;
|
||||
colour.b = scene.sample(samp, uv - float2(ca, 0.0f)).b;
|
||||
|
||||
if (u.gamma_strength > 0.0f) {
|
||||
float3 lin = pow(colour, float3(2.4f));
|
||||
colour = mix(colour, lin, u.gamma_strength);
|
||||
}
|
||||
|
||||
const float SCAN_DARK_RATIO = 0.333f;
|
||||
const float SCAN_DARK_FLOOR = 0.42f;
|
||||
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 bright_rows = (ps < 2.0f) ? ps : ((ps < 3.0f) ? 1.0f : floor(ps * (1.0f - SCAN_DARK_RATIO)));
|
||||
float is_dark = step(bright_rows, row_pos);
|
||||
float scan = mix(1.0f, SCAN_DARK_FLOOR, is_dark);
|
||||
colour *= mix(1.0f, scan, u.scanline_strength);
|
||||
}
|
||||
|
||||
if (u.gamma_strength > 0.0f) {
|
||||
float3 enc = pow(colour, float3(1.0f/2.2f));
|
||||
colour = mix(colour, enc, u.gamma_strength);
|
||||
}
|
||||
|
||||
float2 d = uv - 0.5f;
|
||||
float vignette = 1.0f - dot(d, d) * u.vignette_strength;
|
||||
colour *= clamp(vignette, 0.0f, 1.0f);
|
||||
|
||||
if (u.mask_strength > 0.0f) {
|
||||
float whichMask = fract(in.pos.x * 0.3333333f);
|
||||
float3 mask = float3(0.80f);
|
||||
if (whichMask < 0.3333333f) mask.x = 1.0f;
|
||||
else if (whichMask < 0.6666667f) mask.y = 1.0f;
|
||||
else mask.z = 1.0f;
|
||||
colour = mix(colour, colour * mask, u.mask_strength);
|
||||
}
|
||||
|
||||
if (u.flicker > 0.0f) {
|
||||
float flicker_wave = sin(u.time * 100.0f) * 0.5f + 0.5f;
|
||||
colour *= 1.0f - u.flicker * 0.04f * flicker_wave;
|
||||
}
|
||||
|
||||
return float4(colour, 1.0f);
|
||||
}
|
||||
)";
|
||||
static const char* UPSCALE_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
struct VertOut { float4 pos [[position]]; float2 uv; };
|
||||
fragment float4 upscale_fs(VertOut in [[stage_in]],
|
||||
texture2d<float> scene [[texture(0)]],
|
||||
sampler smp [[sampler(0)]])
|
||||
{
|
||||
return scene.sample(smp, in.uv);
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* DOWNSCALE_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
struct VertOut { float4 pos [[position]]; float2 uv; };
|
||||
struct DownscaleUniforms { int algorithm; float pad0; float pad1; float pad2; };
|
||||
|
||||
static float lanczos_w(float t, float a) {
|
||||
t = abs(t);
|
||||
if (t < 0.0001f) { return 1.0f; }
|
||||
if (t >= a) { return 0.0f; }
|
||||
const float PI = 3.14159265358979f;
|
||||
float pt = PI * t;
|
||||
return (a * sin(pt) * sin(pt / a)) / (pt * pt);
|
||||
}
|
||||
|
||||
fragment float4 downscale_fs(VertOut in [[stage_in]],
|
||||
texture2d<float> source [[texture(0)]],
|
||||
sampler smp [[sampler(0)]],
|
||||
constant DownscaleUniforms& u [[buffer(0)]])
|
||||
{
|
||||
float2 src_size = float2(source.get_width(), source.get_height());
|
||||
float2 p = in.uv * src_size;
|
||||
float2 p_floor = floor(p);
|
||||
float a = (u.algorithm == 0) ? 2.0f : 3.0f;
|
||||
int win = int(a);
|
||||
float4 color = float4(0.0f);
|
||||
float weight_sum = 0.0f;
|
||||
for (int j = -win; j <= win; j++) {
|
||||
for (int i = -win; i <= win; i++) {
|
||||
float2 tap_center = p_floor + float2(float(i), float(j)) + 0.5f;
|
||||
float2 offset = tap_center - p;
|
||||
float w = lanczos_w(offset.x, a) * lanczos_w(offset.y, a);
|
||||
color += source.sample(smp, tap_center / src_size) * w;
|
||||
weight_sum += w;
|
||||
}
|
||||
}
|
||||
return (weight_sum > 0.0f) ? (color / weight_sum) : float4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
)";
|
||||
static const char* CRTPI_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
struct CrtPiUniforms {
|
||||
float scanline_weight;
|
||||
float scanline_gap_brightness;
|
||||
float bloom_factor;
|
||||
float input_gamma;
|
||||
float output_gamma;
|
||||
float mask_brightness;
|
||||
float curvature_x;
|
||||
float curvature_y;
|
||||
int mask_type;
|
||||
int enable_scanlines;
|
||||
int enable_multisample;
|
||||
int enable_gamma;
|
||||
int enable_curvature;
|
||||
int enable_sharper;
|
||||
float texture_width;
|
||||
float texture_height;
|
||||
};
|
||||
|
||||
static float2 crtpi_distort(float2 coord, float2 screen_scale, float cx, float cy) {
|
||||
float2 curvature = float2(cx, cy);
|
||||
float2 barrel_scale = 1.0f - (0.23f * curvature);
|
||||
coord *= screen_scale;
|
||||
coord -= 0.5f;
|
||||
float rsq = coord.x * coord.x + coord.y * coord.y;
|
||||
coord += coord * (curvature * rsq);
|
||||
coord *= barrel_scale;
|
||||
if (abs(coord.x) >= 0.5f || abs(coord.y) >= 0.5f) { return float2(-1.0f); }
|
||||
coord += 0.5f;
|
||||
coord /= screen_scale;
|
||||
return coord;
|
||||
}
|
||||
|
||||
static float crtpi_scan_weight(float dist, float sw, float gap) {
|
||||
return max(1.0f - dist * dist * sw, gap);
|
||||
}
|
||||
|
||||
static float crtpi_scan_line(float dy, float filter_w, float sw, float gap, bool ms) {
|
||||
float w = crtpi_scan_weight(dy, sw, gap);
|
||||
if (ms) {
|
||||
w += crtpi_scan_weight(dy - filter_w, sw, gap);
|
||||
w += crtpi_scan_weight(dy + filter_w, sw, gap);
|
||||
w *= 0.3333333f;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
fragment float4 crtpi_fs(PostVOut in [[stage_in]],
|
||||
texture2d<float> tex [[texture(0)]],
|
||||
sampler samp [[sampler(0)]],
|
||||
constant CrtPiUniforms& u [[buffer(0)]]) {
|
||||
float2 tex_size = float2(u.texture_width, u.texture_height);
|
||||
float filter_width = (768.0f / u.texture_height) / 3.0f;
|
||||
float2 texcoord = in.uv;
|
||||
|
||||
if (u.enable_curvature != 0) {
|
||||
texcoord = crtpi_distort(texcoord, float2(1.0f, 1.0f), u.curvature_x, u.curvature_y);
|
||||
if (texcoord.x < 0.0f) { return float4(0.0f, 0.0f, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
float2 coord_in_pixels = texcoord * tex_size;
|
||||
float2 tc;
|
||||
float scan_weight;
|
||||
|
||||
if (u.enable_sharper != 0) {
|
||||
float2 temp = floor(coord_in_pixels) + 0.5f;
|
||||
tc = temp / tex_size;
|
||||
float2 deltas = coord_in_pixels - temp;
|
||||
scan_weight = crtpi_scan_line(deltas.y, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
||||
float2 signs = sign(deltas);
|
||||
deltas.x *= 2.0f;
|
||||
deltas = deltas * deltas;
|
||||
deltas.y = deltas.y * deltas.y;
|
||||
deltas.x *= 0.5f;
|
||||
deltas.y *= 8.0f;
|
||||
deltas /= tex_size;
|
||||
deltas *= signs;
|
||||
tc = tc + deltas;
|
||||
} else {
|
||||
float temp_y = floor(coord_in_pixels.y) + 0.5f;
|
||||
float y_coord = temp_y / tex_size.y;
|
||||
float dy = coord_in_pixels.y - temp_y;
|
||||
scan_weight = crtpi_scan_line(dy, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
||||
float sign_y = sign(dy);
|
||||
dy = dy * dy;
|
||||
dy = dy * dy;
|
||||
dy *= 8.0f;
|
||||
dy /= tex_size.y;
|
||||
dy *= sign_y;
|
||||
tc = float2(texcoord.x, y_coord + dy);
|
||||
}
|
||||
|
||||
float3 colour = tex.sample(samp, tc).rgb;
|
||||
|
||||
if (u.enable_scanlines != 0) {
|
||||
if (u.enable_gamma != 0) { colour = pow(colour, float3(u.input_gamma)); }
|
||||
colour *= scan_weight * u.bloom_factor;
|
||||
if (u.enable_gamma != 0) { colour = pow(colour, float3(1.0f / u.output_gamma)); }
|
||||
}
|
||||
|
||||
if (u.mask_type == 1) {
|
||||
float wm = fract(in.pos.x * 0.5f);
|
||||
float3 mask = (wm < 0.5f) ? float3(u.mask_brightness, 1.0f, u.mask_brightness)
|
||||
: float3(1.0f, u.mask_brightness, 1.0f);
|
||||
colour *= mask;
|
||||
} else if (u.mask_type == 2) {
|
||||
float wm = fract(in.pos.x * 0.3333333f);
|
||||
float3 mask = float3(u.mask_brightness);
|
||||
if (wm < 0.3333333f) mask.x = 1.0f;
|
||||
else if (wm < 0.6666666f) mask.y = 1.0f;
|
||||
else mask.z = 1.0f;
|
||||
colour *= mask;
|
||||
}
|
||||
|
||||
return float4(colour, 1.0f);
|
||||
}
|
||||
)";
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
||||
#endif // __APPLE__
|
||||
#include "core/rendering/sdl3gpu/msl/crtpi_frag.msl.h"
|
||||
#include "core/rendering/sdl3gpu/msl/postfx_frag.msl.h"
|
||||
#include "core/rendering/sdl3gpu/msl/postfx_vert.msl.h"
|
||||
#endif
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
@@ -374,7 +47,6 @@ namespace Rendering {
|
||||
game_width_ = static_cast<int>(fw);
|
||||
game_height_ = static_cast<int>(fh);
|
||||
uniforms_.screen_height = static_cast<float>(game_height_);
|
||||
uniforms_.oversample = static_cast<float>(oversample_);
|
||||
|
||||
// 1. GPU disabled by config
|
||||
if (preferred_driver_ == "none") {
|
||||
@@ -412,7 +84,7 @@ namespace Rendering {
|
||||
SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, bestPresentMode(vsync_));
|
||||
}
|
||||
|
||||
// 3. Create scene texture (always game resolution)
|
||||
// 3. Create scene texture (game resolution)
|
||||
SDL_GPUTextureCreateInfo tex_info = {};
|
||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
@@ -428,9 +100,7 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
ss_factor_ = 0;
|
||||
|
||||
// 4. Create upload transfer buffer (always game resolution)
|
||||
// 4. Create upload transfer buffer (game resolution)
|
||||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
tb_info.size = static_cast<Uint32>(game_width_ * game_height_ * 4);
|
||||
@@ -441,7 +111,7 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. Create samplers
|
||||
// 5. Create nearest sampler
|
||||
SDL_GPUSamplerCreateInfo samp_info = {};
|
||||
samp_info.min_filter = SDL_GPU_FILTER_NEAREST;
|
||||
samp_info.mag_filter = SDL_GPU_FILTER_NEAREST;
|
||||
@@ -456,20 +126,6 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GPUSamplerCreateInfo lsamp_info = {};
|
||||
lsamp_info.min_filter = SDL_GPU_FILTER_LINEAR;
|
||||
lsamp_info.mag_filter = SDL_GPU_FILTER_LINEAR;
|
||||
lsamp_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
|
||||
lsamp_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
lsamp_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
lsamp_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
linear_sampler_ = SDL_CreateGPUSampler(device_, &lsamp_info);
|
||||
if (linear_sampler_ == nullptr) {
|
||||
std::cout << "SDL3GPUShader: failed to create linear sampler: " << SDL_GetError() << '\n';
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 6. Create pipelines
|
||||
if (!createPipeline()) {
|
||||
cleanup();
|
||||
@@ -486,15 +142,14 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// createPipeline — PostFX + Upscale + PostFX offscreen + Downscale pipelines
|
||||
// createPipeline — PostFX → swapchain
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::createPipeline() -> bool { // NOLINT(readability-function-cognitive-complexity)
|
||||
auto SDL3GPUShader::createPipeline() -> bool {
|
||||
const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
|
||||
|
||||
// ---- PostFX pipeline (→ swapchain) ----
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* vert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* vert = createShaderMSL(device_, Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderMSL(device_, Msl::kPostfxFrag, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#else
|
||||
SDL_GPUShader* vert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
@@ -534,114 +189,6 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- Upscale pipeline (scene → scaled_texture_) ----
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* uvert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* ufrag = createShaderMSL(device_, UPSCALE_FRAG_MSL, "upscale_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
#else
|
||||
SDL_GPUShader* uvert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* ufrag = createShaderSPIRV(device_, kupscale_frag_spv, kupscale_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
#endif
|
||||
|
||||
if ((uvert == nullptr) || (ufrag == nullptr)) {
|
||||
if (uvert != nullptr) { SDL_ReleaseGPUShader(device_, uvert); }
|
||||
if (ufrag != nullptr) { SDL_ReleaseGPUShader(device_, ufrag); }
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GPUColorTargetDescription upscale_ct = {};
|
||||
upscale_ct.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
upscale_ct.blend_state = no_blend;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo upscale_pi = {};
|
||||
upscale_pi.vertex_shader = uvert;
|
||||
upscale_pi.fragment_shader = ufrag;
|
||||
upscale_pi.vertex_input_state = no_input;
|
||||
upscale_pi.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
upscale_pi.target_info.num_color_targets = 1;
|
||||
upscale_pi.target_info.color_target_descriptions = &upscale_ct;
|
||||
|
||||
upscale_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &upscale_pi);
|
||||
SDL_ReleaseGPUShader(device_, uvert);
|
||||
SDL_ReleaseGPUShader(device_, ufrag);
|
||||
|
||||
if (upscale_pipeline_ == nullptr) {
|
||||
std::cout << "SDL3GPUShader: upscale pipeline creation failed: " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- PostFX offscreen pipeline (→ B8G8R8A8 texture, for Lanczos path) ----
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* ofvert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* offrag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#else
|
||||
SDL_GPUShader* ofvert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* offrag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#endif
|
||||
|
||||
if ((ofvert == nullptr) || (offrag == nullptr)) {
|
||||
if (ofvert != nullptr) { SDL_ReleaseGPUShader(device_, ofvert); }
|
||||
if (offrag != nullptr) { SDL_ReleaseGPUShader(device_, offrag); }
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GPUColorTargetDescription offscreen_ct = {};
|
||||
offscreen_ct.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
offscreen_ct.blend_state = no_blend;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo offscreen_pi = {};
|
||||
offscreen_pi.vertex_shader = ofvert;
|
||||
offscreen_pi.fragment_shader = offrag;
|
||||
offscreen_pi.vertex_input_state = no_input;
|
||||
offscreen_pi.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
offscreen_pi.target_info.num_color_targets = 1;
|
||||
offscreen_pi.target_info.color_target_descriptions = &offscreen_ct;
|
||||
|
||||
postfx_offscreen_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &offscreen_pi);
|
||||
SDL_ReleaseGPUShader(device_, ofvert);
|
||||
SDL_ReleaseGPUShader(device_, offrag);
|
||||
|
||||
if (postfx_offscreen_pipeline_ == nullptr) {
|
||||
std::cout << "SDL3GPUShader: PostFX offscreen pipeline creation failed: " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- Downscale pipeline (Lanczos → swapchain) ----
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* dvert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* dfrag = createShaderMSL(device_, DOWNSCALE_FRAG_MSL, "downscale_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#else
|
||||
SDL_GPUShader* dvert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* dfrag = createShaderSPIRV(device_, kdownscale_frag_spv, kdownscale_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#endif
|
||||
|
||||
if ((dvert == nullptr) || (dfrag == nullptr)) {
|
||||
if (dvert != nullptr) { SDL_ReleaseGPUShader(device_, dvert); }
|
||||
if (dfrag != nullptr) { SDL_ReleaseGPUShader(device_, dfrag); }
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GPUColorTargetDescription downscale_ct = {};
|
||||
downscale_ct.format = SWAPCHAIN_FMT;
|
||||
downscale_ct.blend_state = no_blend;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo downscale_pi = {};
|
||||
downscale_pi.vertex_shader = dvert;
|
||||
downscale_pi.fragment_shader = dfrag;
|
||||
downscale_pi.vertex_input_state = no_input;
|
||||
downscale_pi.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
downscale_pi.target_info.num_color_targets = 1;
|
||||
downscale_pi.target_info.color_target_descriptions = &downscale_ct;
|
||||
|
||||
downscale_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &downscale_pi);
|
||||
SDL_ReleaseGPUShader(device_, dvert);
|
||||
SDL_ReleaseGPUShader(device_, dfrag);
|
||||
|
||||
if (downscale_pipeline_ == nullptr) {
|
||||
std::cout << "SDL3GPUShader: downscale pipeline creation failed: " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -652,8 +199,8 @@ namespace Rendering {
|
||||
const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
|
||||
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* vert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderMSL(device_, CRTPI_FRAG_MSL, "crtpi_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* vert = createShaderMSL(device_, Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderMSL(device_, Msl::kCrtpiFrag, "crtpi_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#else
|
||||
SDL_GPUShader* vert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderSPIRV(device_, kcrtpi_frag_spv, kcrtpi_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
@@ -691,7 +238,7 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// uploadPixels — direct memcpy, GPU handles upscale
|
||||
// uploadPixels — direct memcpy
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::uploadPixels(const Uint32* pixels, int width, int height) {
|
||||
if (!is_initialized_ || (upload_buffer_ == nullptr)) { return; }
|
||||
@@ -707,24 +254,11 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// render — 3-path: CrtPi direct, PostFX+Lanczos, PostFX direct
|
||||
// render — CrtPi direct OR PostFX direct → swapchain
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::render() { // NOLINT(readability-function-cognitive-complexity)
|
||||
void SDL3GPUShader::render() {
|
||||
if (!is_initialized_) { return; }
|
||||
|
||||
// SS factor calculation
|
||||
if (oversample_ > 1 && game_height_ > 0) {
|
||||
int win_w = 0;
|
||||
int win_h = 0;
|
||||
SDL_GetWindowSizeInPixels(window_, &win_w, &win_h);
|
||||
const float ZOOM = static_cast<float>(win_h) / static_cast<float>(game_height_);
|
||||
const int NEED_FACTOR = calcSsFactor(ZOOM);
|
||||
if (NEED_FACTOR != ss_factor_) {
|
||||
SDL_WaitForGPUIdle(device_);
|
||||
recreateScaledTexture(NEED_FACTOR);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_);
|
||||
if (cmd == nullptr) {
|
||||
std::cout << "SDL3GPUShader: SDL_AcquireGPUCommandBuffer failed: " << SDL_GetError() << '\n';
|
||||
@@ -750,25 +284,6 @@ namespace Rendering {
|
||||
SDL_EndGPUCopyPass(copy);
|
||||
}
|
||||
|
||||
// ---- Upscale pass: scene → scaled_texture_ ----
|
||||
if (oversample_ > 1 && scaled_texture_ != nullptr && upscale_pipeline_ != nullptr) {
|
||||
SDL_GPUColorTargetInfo upscale_target = {};
|
||||
upscale_target.texture = scaled_texture_;
|
||||
upscale_target.load_op = SDL_GPU_LOADOP_DONT_CARE;
|
||||
upscale_target.store_op = SDL_GPU_STOREOP_STORE;
|
||||
|
||||
SDL_GPURenderPass* upass = SDL_BeginGPURenderPass(cmd, &upscale_target, 1, nullptr);
|
||||
if (upass != nullptr) {
|
||||
SDL_BindGPUGraphicsPipeline(upass, upscale_pipeline_);
|
||||
SDL_GPUTextureSamplerBinding ubinding = {};
|
||||
ubinding.texture = scene_texture_;
|
||||
ubinding.sampler = (linear_upscale_ && linear_sampler_ != nullptr) ? linear_sampler_ : sampler_;
|
||||
SDL_BindGPUFragmentSamplers(upass, 0, &ubinding, 1);
|
||||
SDL_DrawGPUPrimitives(upass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(upass);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Acquire swapchain texture ----
|
||||
SDL_GPUTexture* swapchain = nullptr;
|
||||
Uint32 sw = 0;
|
||||
@@ -802,117 +317,37 @@ namespace Rendering {
|
||||
vx = std::floor((static_cast<float>(sw) - vw) * 0.5F);
|
||||
vy = std::floor((static_cast<float>(sh) - vh) * 0.5F);
|
||||
|
||||
if (oversample_ > 1 && ss_factor_ > 0) {
|
||||
uniforms_.pixel_scale = static_cast<float>(ss_factor_);
|
||||
} else {
|
||||
uniforms_.pixel_scale = (game_height_ > 0) ? (vh / static_cast<float>(game_height_)) : 1.0F;
|
||||
}
|
||||
uniforms_.pixel_scale = (game_height_ > 0) ? (vh / static_cast<float>(game_height_)) : 1.0F;
|
||||
uniforms_.time = static_cast<float>(SDL_GetTicks()) / 1000.0F;
|
||||
uniforms_.oversample = (oversample_ > 1 && ss_factor_ > 0)
|
||||
? static_cast<float>(ss_factor_)
|
||||
: 1.0F;
|
||||
|
||||
// ---- Path A: CrtPi direct ----
|
||||
if (active_shader_ == ShaderType::CRTPI && crtpi_pipeline_ != nullptr) {
|
||||
SDL_GPUColorTargetInfo ct = {};
|
||||
ct.texture = swapchain;
|
||||
ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
ct.store_op = SDL_GPU_STOREOP_STORE;
|
||||
ct.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
SDL_GPUColorTargetInfo ct = {};
|
||||
ct.texture = swapchain;
|
||||
ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
ct.store_op = SDL_GPU_STOREOP_STORE;
|
||||
ct.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
|
||||
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr);
|
||||
if (pass != nullptr) {
|
||||
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr);
|
||||
if (pass != nullptr) {
|
||||
SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
|
||||
SDL_SetGPUViewport(pass, &vp);
|
||||
|
||||
SDL_GPUTextureSamplerBinding binding = {};
|
||||
binding.texture = scene_texture_;
|
||||
binding.sampler = sampler_;
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
|
||||
|
||||
if (active_shader_ == ShaderType::CRTPI && crtpi_pipeline_ != nullptr) {
|
||||
SDL_BindGPUGraphicsPipeline(pass, crtpi_pipeline_);
|
||||
SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
|
||||
SDL_SetGPUViewport(pass, &vp);
|
||||
|
||||
SDL_GPUTextureSamplerBinding binding = {};
|
||||
binding.texture = scene_texture_;
|
||||
binding.sampler = sampler_;
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
|
||||
|
||||
crtpi_uniforms_.texture_width = static_cast<float>(game_width_);
|
||||
crtpi_uniforms_.texture_height = static_cast<float>(game_height_);
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &crtpi_uniforms_, sizeof(CrtPiUniforms));
|
||||
|
||||
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(pass);
|
||||
}
|
||||
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Path B: PostFX + Lanczos (SS active + algo selected) ----
|
||||
const bool USE_LANCZOS = (oversample_ > 1 && downscale_algo_ > 0 && scaled_texture_ != nullptr && postfx_texture_ != nullptr && postfx_offscreen_pipeline_ != nullptr && downscale_pipeline_ != nullptr);
|
||||
|
||||
if (USE_LANCZOS) {
|
||||
// Pass B1: PostFX → postfx_texture_
|
||||
SDL_GPUColorTargetInfo postfx_ct = {};
|
||||
postfx_ct.texture = postfx_texture_;
|
||||
postfx_ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
postfx_ct.store_op = SDL_GPU_STOREOP_STORE;
|
||||
postfx_ct.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
|
||||
SDL_GPURenderPass* ppass = SDL_BeginGPURenderPass(cmd, &postfx_ct, 1, nullptr);
|
||||
if (ppass != nullptr) {
|
||||
SDL_BindGPUGraphicsPipeline(ppass, postfx_offscreen_pipeline_);
|
||||
SDL_GPUTextureSamplerBinding pbinding = {};
|
||||
pbinding.texture = scaled_texture_;
|
||||
pbinding.sampler = sampler_;
|
||||
SDL_BindGPUFragmentSamplers(ppass, 0, &pbinding, 1);
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms));
|
||||
SDL_DrawGPUPrimitives(ppass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(ppass);
|
||||
}
|
||||
|
||||
// Pass B2: Lanczos downscale → swapchain
|
||||
SDL_GPUColorTargetInfo ds_ct = {};
|
||||
ds_ct.texture = swapchain;
|
||||
ds_ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
ds_ct.store_op = SDL_GPU_STOREOP_STORE;
|
||||
ds_ct.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
|
||||
SDL_GPURenderPass* dpass = SDL_BeginGPURenderPass(cmd, &ds_ct, 1, nullptr);
|
||||
if (dpass != nullptr) {
|
||||
SDL_BindGPUGraphicsPipeline(dpass, downscale_pipeline_);
|
||||
SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
|
||||
SDL_SetGPUViewport(dpass, &vp);
|
||||
SDL_GPUTextureSamplerBinding dbinding = {};
|
||||
dbinding.texture = postfx_texture_;
|
||||
dbinding.sampler = sampler_;
|
||||
SDL_BindGPUFragmentSamplers(dpass, 0, &dbinding, 1);
|
||||
DownscaleUniforms du = {.algorithm = downscale_algo_ - 1, .pad0 = 0.0F, .pad1 = 0.0F, .pad2 = 0.0F};
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &du, sizeof(DownscaleUniforms));
|
||||
SDL_DrawGPUPrimitives(dpass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(dpass);
|
||||
}
|
||||
} else {
|
||||
// ---- Path C: PostFX direct → swapchain ----
|
||||
SDL_GPUColorTargetInfo ct = {};
|
||||
ct.texture = swapchain;
|
||||
ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
ct.store_op = SDL_GPU_STOREOP_STORE;
|
||||
ct.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
|
||||
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr);
|
||||
if (pass != nullptr) {
|
||||
} else {
|
||||
SDL_BindGPUGraphicsPipeline(pass, pipeline_);
|
||||
SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
|
||||
SDL_SetGPUViewport(pass, &vp);
|
||||
|
||||
SDL_GPUTexture* input_texture = (oversample_ > 1 && scaled_texture_ != nullptr) ? scaled_texture_ : scene_texture_;
|
||||
SDL_GPUSampler* active_sampler = (oversample_ > 1 && linear_sampler_ != nullptr) ? linear_sampler_ : sampler_;
|
||||
|
||||
SDL_GPUTextureSamplerBinding binding = {};
|
||||
binding.texture = input_texture;
|
||||
binding.sampler = active_sampler;
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
|
||||
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms));
|
||||
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(pass);
|
||||
}
|
||||
|
||||
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(pass);
|
||||
}
|
||||
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
@@ -942,13 +377,7 @@ namespace Rendering {
|
||||
|
||||
release_pipeline(pipeline_);
|
||||
release_pipeline(crtpi_pipeline_);
|
||||
release_pipeline(postfx_offscreen_pipeline_);
|
||||
release_pipeline(upscale_pipeline_);
|
||||
release_pipeline(downscale_pipeline_);
|
||||
release_texture(scene_texture_);
|
||||
release_texture(scaled_texture_);
|
||||
release_texture(postfx_texture_);
|
||||
ss_factor_ = 0;
|
||||
if (upload_buffer_ != nullptr) {
|
||||
SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
|
||||
upload_buffer_ = nullptr;
|
||||
@@ -957,10 +386,6 @@ namespace Rendering {
|
||||
SDL_ReleaseGPUSampler(device_, sampler_);
|
||||
sampler_ = nullptr;
|
||||
}
|
||||
if (linear_sampler_ != nullptr) {
|
||||
SDL_ReleaseGPUSampler(device_, linear_sampler_);
|
||||
linear_sampler_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1028,13 +453,17 @@ namespace Rendering {
|
||||
|
||||
void SDL3GPUShader::setPostFXParams(const PostFXParams& p) {
|
||||
uniforms_.vignette_strength = p.vignette;
|
||||
uniforms_.chroma_strength = p.chroma;
|
||||
uniforms_.chroma_min = p.chroma_min;
|
||||
uniforms_.chroma_max = p.chroma_max;
|
||||
uniforms_.scanline_strength = p.scanlines;
|
||||
uniforms_.mask_strength = p.mask;
|
||||
uniforms_.gamma_strength = p.gamma;
|
||||
uniforms_.curvature = p.curvature;
|
||||
uniforms_.bleeding = p.bleeding;
|
||||
uniforms_.flicker = p.flicker;
|
||||
uniforms_.scan_dark_ratio = p.scan_dark_ratio;
|
||||
uniforms_.scan_dark_floor = p.scan_dark_floor;
|
||||
uniforms_.scan_edge_soft = p.scan_edge_soft;
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setCrtPiParams(const CrtPiParams& p) {
|
||||
@@ -1080,28 +509,6 @@ namespace Rendering {
|
||||
integer_scale_ = integer_scale;
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setOversample(int factor) {
|
||||
const int NEW_FACTOR = std::max(1, factor);
|
||||
if (NEW_FACTOR == oversample_) { return; }
|
||||
oversample_ = NEW_FACTOR;
|
||||
if (is_initialized_) {
|
||||
reinitTexturesAndBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setLinearUpscale(bool linear) {
|
||||
linear_upscale_ = linear;
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setDownscaleAlgo(int algo) {
|
||||
downscale_algo_ = std::max(0, std::min(algo, 2));
|
||||
}
|
||||
|
||||
auto SDL3GPUShader::getSsTextureSize() const -> std::pair<int, int> {
|
||||
if (ss_factor_ <= 1) { return {0, 0}; }
|
||||
return {game_width_ * ss_factor_, game_height_ * ss_factor_};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// reinitTexturesAndBuffer
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1113,18 +520,12 @@ namespace Rendering {
|
||||
SDL_ReleaseGPUTexture(device_, scene_texture_);
|
||||
scene_texture_ = nullptr;
|
||||
}
|
||||
if (scaled_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device_, scaled_texture_);
|
||||
scaled_texture_ = nullptr;
|
||||
}
|
||||
ss_factor_ = 0;
|
||||
if (upload_buffer_ != nullptr) {
|
||||
SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
|
||||
upload_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
uniforms_.screen_height = static_cast<float>(game_height_);
|
||||
uniforms_.oversample = static_cast<float>(oversample_);
|
||||
|
||||
SDL_GPUTextureCreateInfo tex_info = {};
|
||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
@@ -1151,55 +552,7 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << "SDL3GPUShader: reinit — scene " << game_width_ << "x" << game_height_ << ", SS " << (oversample_ > 1 ? "on" : "off") << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
auto SDL3GPUShader::calcSsFactor(float zoom) -> int {
|
||||
const int MULTIPLE = 3;
|
||||
const int N = static_cast<int>(std::ceil(zoom / static_cast<float>(MULTIPLE)));
|
||||
return std::max(1, N) * MULTIPLE;
|
||||
}
|
||||
|
||||
auto SDL3GPUShader::recreateScaledTexture(int factor) -> bool {
|
||||
if (scaled_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device_, scaled_texture_);
|
||||
scaled_texture_ = nullptr;
|
||||
}
|
||||
if (postfx_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device_, postfx_texture_);
|
||||
postfx_texture_ = nullptr;
|
||||
}
|
||||
ss_factor_ = 0;
|
||||
|
||||
const int W = game_width_ * factor;
|
||||
const int H = game_height_ * factor;
|
||||
|
||||
SDL_GPUTextureCreateInfo info = {};
|
||||
info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
|
||||
info.width = static_cast<Uint32>(W);
|
||||
info.height = static_cast<Uint32>(H);
|
||||
info.layer_count_or_depth = 1;
|
||||
info.num_levels = 1;
|
||||
|
||||
scaled_texture_ = SDL_CreateGPUTexture(device_, &info);
|
||||
if (scaled_texture_ == nullptr) {
|
||||
std::cout << "SDL3GPUShader: failed to create scaled texture " << W << "x" << H << ": " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
postfx_texture_ = SDL_CreateGPUTexture(device_, &info);
|
||||
if (postfx_texture_ == nullptr) {
|
||||
std::cout << "SDL3GPUShader: failed to create postfx texture " << W << "x" << H << ": " << SDL_GetError() << '\n';
|
||||
SDL_ReleaseGPUTexture(device_, scaled_texture_);
|
||||
scaled_texture_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
ss_factor_ = factor;
|
||||
std::cout << "SDL3GPUShader: scaled+postfx textures " << W << "x" << H << " (factor " << factor << "x)" << '\n';
|
||||
std::cout << "SDL3GPUShader: reinit — scene " << game_width_ << "x" << game_height_ << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,25 +4,32 @@
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "core/rendering/shader_backend.hpp"
|
||||
|
||||
// PostFX uniforms pushed to fragment stage each frame.
|
||||
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
|
||||
// 16 floats = 64 bytes (4 × vec4) — std140/scalar layout for Vulkan + Metal.
|
||||
struct PostFXUniforms {
|
||||
// vec4 #0
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float chroma_min;
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
// vec4 #1
|
||||
float mask_strength;
|
||||
float gamma_strength;
|
||||
float curvature;
|
||||
float bleeding;
|
||||
// vec4 #2
|
||||
float pixel_scale;
|
||||
float time;
|
||||
float oversample;
|
||||
float flicker;
|
||||
float chroma_max;
|
||||
// vec4 #3 — paràmetres de scanlines exposats al preset YAML
|
||||
float scan_dark_ratio;
|
||||
float scan_dark_floor;
|
||||
float scan_edge_soft;
|
||||
float pad3;
|
||||
};
|
||||
|
||||
// CrtPi uniforms pushed to fragment stage each frame.
|
||||
@@ -46,22 +53,13 @@ struct CrtPiUniforms {
|
||||
float texture_height;
|
||||
};
|
||||
|
||||
// Downscale uniforms for Lanczos downscale fragment stage.
|
||||
// 1 int + 3 floats = 16 bytes.
|
||||
struct DownscaleUniforms {
|
||||
int algorithm;
|
||||
float pad0;
|
||||
float pad1;
|
||||
float pad2;
|
||||
};
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/**
|
||||
* @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
|
||||
*
|
||||
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
|
||||
* → [Upscale →] PostFX/CrtPi render pass → [Lanczos downscale →] swapchain → present
|
||||
* → PostFX/CrtPi render pass → swapchain → present
|
||||
*/
|
||||
class SDL3GPUShader : public ShaderBackend {
|
||||
public:
|
||||
@@ -85,13 +83,6 @@ namespace Rendering {
|
||||
void setPostFXParams(const PostFXParams& p) override;
|
||||
void setVSync(bool vsync) override;
|
||||
void setScaleMode(bool integer_scale) override;
|
||||
void setOversample(int factor) override;
|
||||
|
||||
void setLinearUpscale(bool linear) override;
|
||||
[[nodiscard]] auto isLinearUpscale() const -> bool override { return linear_upscale_; }
|
||||
void setDownscaleAlgo(int algo) override;
|
||||
[[nodiscard]] auto getDownscaleAlgo() const -> int override { return downscale_algo_; }
|
||||
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;
|
||||
|
||||
void setActiveShader(ShaderType type) override;
|
||||
void setCrtPiParams(const CrtPiParams& p) override;
|
||||
@@ -116,39 +107,27 @@ namespace Rendering {
|
||||
auto createPipeline() -> bool;
|
||||
auto createCrtPiPipeline() -> bool;
|
||||
auto reinitTexturesAndBuffer() -> bool;
|
||||
auto recreateScaledTexture(int factor) -> bool;
|
||||
static auto calcSsFactor(float zoom) -> int;
|
||||
[[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;
|
||||
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_GPUDevice* device_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr;
|
||||
SDL_GPUTexture* scene_texture_ = nullptr;
|
||||
SDL_GPUTexture* scaled_texture_ = nullptr;
|
||||
SDL_GPUTexture* postfx_texture_ = nullptr;
|
||||
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
||||
SDL_GPUSampler* sampler_ = nullptr;
|
||||
SDL_GPUSampler* linear_sampler_ = nullptr;
|
||||
|
||||
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .oversample = 1.0F};
|
||||
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_min = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .chroma_max = 0.15F, .scan_dark_ratio = 0.333F, .scan_dark_floor = 0.42F, .scan_edge_soft = 1.0F};
|
||||
CrtPiUniforms crtpi_uniforms_{.scanline_weight = 6.0F, .scanline_gap_brightness = 0.12F, .bloom_factor = 3.5F, .input_gamma = 2.4F, .output_gamma = 2.2F, .mask_brightness = 0.80F, .curvature_x = 0.05F, .curvature_y = 0.10F, .mask_type = 2, .enable_scanlines = 1, .enable_multisample = 1, .enable_gamma = 1};
|
||||
ShaderType active_shader_ = ShaderType::POSTFX;
|
||||
|
||||
int game_width_ = 0;
|
||||
int game_height_ = 0;
|
||||
int ss_factor_ = 0;
|
||||
int oversample_ = 1;
|
||||
int downscale_algo_ = 1;
|
||||
std::string driver_name_;
|
||||
std::string preferred_driver_;
|
||||
bool is_initialized_ = false;
|
||||
bool vsync_ = true;
|
||||
bool integer_scale_ = false;
|
||||
bool linear_upscale_ = false;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,634 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
static const uint8_t kupscale_frag_spv[] = {
|
||||
0x03,
|
||||
0x02,
|
||||
0x23,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x14,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x53,
|
||||
0x4c,
|
||||
0x2e,
|
||||
0x73,
|
||||
0x74,
|
||||
0x64,
|
||||
0x2e,
|
||||
0x34,
|
||||
0x35,
|
||||
0x30,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0e,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0f,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x6d,
|
||||
0x61,
|
||||
0x69,
|
||||
0x6e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x10,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xc2,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0a,
|
||||
0x00,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x5f,
|
||||
0x47,
|
||||
0x4f,
|
||||
0x4f,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x45,
|
||||
0x5f,
|
||||
0x63,
|
||||
0x70,
|
||||
0x70,
|
||||
0x5f,
|
||||
0x73,
|
||||
0x74,
|
||||
0x79,
|
||||
0x6c,
|
||||
0x65,
|
||||
0x5f,
|
||||
0x6c,
|
||||
0x69,
|
||||
0x6e,
|
||||
0x65,
|
||||
0x5f,
|
||||
0x64,
|
||||
0x69,
|
||||
0x72,
|
||||
0x65,
|
||||
0x63,
|
||||
0x74,
|
||||
0x69,
|
||||
0x76,
|
||||
0x65,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x08,
|
||||
0x00,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x5f,
|
||||
0x47,
|
||||
0x4f,
|
||||
0x4f,
|
||||
0x47,
|
||||
0x4c,
|
||||
0x45,
|
||||
0x5f,
|
||||
0x69,
|
||||
0x6e,
|
||||
0x63,
|
||||
0x6c,
|
||||
0x75,
|
||||
0x64,
|
||||
0x65,
|
||||
0x5f,
|
||||
0x64,
|
||||
0x69,
|
||||
0x72,
|
||||
0x65,
|
||||
0x63,
|
||||
0x74,
|
||||
0x69,
|
||||
0x76,
|
||||
0x65,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x6d,
|
||||
0x61,
|
||||
0x69,
|
||||
0x6e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x6f,
|
||||
0x75,
|
||||
0x74,
|
||||
0x5f,
|
||||
0x63,
|
||||
0x6f,
|
||||
0x6c,
|
||||
0x6f,
|
||||
0x72,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x73,
|
||||
0x63,
|
||||
0x65,
|
||||
0x6e,
|
||||
0x65,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x76,
|
||||
0x5f,
|
||||
0x75,
|
||||
0x76,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x21,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x22,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x47,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x13,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x21,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x16,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x17,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x08,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3b,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x08,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x19,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x0a,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1b,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0a,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0c,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3b,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0c,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x17,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0f,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x10,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0f,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3b,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x10,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x36,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xf8,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3d,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0b,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3d,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0f,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x12,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x57,
|
||||
0x00,
|
||||
0x05,
|
||||
0x00,
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x13,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0e,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x12,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3e,
|
||||
0x00,
|
||||
0x03,
|
||||
0x00,
|
||||
0x09,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x13,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xfd,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
0x38,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
};
|
||||
static const size_t kupscale_frag_spv_size = 628;
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
@@ -18,12 +17,19 @@ namespace Rendering {
|
||||
struct PostFXParams {
|
||||
float vignette = 0.0F;
|
||||
float scanlines = 0.0F;
|
||||
float chroma = 0.0F;
|
||||
// Aberració cromàtica — varia entre min i max via sinusoidal; si coincideixen
|
||||
// queda estàtica. Permet un "floor" perquè la imatge mai sigui lliure de chroma.
|
||||
float chroma_min = 0.0F;
|
||||
float chroma_max = 0.0F;
|
||||
float mask = 0.0F;
|
||||
float gamma = 0.0F;
|
||||
float curvature = 0.0F;
|
||||
float bleeding = 0.0F;
|
||||
float flicker = 0.0F;
|
||||
// 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};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -66,15 +72,6 @@ namespace Rendering {
|
||||
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
|
||||
virtual void setVSync(bool /*vsync*/) {}
|
||||
virtual void setScaleMode(bool /*integer_scale*/) {}
|
||||
virtual void setOversample(int /*factor*/) {}
|
||||
|
||||
virtual void setLinearUpscale(bool /*linear*/) {}
|
||||
[[nodiscard]] virtual auto isLinearUpscale() const -> bool { return false; }
|
||||
|
||||
virtual void setDownscaleAlgo(int /*algo*/) {}
|
||||
[[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; }
|
||||
|
||||
[[nodiscard]] virtual auto getSsTextureSize() const -> std::pair<int, int> { return {0, 0}; }
|
||||
|
||||
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
|
||||
|
||||
|
||||
@@ -188,9 +188,6 @@ namespace Defaults::Video {
|
||||
constexpr bool INTEGER_SCALE = true;
|
||||
constexpr bool GPU_ACCELERATION = true;
|
||||
constexpr bool SHADER_ENABLED = false;
|
||||
constexpr bool SUPERSAMPLING = false;
|
||||
constexpr bool LINEAR_UPSCALE = false;
|
||||
constexpr int DOWNSCALE_ALGO = 1;
|
||||
} // namespace Defaults::Video
|
||||
|
||||
namespace Defaults::Music {
|
||||
|
||||
+43
-84
@@ -25,14 +25,20 @@ namespace Options {
|
||||
GamepadManager gamepad_manager; // Opciones de mando para cada jugador
|
||||
Keyboard keyboard; // Opciones para el teclado
|
||||
PendingChanges pending_changes; // Opciones que se aplican al cerrar
|
||||
std::vector<PostFXPreset> postfx_presets = {
|
||||
{.name = "CRT", .vignette = 0.15F, .scanlines = 0.7F, .chroma = 0.2F, .mask = 0.5F, .gamma = 0.1F, .curvature = 0.0F, .bleeding = 0.0F, .flicker = 0.0F},
|
||||
{.name = "NTSC", .vignette = 0.4F, .scanlines = 0.5F, .chroma = 0.2F, .mask = 0.3F, .gamma = 0.3F, .curvature = 0.0F, .bleeding = 0.6F, .flicker = 0.0F},
|
||||
{.name = "Curved", .vignette = 0.5F, .scanlines = 0.6F, .chroma = 0.1F, .mask = 0.4F, .gamma = 0.4F, .curvature = 0.8F, .bleeding = 0.0F, .flicker = 0.0F},
|
||||
{.name = "Scanlines", .vignette = 0.0F, .scanlines = 0.8F, .chroma = 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 = 0.05F, .mask = 0.0F, .gamma = 0.2F, .curvature = 0.0F, .bleeding = 0.0F, .flicker = 0.0F},
|
||||
{.name = "CRT Live", .vignette = 0.15F, .scanlines = 0.6F, .chroma = 0.3F, .mask = 0.3F, .gamma = 0.1F, .curvature = 0.0F, .bleeding = 0.4F, .flicker = 0.8F},
|
||||
};
|
||||
namespace {
|
||||
auto defaultPostFXPresets() -> std::vector<PostFXPreset> {
|
||||
return {
|
||||
{.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.17F, .chroma_max = 0.23F, .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.10F, .chroma_max = 0.10F, .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.255F, .chroma_max = 0.365F, .mask = 0.3F, .gamma = 0.4F, .curvature = 0.3F, .bleeding = 0.4F, .flicker = 0.8F},
|
||||
};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::vector<PostFXPreset> postfx_presets = defaultPostFXPresets();
|
||||
std::string postfx_file_path;
|
||||
std::vector<CrtPiPreset> crtpi_presets;
|
||||
std::string crtpi_file_path;
|
||||
@@ -88,12 +94,16 @@ namespace Options {
|
||||
}
|
||||
parseField(p, "vignette", preset.vignette);
|
||||
parseField(p, "scanlines", preset.scanlines);
|
||||
parseField(p, "chroma", preset.chroma);
|
||||
parseField(p, "chroma_min", preset.chroma_min);
|
||||
parseField(p, "chroma_max", preset.chroma_max);
|
||||
parseField(p, "mask", preset.mask);
|
||||
parseField(p, "gamma", preset.gamma);
|
||||
parseField(p, "curvature", preset.curvature);
|
||||
parseField(p, "bleeding", preset.bleeding);
|
||||
parseField(p, "flicker", preset.flicker);
|
||||
parseField(p, "scan_dark_ratio", preset.scan_dark_ratio);
|
||||
parseField(p, "scan_dark_floor", preset.scan_dark_floor);
|
||||
parseField(p, "scan_edge_soft", preset.scan_edge_soft);
|
||||
postfx_presets.push_back(preset);
|
||||
}
|
||||
}
|
||||
@@ -130,6 +140,10 @@ namespace Options {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (postfx_presets.empty()) {
|
||||
postfx_presets = defaultPostFXPresets();
|
||||
}
|
||||
|
||||
std::ofstream file(postfx_file_path);
|
||||
if (!file.is_open()) {
|
||||
std::cout << "Error: " << postfx_file_path << " can't be opened for writing" << '\n';
|
||||
@@ -140,79 +154,35 @@ 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 (RGB color fringing) — intensitat\n";
|
||||
file << "# varia entre min i max amb una sinusoidal; si coincideixen, queda estàtica\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 << "# scan_dark_ratio: fracció obscura dins de cada fila lògica (1/3 ≈ 0.333)\n";
|
||||
file << "# scan_dark_floor: brillantor mínima de la subfila fosca\n";
|
||||
file << "# scan_edge_soft: 0 = step dur, 1 = transició d'1 píxel físic (estil crtpi)\n";
|
||||
file << "\n";
|
||||
file << "presets:\n";
|
||||
file << " - name: \"CRT\"\n";
|
||||
file << " vignette: 0.15\n";
|
||||
file << " scanlines: 0.7\n";
|
||||
file << " chroma: 0.2\n";
|
||||
file << " mask: 0.5\n";
|
||||
file << " gamma: 0.1\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.3\n";
|
||||
file << " gamma: 0.3\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.4\n";
|
||||
file << " gamma: 0.4\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.2\n";
|
||||
file << " curvature: 0.0\n";
|
||||
file << " bleeding: 0.0\n";
|
||||
file << " flicker: 0.0\n";
|
||||
file << " - name: \"CRT Live\"\n";
|
||||
file << " vignette: 0.15\n";
|
||||
file << " scanlines: 0.6\n";
|
||||
file << " chroma: 0.3\n";
|
||||
file << " mask: 0.3\n";
|
||||
file << " gamma: 0.1\n";
|
||||
file << " curvature: 0.0\n";
|
||||
file << " bleeding: 0.4\n";
|
||||
file << " flicker: 0.8\n";
|
||||
for (const auto& preset : postfx_presets) {
|
||||
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();
|
||||
|
||||
// Cargar los presets recién escritos
|
||||
postfx_presets.clear();
|
||||
postfx_presets.push_back({"CRT", 0.15F, 0.7F, 0.2F, 0.5F, 0.1F, 0.0F, 0.0F, 0.0F});
|
||||
postfx_presets.push_back({"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F});
|
||||
postfx_presets.push_back({"Curved", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 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.2F, 0.0F, 0.0F, 0.0F});
|
||||
postfx_presets.push_back({"CRT Live", 0.15F, 0.6F, 0.3F, 0.3F, 0.1F, 0.0F, 0.4F, 0.8F});
|
||||
video.shader.current_postfx_preset = 0;
|
||||
|
||||
return true;
|
||||
@@ -376,13 +346,6 @@ namespace Options {
|
||||
video.shader.current_shader = (shader_name == "crtpi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
|
||||
}
|
||||
}
|
||||
|
||||
if (vid.contains("supersampling")) {
|
||||
const auto& ss_node = vid["supersampling"];
|
||||
parseField(ss_node, "enabled", video.supersampling.enabled);
|
||||
parseField(ss_node, "linear_upscale", video.supersampling.linear_upscale);
|
||||
parseField(ss_node, "downscale_algo", video.supersampling.downscale_algo);
|
||||
}
|
||||
}
|
||||
|
||||
void loadAudioFromYaml(const fkyaml::node& yaml) {
|
||||
@@ -564,10 +527,6 @@ namespace Options {
|
||||
file << " postfx_preset: \"" << postfx_name << "\"\n";
|
||||
file << " crtpi_preset: \"" << crtpi_name << "\"\n";
|
||||
}
|
||||
file << " supersampling:\n";
|
||||
file << " enabled: " << boolToString(video.supersampling.enabled) << "\n";
|
||||
file << " linear_upscale: " << boolToString(video.supersampling.linear_upscale) << "\n";
|
||||
file << " downscale_algo: " << video.supersampling.downscale_algo << "\n";
|
||||
file << "\n";
|
||||
|
||||
// AUDIO
|
||||
|
||||
+9
-10
@@ -29,12 +29,18 @@ namespace Options {
|
||||
std::string name;
|
||||
float vignette{0.6F};
|
||||
float scanlines{0.7F};
|
||||
float chroma{0.15F};
|
||||
float mask{0.0F};
|
||||
float gamma{0.0F};
|
||||
// Aberració cromàtica entre min i max (si coincideixen, estàtica).
|
||||
float chroma_min{0.15F};
|
||||
float chroma_max{0.15F};
|
||||
float mask{0.6F};
|
||||
float gamma{0.8F};
|
||||
float curvature{0.0F};
|
||||
float bleeding{0.0F};
|
||||
float flicker{0.0F};
|
||||
// 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};
|
||||
};
|
||||
|
||||
struct CrtPiPreset {
|
||||
@@ -66,12 +72,6 @@ namespace Options {
|
||||
std::string preferred_driver;
|
||||
};
|
||||
|
||||
struct Supersampling {
|
||||
bool enabled{Defaults::Video::SUPERSAMPLING};
|
||||
bool linear_upscale{Defaults::Video::LINEAR_UPSCALE};
|
||||
int downscale_algo{Defaults::Video::DOWNSCALE_ALGO};
|
||||
};
|
||||
|
||||
struct ShaderConfig {
|
||||
bool enabled{Defaults::Video::SHADER_ENABLED};
|
||||
Rendering::ShaderType current_shader{Rendering::ShaderType::POSTFX};
|
||||
@@ -88,7 +88,6 @@ namespace Options {
|
||||
bool integer_scale = Defaults::Video::INTEGER_SCALE;
|
||||
std::string info;
|
||||
GPU gpu{};
|
||||
Supersampling supersampling{};
|
||||
ShaderConfig shader{};
|
||||
};
|
||||
|
||||
|
||||
@@ -375,11 +375,6 @@ void ServiceMenu::addVideoOptions() {
|
||||
addVideoShaderOption();
|
||||
addVideoPresetOption();
|
||||
|
||||
options_.push_back(std::make_unique<BoolOption>(
|
||||
Lang::getText("[SERVICE_MENU] SUPERSAMPLING"),
|
||||
SettingsGroup::VIDEO,
|
||||
&Options::video.supersampling.enabled));
|
||||
|
||||
options_.push_back(std::make_unique<BoolOption>(
|
||||
Lang::getText("[SERVICE_MENU] VSYNC"),
|
||||
SettingsGroup::VIDEO,
|
||||
@@ -702,13 +697,6 @@ void ServiceMenu::setHiddenOptions() {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] SUPERSAMPLING"));
|
||||
if (option != nullptr) {
|
||||
option->setHidden(!HW_ACCEL || !Options::video.shader.enabled || Options::video.shader.current_shader != Rendering::ShaderType::POSTFX);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// En la versión web no tiene sentido exponer: permitir apagar el sistema
|
||||
// (no aplica al navegador) ni salir del juego (lo gestiona el navegador).
|
||||
|
||||
@@ -15,8 +15,6 @@ cmake_policy(SET CMP0007 NEW)
|
||||
set(SHADER_SOURCES
|
||||
"postfx.vert"
|
||||
"postfx.frag"
|
||||
"upscale.frag"
|
||||
"downscale.frag"
|
||||
"crtpi_frag.glsl"
|
||||
)
|
||||
|
||||
@@ -24,15 +22,11 @@ set(SHADER_SOURCES
|
||||
set(SHADER_VARS
|
||||
"kpostfx_vert_spv"
|
||||
"kpostfx_frag_spv"
|
||||
"kupscale_frag_spv"
|
||||
"kdownscale_frag_spv"
|
||||
"kcrtpi_frag_spv"
|
||||
)
|
||||
|
||||
# Flags extra de glslc para cada shader (vacío si no hay)
|
||||
set(SHADER_FLAGS
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
"-fshader-stage=frag"
|
||||
|
||||
Reference in New Issue
Block a user