sdl3gpu
This commit is contained in:
@@ -34,6 +34,11 @@ enum inputs_e {
|
||||
input_window_inc_size,
|
||||
input_window_dec_size,
|
||||
|
||||
// GPU / shaders (hotkeys provisionales hasta que haya menú de opciones)
|
||||
input_toggle_gpu,
|
||||
input_toggle_shader,
|
||||
input_toggle_shader_type,
|
||||
|
||||
// Input obligatorio
|
||||
input_number_of_inputs
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // for max, min
|
||||
#include <cstring> // for memcpy
|
||||
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
||||
#include <string> // for basic_string, char_traits, string
|
||||
|
||||
@@ -11,6 +12,10 @@
|
||||
#include "core/resources/asset.h" // for Asset
|
||||
#include "core/resources/resource.h"
|
||||
|
||||
#ifndef NO_SHADERS
|
||||
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // for Rendering::SDL3GPUShader
|
||||
#endif
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
@@ -69,8 +74,23 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options
|
||||
// Define el color del borde para el modo de pantalla completa
|
||||
borderColor = {0x00, 0x00, 0x00};
|
||||
|
||||
// Crea la textura donde se dibujan los graficos del juego
|
||||
gameCanvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, gameCanvasWidth, gameCanvasHeight);
|
||||
// Establece el modo de video (fullscreen/ventana + logical presentation)
|
||||
// ANTES de crear la textura — SDL3 GPU necesita la logical presentation
|
||||
// del renderer ya aplicada al swapchain quan es reclama la ventana per a GPU.
|
||||
// Mirror del pattern de jaildoctors_dilemma (que usa exactament 256×192 i
|
||||
// funciona) on `initSDLVideo` configura la presentation abans de crear cap
|
||||
// textura.
|
||||
setVideoMode(options->videoMode != 0);
|
||||
|
||||
// Força al window manager a completar el resize/posicionat abans de passar
|
||||
// la ventana al dispositiu GPU. Sense açò en Linux/X11 hi ha un race
|
||||
// condition que deixa el swapchain en estat inestable i fa crashear el
|
||||
// driver Vulkan en `SDL_CreateGPUGraphicsPipeline`.
|
||||
SDL_SyncWindow(window);
|
||||
|
||||
// Crea la textura donde se dibujan los graficos del juego.
|
||||
// ARGB8888 per simplificar el readback cap al pipeline SDL3 GPU.
|
||||
gameCanvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gameCanvasWidth, gameCanvasHeight);
|
||||
if (gameCanvas != nullptr) {
|
||||
SDL_SetTextureScaleMode(gameCanvas, options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR);
|
||||
}
|
||||
@@ -80,11 +100,24 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el modo de video
|
||||
setVideoMode(options->videoMode != 0);
|
||||
#ifndef NO_SHADERS
|
||||
// Buffer de readback del gameCanvas (lo dimensionamos una vez)
|
||||
pixel_buffer_.resize(static_cast<size_t>(gameCanvasWidth) * static_cast<size_t>(gameCanvasHeight));
|
||||
#endif
|
||||
|
||||
// Inicializa el sistema de notificaciones (Text compartido de Resource)
|
||||
notificationText = Resource::get()->getText("8bithud");
|
||||
// Renderiza una vez la textura vacía al renderer abans d'inicialitzar els
|
||||
// shaders: jaildoctors_dilemma ho fa així i evita que el driver Vulkan
|
||||
// crashegi en la creació del pipeline gràfic. `initShaders()` es crida
|
||||
// després des de `Director` amb el swapchain ja estable.
|
||||
SDL_RenderTexture(renderer, gameCanvas, nullptr, nullptr);
|
||||
|
||||
// Estado inicial de las notificaciones. El Text real se enlaza después vía
|
||||
// `initNotifications()` quan `Resource` ja estigui inicialitzat. Dividim
|
||||
// això del constructor perquè `initShaders()` (GPU) ha de cridar-se ABANS
|
||||
// de carregar recursos: si el SDL_Renderer ha fet abans moltes
|
||||
// allocacions (carrega de textures), el driver Vulkan crasheja quan
|
||||
// després es reclama la ventana per al dispositiu GPU.
|
||||
notificationText = nullptr;
|
||||
notificationMessage = "";
|
||||
notificationTextColor = {0xFF, 0xFF, 0xFF};
|
||||
notificationOutlineColor = {0x00, 0x00, 0x00};
|
||||
@@ -95,9 +128,18 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options
|
||||
registerEmscriptenEventCallbacks();
|
||||
}
|
||||
|
||||
// Enllaça el Text de les notificacions amb el recurs compartit de `Resource`.
|
||||
// S'ha de cridar després de `Resource::init(...)`.
|
||||
void Screen::initNotifications() {
|
||||
notificationText = Resource::get()->getText("8bithud");
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Screen::~Screen() {
|
||||
// notificationText es propiedad de Resource — no liberar.
|
||||
#ifndef NO_SHADERS
|
||||
shutdownShaders();
|
||||
#endif
|
||||
SDL_DestroyTexture(gameCanvas);
|
||||
}
|
||||
|
||||
@@ -118,6 +160,51 @@ void Screen::blit() {
|
||||
SDL_SetRenderTarget(renderer, gameCanvas);
|
||||
renderNotification();
|
||||
|
||||
#ifndef NO_SHADERS
|
||||
// Si el backend GPU està viu i accelerat, passem sempre per ell (tant amb
|
||||
// shaders com sense). Seguim el mateix pattern que aee_plus: quan shader
|
||||
// està desactivat, forcem POSTFX + params a zero només per a aquest frame
|
||||
// i restaurem el shader actiu, així CRTPI no aplica les seues scanlines
|
||||
// quan l'usuari ho ha desactivat.
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
SDL_Surface *surface = SDL_RenderReadPixels(renderer, nullptr);
|
||||
if (surface != nullptr) {
|
||||
if (surface->format == SDL_PIXELFORMAT_ARGB8888) {
|
||||
std::memcpy(pixel_buffer_.data(), surface->pixels, pixel_buffer_.size() * sizeof(Uint32));
|
||||
} else {
|
||||
SDL_Surface *converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
|
||||
if (converted != nullptr) {
|
||||
std::memcpy(pixel_buffer_.data(), converted->pixels, pixel_buffer_.size() * sizeof(Uint32));
|
||||
SDL_DestroySurface(converted);
|
||||
}
|
||||
}
|
||||
SDL_DestroySurface(surface);
|
||||
}
|
||||
SDL_SetRenderTarget(renderer, nullptr);
|
||||
|
||||
if (options->videoShaderEnabled) {
|
||||
// Ruta normal: shader amb els seus params.
|
||||
shader_backend_->uploadPixels(pixel_buffer_.data(), gameCanvasWidth, gameCanvasHeight);
|
||||
shader_backend_->render();
|
||||
} else {
|
||||
// Shader off: POSTFX amb params zero (passa-per-aquí). CRTPI no
|
||||
// val perque sempre aplica els seus efectes interns; salvem i
|
||||
// restaurem el shader actiu.
|
||||
const auto PREV_SHADER = shader_backend_->getActiveShader();
|
||||
if (PREV_SHADER != Rendering::ShaderType::POSTFX) {
|
||||
shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||
}
|
||||
shader_backend_->setPostFXParams(Rendering::PostFXParams{});
|
||||
shader_backend_->uploadPixels(pixel_buffer_.data(), gameCanvasWidth, gameCanvasHeight);
|
||||
shader_backend_->render();
|
||||
if (PREV_SHADER != Rendering::ShaderType::POSTFX) {
|
||||
shader_backend_->setActiveShader(PREV_SHADER);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Vuelve a dejar el renderizador en modo normal
|
||||
SDL_SetRenderTarget(renderer, nullptr);
|
||||
|
||||
@@ -199,6 +286,11 @@ void Screen::toggleIntegerScale() {
|
||||
void Screen::setVSync(bool enabled) {
|
||||
options->vSync = enabled;
|
||||
SDL_SetRenderVSync(renderer, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
#ifndef NO_SHADERS
|
||||
if (shader_backend_) {
|
||||
shader_backend_->setVSync(enabled);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Alterna el V-Sync
|
||||
@@ -321,7 +413,7 @@ void Screen::clearNotification() {
|
||||
|
||||
// Dibuja la notificación activa (si la hay) sobre el gameCanvas
|
||||
void Screen::renderNotification() {
|
||||
if (SDL_GetTicks() >= notificationEndTime) {
|
||||
if (notificationText == nullptr || SDL_GetTicks() >= notificationEndTime) {
|
||||
return;
|
||||
}
|
||||
notificationText->writeDX(TXT_CENTER | TXT_COLOR | TXT_STROKE,
|
||||
@@ -367,3 +459,144 @@ void Screen::registerEmscriptenEventCallbacks() {
|
||||
emscripten_set_orientationchange_callback(nullptr, EM_TRUE, onEmOrientationChange);
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// GPU / shaders (SDL3 GPU post-procesado). En builds con NO_SHADERS (Emscripten)
|
||||
// las operaciones son no-op; la ruta clásica sigue siendo la única disponible.
|
||||
// ============================================================================
|
||||
|
||||
#ifndef NO_SHADERS
|
||||
// Aplica al backend el preset del shader actiu segons options.
|
||||
// Només s'ha de cridar quan `videoShaderEnabled=true` (en cas contrari el
|
||||
// blit() ja força POSTFX+zero params per a desactivar els efectes sense
|
||||
// tocar els paràmetres emmagatzemats).
|
||||
void Screen::applyShaderParams() {
|
||||
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) {
|
||||
return;
|
||||
}
|
||||
const Rendering::ShaderType ACTIVE = options->videoShaderType == 1
|
||||
? Rendering::ShaderType::CRTPI
|
||||
: Rendering::ShaderType::POSTFX;
|
||||
shader_backend_->setActiveShader(ACTIVE);
|
||||
|
||||
// Preset per defecte (carregador YAML pendent). Valors estil "CRT" de CCAE.
|
||||
Rendering::PostFXParams POSTFX;
|
||||
POSTFX.vignette = 0.15F;
|
||||
POSTFX.scanlines = 0.7F;
|
||||
POSTFX.chroma = 0.2F;
|
||||
shader_backend_->setPostFXParams(POSTFX);
|
||||
|
||||
// CrtPi: defaults del struct ja raonables (scanline_weight=6.0, bloom=3.5…).
|
||||
shader_backend_->setCrtPiParams(Rendering::CrtPiParams{});
|
||||
}
|
||||
#endif
|
||||
|
||||
void Screen::initShaders() {
|
||||
#ifndef NO_SHADERS
|
||||
if (!shader_backend_) {
|
||||
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||
const std::string FALLBACK_DRIVER = "none";
|
||||
shader_backend_->setPreferredDriver(
|
||||
options->videoGpuAcceleration ? options->videoGpuPreferredDriver : FALLBACK_DRIVER);
|
||||
}
|
||||
if (!shader_backend_->isHardwareAccelerated()) {
|
||||
const bool ok = shader_backend_->init(window, gameCanvas, "", "");
|
||||
if (options->console) {
|
||||
std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (ok ? "OK" : "FAILED") << '\n';
|
||||
}
|
||||
}
|
||||
if (shader_backend_->isHardwareAccelerated()) {
|
||||
shader_backend_->setScaleMode(options->integerScale);
|
||||
shader_backend_->setVSync(options->vSync);
|
||||
applyShaderParams(); // aplica preset del shader actiu
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Screen::shutdownShaders() {
|
||||
#ifndef NO_SHADERS
|
||||
// Només es crida des del destructor de Screen. Els toggles runtime NO la
|
||||
// poden cridar: destruir + recrear el dispositiu SDL3 GPU amb la ventana
|
||||
// ja reclamada és inestable (Vulkan/Radeon crasheja en el següent claim).
|
||||
if (shader_backend_) {
|
||||
shader_backend_->cleanup();
|
||||
shader_backend_.reset();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Screen::setGpuAcceleration(bool enabled) {
|
||||
if (options->videoGpuAcceleration == enabled) { return; }
|
||||
options->videoGpuAcceleration = enabled;
|
||||
// Soft toggle: el backend es manté viu (vegeu shutdownShaders). El canvi
|
||||
// s'aplica al proper arrencada. S'emet una notificació perquè l'usuari
|
||||
// sap que ha tocat la tecla però el canvi no és immediat.
|
||||
const color_t YELLOW = {0xFF, 0xFF, 0x00};
|
||||
const color_t BLACK = {0x00, 0x00, 0x00};
|
||||
const Uint32 DUR_MS = 2500;
|
||||
notify(enabled ? "GPU: ON (restart)" : "GPU: OFF (restart)", YELLOW, BLACK, DUR_MS);
|
||||
}
|
||||
|
||||
void Screen::toggleGpuAcceleration() {
|
||||
setGpuAcceleration(!options->videoGpuAcceleration);
|
||||
}
|
||||
|
||||
auto Screen::isGpuAccelerated() const -> bool {
|
||||
#ifndef NO_SHADERS
|
||||
return shader_backend_ && shader_backend_->isHardwareAccelerated();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Screen::setShaderEnabled(bool enabled) {
|
||||
if (options->videoShaderEnabled == enabled) { return; }
|
||||
options->videoShaderEnabled = enabled;
|
||||
#ifndef NO_SHADERS
|
||||
if (enabled) {
|
||||
applyShaderParams(); // restaura preset del shader actiu
|
||||
}
|
||||
// Si enabled=false, blit() forçarà POSTFX+zero per frame — no cal tocar
|
||||
// res ara.
|
||||
#endif
|
||||
const color_t CYAN = {0x00, 0xFF, 0xFF};
|
||||
const color_t BLACK = {0x00, 0x00, 0x00};
|
||||
const Uint32 DUR_MS = 1500;
|
||||
notify(enabled ? "Shader: ON" : "Shader: OFF", CYAN, BLACK, DUR_MS);
|
||||
}
|
||||
|
||||
void Screen::toggleShaderEnabled() {
|
||||
setShaderEnabled(!options->videoShaderEnabled);
|
||||
}
|
||||
|
||||
auto Screen::isShaderEnabled() const -> bool {
|
||||
return options->videoShaderEnabled;
|
||||
}
|
||||
|
||||
#ifndef NO_SHADERS
|
||||
void Screen::setActiveShader(Rendering::ShaderType type) {
|
||||
options->videoShaderType = type == Rendering::ShaderType::CRTPI ? 1 : 0;
|
||||
if (options->videoShaderEnabled) {
|
||||
applyShaderParams();
|
||||
}
|
||||
const color_t MAGENTA = {0xFF, 0x00, 0xFF};
|
||||
const color_t BLACK = {0x00, 0x00, 0x00};
|
||||
const Uint32 DUR_MS = 1500;
|
||||
notify(type == Rendering::ShaderType::CRTPI ? "Shader: CRTPI" : "Shader: POSTFX", MAGENTA, BLACK, DUR_MS);
|
||||
}
|
||||
|
||||
auto Screen::getActiveShader() const -> Rendering::ShaderType {
|
||||
return options->videoShaderType == 1 ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
|
||||
}
|
||||
#endif
|
||||
|
||||
void Screen::toggleActiveShader() {
|
||||
#ifndef NO_SHADERS
|
||||
const Rendering::ShaderType NEXT = getActiveShader() == Rendering::ShaderType::POSTFX
|
||||
? Rendering::ShaderType::CRTPI
|
||||
: Rendering::ShaderType::POSTFX;
|
||||
setActiveShader(NEXT);
|
||||
#else
|
||||
options->videoShaderType = options->videoShaderType == 1 ? 0 : 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -2,9 +2,19 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // for unique_ptr
|
||||
#include <string> // for string
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "utils/utils.h" // for color_t
|
||||
|
||||
#ifndef NO_SHADERS
|
||||
#include "core/rendering/shader_backend.hpp" // for Rendering::ShaderType
|
||||
namespace Rendering {
|
||||
class ShaderBackend;
|
||||
}
|
||||
#endif
|
||||
|
||||
class Asset;
|
||||
class Text;
|
||||
|
||||
@@ -50,9 +60,25 @@ class Screen {
|
||||
void setBorderColor(color_t color); // Cambia el color del borde
|
||||
|
||||
// Notificaciones
|
||||
void initNotifications(); // Enllaça el Text de notificacions amb `Resource`. A cridar després de `Resource::init(...)`.
|
||||
void notify(const std::string &text, color_t textColor, color_t outlineColor, Uint32 durationMs); // Muestra una notificación en la línea superior del canvas durante durationMs. Sobrescribe cualquier notificación activa (sin apilación).
|
||||
void clearNotification(); // Limpia la notificación actual
|
||||
|
||||
// GPU / shaders (post-procesado). En builds con NO_SHADERS (Emscripten) son no-op.
|
||||
void initShaders(); // Crea el backend GPU si no existe y lo inicializa
|
||||
void shutdownShaders(); // Libera el backend GPU
|
||||
void setGpuAcceleration(bool enabled); // Crea/destruye el backend según valor, persiste options
|
||||
void toggleGpuAcceleration(); // Alterna aceleración GPU
|
||||
auto isGpuAccelerated() const -> bool; // true si el backend existe y reporta hardware OK
|
||||
void setShaderEnabled(bool enabled); // Activa o desactiva el post-procesado (persiste)
|
||||
void toggleShaderEnabled(); // Alterna post-procesado
|
||||
auto isShaderEnabled() const -> bool; // Estado actual (lee options)
|
||||
#ifndef NO_SHADERS
|
||||
void setActiveShader(Rendering::ShaderType type); // POSTFX o CRTPI
|
||||
auto getActiveShader() const -> Rendering::ShaderType;
|
||||
#endif
|
||||
void toggleActiveShader(); // Alterna POSTFX ↔ CRTPI
|
||||
|
||||
private:
|
||||
// Helpers internos de setVideoMode
|
||||
void applyFullscreen(bool fullscreen); // SDL_SetWindowFullscreen + visibilidad del cursor
|
||||
@@ -67,6 +93,12 @@ class Screen {
|
||||
// Notificaciones
|
||||
void renderNotification(); // Dibuja la notificación activa (si la hay) sobre el gameCanvas
|
||||
|
||||
#ifndef NO_SHADERS
|
||||
// Aplica els paràmetres actuals del shader al backend segons options
|
||||
// (pass-through si `videoShaderEnabled==false`, preset per defecte si true).
|
||||
void applyShaderParams();
|
||||
#endif
|
||||
|
||||
// Objetos y punteros
|
||||
SDL_Window *window; // Ventana de la aplicación
|
||||
SDL_Renderer *renderer; // El renderizador de la ventana
|
||||
@@ -91,4 +123,10 @@ class Screen {
|
||||
color_t notificationOutlineColor; // Color del outline
|
||||
Uint32 notificationEndTime; // SDL_GetTicks() hasta el cual se muestra
|
||||
int notificationY; // Fila vertical en el canvas virtual
|
||||
|
||||
#ifndef NO_SHADERS
|
||||
// GPU / shaders
|
||||
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend GPU (nullptr si inactivo)
|
||||
std::vector<Uint32> pixel_buffer_; // Buffer de readback del gameCanvas (ARGB8888)
|
||||
#endif
|
||||
};
|
||||
|
||||
1329
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
1329
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
File diff suppressed because it is too large
Load Diff
178
source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
178
source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
@@ -0,0 +1,178 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
|
||||
#include "core/rendering/shader_backend.hpp"
|
||||
|
||||
// PostFX uniforms pushed to fragment stage each frame.
|
||||
// Must match the MSL struct and GLSL uniform block layout.
|
||||
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength; // 0 = none, ~0.8 = subtle
|
||||
float chroma_strength; // 0 = off, ~0.2 = subtle chromatic aberration
|
||||
float scanline_strength; // 0 = off, 1 = full
|
||||
float screen_height; // logical height in pixels (used by bleeding effect)
|
||||
float mask_strength; // 0 = off, 1 = full phosphor dot mask
|
||||
float gamma_strength; // 0 = off, 1 = full gamma 2.4/2.2 correction
|
||||
float curvature; // 0 = flat, 1 = max barrel distortion
|
||||
float bleeding; // 0 = off, 1 = max NTSC chrominance bleeding
|
||||
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
||||
float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f)
|
||||
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
|
||||
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — keep struct at 48 bytes (3 × 16)
|
||||
};
|
||||
|
||||
// CrtPi uniforms pushed to fragment stage each frame.
|
||||
// Must match the MSL struct and GLSL uniform block layout.
|
||||
// 14 fields (8 floats + 6 ints) + 2 floats (texture size) = 16 fields = 64 bytes — 4 × 16-byte alignment.
|
||||
struct CrtPiUniforms {
|
||||
// vec4 #0
|
||||
float scanline_weight; // Ajuste gaussiano (default 6.0)
|
||||
float scanline_gap_brightness; // Brillo mínimo entre scanlines (default 0.12)
|
||||
float bloom_factor; // Factor brillo zonas iluminadas (default 3.5)
|
||||
float input_gamma; // Gamma de entrada (default 2.4)
|
||||
// vec4 #1
|
||||
float output_gamma; // Gamma de salida (default 2.2)
|
||||
float mask_brightness; // Brillo sub-píxeles máscara (default 0.80)
|
||||
float curvature_x; // Distorsión barrel X (default 0.05)
|
||||
float curvature_y; // Distorsión barrel Y (default 0.10)
|
||||
// vec4 #2
|
||||
int mask_type; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||
int enable_scanlines; // 0 = off, 1 = on
|
||||
int enable_multisample; // 0 = off, 1 = on (antialiasing analítico)
|
||||
int enable_gamma; // 0 = off, 1 = on
|
||||
// vec4 #3
|
||||
int enable_curvature; // 0 = off, 1 = on
|
||||
int enable_sharper; // 0 = off, 1 = on
|
||||
float texture_width; // Ancho del canvas en píxeles (inyectado en render)
|
||||
float texture_height; // Alto del canvas en píxeles (inyectado en render)
|
||||
};
|
||||
|
||||
// Downscale uniforms pushed to the Lanczos downscale fragment stage.
|
||||
// 1 int + 3 floats = 16 bytes — meets Metal/Vulkan alignment.
|
||||
struct DownscaleUniforms {
|
||||
int algorithm; // 0 = Lanczos2 (ventana 2), 1 = Lanczos3 (ventana 3)
|
||||
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)
|
||||
*
|
||||
* Reemplaza el backend OpenGL para que los shaders PostFX funcionen en macOS.
|
||||
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
|
||||
* → PostFX render pass → swapchain → present
|
||||
*/
|
||||
class SDL3GPUShader : public ShaderBackend {
|
||||
public:
|
||||
SDL3GPUShader() = default;
|
||||
~SDL3GPUShader() override;
|
||||
|
||||
auto init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& vertex_source,
|
||||
const std::string& fragment_source) -> bool override;
|
||||
|
||||
void render() override;
|
||||
void setTextureSize(float width, float height) override {}
|
||||
void cleanup() final; // Libera pipeline/texturas pero mantiene el device vivo
|
||||
void destroy(); // Limpieza completa (device + swapchain); llamar solo al cerrar
|
||||
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
|
||||
[[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }
|
||||
|
||||
// Establece el driver GPU preferido (vacío = auto). Debe llamarse antes de init().
|
||||
void setPreferredDriver(const std::string& driver) override { preferred_driver_ = driver; }
|
||||
|
||||
// Sube píxeles ARGB8888 desde CPU; llamado antes de render()
|
||||
void uploadPixels(const Uint32* pixels, int width, int height) override;
|
||||
|
||||
// Actualiza los parámetros de intensidad de los efectos PostFX
|
||||
void setPostFXParams(const PostFXParams& p) override;
|
||||
|
||||
// Activa/desactiva VSync en el swapchain
|
||||
void setVSync(bool vsync) override;
|
||||
|
||||
// Activa/desactiva escalado entero (integer scale)
|
||||
void setScaleMode(bool integer_scale) override;
|
||||
|
||||
// Establece factor de supersampling (1 = off, 3 = 3×SS)
|
||||
void setOversample(int factor) override;
|
||||
|
||||
// Activa/desactiva interpolación LINEAR en el upscale (false = NEAREST)
|
||||
void setLinearUpscale(bool linear) override;
|
||||
|
||||
// Selecciona algoritmo de downscale: 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3
|
||||
void setDownscaleAlgo(int algo) override;
|
||||
|
||||
// Devuelve las dimensiones de la textura de supersampling (0,0 si SS desactivado)
|
||||
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;
|
||||
|
||||
// Selecciona el shader de post-procesado activo (POSTFX o CRTPI)
|
||||
void setActiveShader(ShaderType type) override;
|
||||
|
||||
// Actualiza los parámetros del shader CRT-Pi
|
||||
void setCrtPiParams(const CrtPiParams& p) override;
|
||||
|
||||
// Devuelve el shader activo
|
||||
[[nodiscard]] auto getActiveShader() const -> ShaderType override { return active_shader_; }
|
||||
|
||||
private:
|
||||
static auto createShaderMSL(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||
|
||||
static auto createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||
|
||||
auto createPipeline() -> bool;
|
||||
auto createCrtPiPipeline() -> bool; // Pipeline dedicado para el shader CrtPi
|
||||
auto reinitTexturesAndBuffer() -> bool; // Recrea scene_texture_ y upload_buffer_
|
||||
auto recreateScaledTexture(int factor) -> bool; // Recrea scaled_texture_ para factor dado
|
||||
static auto calcSsFactor(float zoom) -> int; // Primer múltiplo de 3 >= zoom (mín 3)
|
||||
// Devuelve el mejor present mode disponible: IMMEDIATE > MAILBOX > VSYNC
|
||||
[[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;
|
||||
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_GPUDevice* device_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* pipeline_ = nullptr; // PostFX pass (→ swapchain o → postfx_texture_)
|
||||
SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr; // CrtPi pass (→ swapchain directo, sin SS)
|
||||
SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr; // PostFX → postfx_texture_ (B8G8R8A8, solo con Lanczos)
|
||||
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr; // Upscale pass (solo con SS)
|
||||
SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr; // Lanczos downscale (solo con SS + algo > 0)
|
||||
SDL_GPUTexture* scene_texture_ = nullptr; // Canvas del juego (game_width_ × game_height_)
|
||||
SDL_GPUTexture* scaled_texture_ = nullptr; // Upscale target (game×factor), solo con SS
|
||||
SDL_GPUTexture* postfx_texture_ = nullptr; // PostFX output a resolución escalada, solo con Lanczos
|
||||
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
||||
SDL_GPUSampler* sampler_ = nullptr; // NEAREST
|
||||
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR
|
||||
|
||||
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};
|
||||
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; // Shader de post-procesado activo
|
||||
|
||||
int game_width_ = 0; // Dimensiones originales del canvas
|
||||
int game_height_ = 0;
|
||||
int ss_factor_ = 0; // Factor SS activo (3, 6, 9...) o 0 si SS desactivado
|
||||
int oversample_ = 1; // SS on/off (1 = off, >1 = on)
|
||||
int downscale_algo_ = 1; // 0 = bilinear legacy, 1 = Lanczos2, 2 = Lanczos3
|
||||
std::string driver_name_;
|
||||
std::string preferred_driver_; // Driver preferido; vacío = auto (SDL elige)
|
||||
bool is_initialized_ = false;
|
||||
bool vsync_ = true;
|
||||
bool integer_scale_ = false;
|
||||
bool linear_upscale_ = false; // Upscale NEAREST (false) o LINEAR (true)
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
2
source/core/rendering/sdl3gpu/spv/.clang-format
Normal file
2
source/core/rendering/sdl3gpu/spv/.clang-format
Normal file
@@ -0,0 +1,2 @@
|
||||
DisableFormat: true
|
||||
SortIncludes: Never
|
||||
4
source/core/rendering/sdl3gpu/spv/.clang-tidy
Normal file
4
source/core/rendering/sdl3gpu/spv/.clang-tidy
Normal file
@@ -0,0 +1,4 @@
|
||||
# source/core/rendering/sdl3gpu/spv/.clang-tidy
|
||||
Checks: '-*'
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: ''
|
||||
10362
source/core/rendering/sdl3gpu/spv/crtpi_frag_spv.h
Normal file
10362
source/core/rendering/sdl3gpu/spv/crtpi_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
4253
source/core/rendering/sdl3gpu/spv/downscale_frag_spv.h
Normal file
4253
source/core/rendering/sdl3gpu/spv/downscale_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
11717
source/core/rendering/sdl3gpu/spv/postfx_frag_spv.h
Normal file
11717
source/core/rendering/sdl3gpu/spv/postfx_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
1449
source/core/rendering/sdl3gpu/spv/postfx_vert_spv.h
Normal file
1449
source/core/rendering/sdl3gpu/spv/postfx_vert_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
633
source/core/rendering/sdl3gpu/spv/upscale_frag_spv.h
Normal file
633
source/core/rendering/sdl3gpu/spv/upscale_frag_spv.h
Normal file
@@ -0,0 +1,633 @@
|
||||
#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;
|
||||
175
source/core/rendering/shader_backend.hpp
Normal file
175
source/core/rendering/shader_backend.hpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/** @brief Identificador del shader de post-procesado activo */
|
||||
enum class ShaderType { POSTFX,
|
||||
CRTPI };
|
||||
|
||||
/**
|
||||
* @brief Parámetros de intensidad de los efectos PostFX
|
||||
* Definido a nivel de namespace para facilitar el uso desde subclases y screen.cpp
|
||||
*/
|
||||
struct PostFXParams {
|
||||
float vignette = 0.0F; // Intensidad de la viñeta
|
||||
float scanlines = 0.0F; // Intensidad de las scanlines
|
||||
float chroma = 0.0F; // Aberración cromática
|
||||
float mask = 0.0F; // Máscara de fósforo RGB
|
||||
float gamma = 0.0F; // Corrección gamma (blend 0=off, 1=full)
|
||||
float curvature = 0.0F; // Curvatura barrel CRT
|
||||
float bleeding = 0.0F; // Sangrado de color NTSC
|
||||
float flicker = 0.0F; // Parpadeo de fósforo CRT ~50 Hz
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Parámetros del shader CRT-Pi (algoritmo de scanlines continuas)
|
||||
* Diferente al PostFX: usa pesos gaussianos por distancia subpixel y bloom.
|
||||
*/
|
||||
struct CrtPiParams {
|
||||
float scanline_weight{6.0F}; // Ajuste gaussiano (mayor = scanlines más estrechas)
|
||||
float scanline_gap_brightness{0.12F}; // Brillo mínimo en las ranuras entre scanlines
|
||||
float bloom_factor{3.5F}; // Factor de brillo para zonas iluminadas
|
||||
float input_gamma{2.4F}; // Gamma de entrada (linealización)
|
||||
float output_gamma{2.2F}; // Gamma de salida (codificación)
|
||||
float mask_brightness{0.80F}; // Sub-píxeles tenues en la máscara de fósforo
|
||||
float curvature_x{0.05F}; // Distorsión barrel eje X
|
||||
float curvature_y{0.10F}; // Distorsión barrel eje Y
|
||||
int mask_type{2}; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||
bool enable_scanlines{true}; // Activar efecto de scanlines
|
||||
bool enable_multisample{true}; // Antialiasing analítico de scanlines
|
||||
bool enable_gamma{true}; // Corrección gamma
|
||||
bool enable_curvature{false}; // Distorsión barrel CRT
|
||||
bool enable_sharper{false}; // Submuestreo más nítido (modo SHARPER)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Interfaz abstracta para backends de renderizado con shaders
|
||||
*
|
||||
* Esta interfaz define el contrato que todos los backends de shaders
|
||||
* deben cumplir (OpenGL, Metal, Vulkan, etc.)
|
||||
*/
|
||||
class ShaderBackend {
|
||||
public:
|
||||
virtual ~ShaderBackend() = default;
|
||||
|
||||
/**
|
||||
* @brief Inicializa el backend de shaders
|
||||
* @param window Ventana SDL
|
||||
* @param texture Textura de backbuffer a la que aplicar shaders
|
||||
* @param vertex_source Código fuente del vertex shader
|
||||
* @param fragment_source Código fuente del fragment shader
|
||||
* @return true si la inicialización fue exitosa
|
||||
*/
|
||||
virtual auto init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& vertex_source,
|
||||
const std::string& fragment_source) -> bool = 0;
|
||||
|
||||
/**
|
||||
* @brief Renderiza la textura con los shaders aplicados
|
||||
*/
|
||||
virtual void render() = 0;
|
||||
|
||||
/**
|
||||
* @brief Establece el tamaño de la textura como parámetro del shader
|
||||
* @param width Ancho de la textura
|
||||
* @param height Alto de la textura
|
||||
*/
|
||||
virtual void setTextureSize(float width, float height) = 0;
|
||||
|
||||
/**
|
||||
* @brief Limpia y libera recursos del backend
|
||||
*/
|
||||
virtual void cleanup() = 0;
|
||||
|
||||
/**
|
||||
* @brief Sube píxeles ARGB8888 desde la CPU al backend de shaders
|
||||
* Usado por SDL3GPUShader para evitar pasar por SDL_Texture
|
||||
*/
|
||||
virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {}
|
||||
|
||||
/**
|
||||
* @brief Establece los parámetros de intensidad de los efectos PostFX
|
||||
* @param p Struct con todos los parámetros PostFX
|
||||
*/
|
||||
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
|
||||
|
||||
/**
|
||||
* @brief Activa o desactiva VSync en el swapchain del GPU device
|
||||
*/
|
||||
virtual void setVSync(bool /*vsync*/) {}
|
||||
|
||||
/**
|
||||
* @brief Activa o desactiva el escalado entero (integer scale)
|
||||
*/
|
||||
virtual void setScaleMode(bool /*integer_scale*/) {}
|
||||
|
||||
/**
|
||||
* @brief Establece el factor de supersampling (1 = off, 3 = 3× SS)
|
||||
* Con factor > 1, la textura GPU se crea a game×factor resolución y
|
||||
* las scanlines se hornean en CPU (uploadPixels). El sampler usa LINEAR.
|
||||
*/
|
||||
virtual void setOversample(int /*factor*/) {}
|
||||
|
||||
/**
|
||||
* @brief Activa/desactiva interpolación LINEAR en el paso de upscale (SS).
|
||||
* Por defecto NEAREST (false). Solo tiene efecto con supersampling activo.
|
||||
*/
|
||||
virtual void setLinearUpscale(bool /*linear*/) {}
|
||||
[[nodiscard]] virtual auto isLinearUpscale() const -> bool { return false; }
|
||||
|
||||
/**
|
||||
* @brief Selecciona el algoritmo de downscale tras el PostFX (SS activo).
|
||||
* 0 = bilinear legacy (comportamiento actual, sin textura intermedia),
|
||||
* 1 = Lanczos2 (ventana 2, ~25 muestras), 2 = Lanczos3 (ventana 3, ~49 muestras).
|
||||
*/
|
||||
virtual void setDownscaleAlgo(int /*algo*/) {}
|
||||
[[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; }
|
||||
|
||||
/**
|
||||
* @brief Devuelve las dimensiones de la textura de supersampling.
|
||||
* @return Par (ancho, alto) en píxeles; (0, 0) si SS está desactivado.
|
||||
*/
|
||||
[[nodiscard]] virtual auto getSsTextureSize() const -> std::pair<int, int> { return {0, 0}; }
|
||||
|
||||
/**
|
||||
* @brief Verifica si el backend está usando aceleración por hardware
|
||||
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
|
||||
*/
|
||||
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
|
||||
|
||||
/**
|
||||
* @brief Nombre del driver GPU activo (p.ej. "vulkan", "metal", "direct3d12")
|
||||
* @return Cadena vacía si no disponible
|
||||
*/
|
||||
[[nodiscard]] virtual auto getDriverName() const -> std::string { return {}; }
|
||||
|
||||
/**
|
||||
* @brief Establece el driver GPU preferido antes de init().
|
||||
* Vacío = selección automática de SDL. Implementado en SDL3GPUShader.
|
||||
*/
|
||||
virtual void setPreferredDriver(const std::string& /*driver*/) {}
|
||||
|
||||
/**
|
||||
* @brief Selecciona el shader de post-procesado activo (POSTFX o CRTPI).
|
||||
* Debe llamarse antes de render(). No recrea pipelines.
|
||||
*/
|
||||
virtual void setActiveShader(ShaderType /*type*/) {}
|
||||
|
||||
/**
|
||||
* @brief Establece los parámetros del shader CRT-Pi.
|
||||
*/
|
||||
virtual void setCrtPiParams(const CrtPiParams& /*p*/) {}
|
||||
|
||||
/**
|
||||
* @brief Devuelve el shader de post-procesado activo.
|
||||
*/
|
||||
[[nodiscard]] virtual auto getActiveShader() const -> ShaderType { return ShaderType::POSTFX; }
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
@@ -114,12 +114,29 @@ Director::Director(int argc, const char *argv[]) {
|
||||
#endif
|
||||
initInput();
|
||||
|
||||
// Precarga todos los recursos en memoria (texturas, sonidos, música, ...).
|
||||
// Vivirán durante toda la vida de la app; las escenas leen handles compartidos.
|
||||
// Debe ir antes de crear Screen porque Screen usa Text (propiedad de Resource).
|
||||
// Orden importante: Screen + initShaders ANTES de Resource::init.
|
||||
// Si `Resource::init` se ejecuta primero, carga ~100 texturas vía
|
||||
// `SDL_CreateTexture` que dejan el SDL_Renderer con el swapchain en un
|
||||
// estado que hace crashear al driver Vulkan cuando después `initShaders`
|
||||
// intenta reclamar la ventana para el dispositivo SDL3 GPU.
|
||||
//
|
||||
// Por eso el constructor de Screen NO carga notificationText desde
|
||||
// Resource; se enlaza después vía `screen->initNotifications()`.
|
||||
screen = new Screen(window, renderer, asset, options);
|
||||
|
||||
#ifndef NO_SHADERS
|
||||
if (options->videoGpuAcceleration) {
|
||||
screen->initShaders();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Ahora sí, precarga todos los recursos en memoria (texturas, sonidos,
|
||||
// música, ...). Vivirán durante toda la vida de la app.
|
||||
Resource::init(renderer, asset, input);
|
||||
|
||||
screen = new Screen(window, renderer, asset, options);
|
||||
// Completa el enlazado de Screen con recursos que necesitan Resource
|
||||
// inicializado (actualmente sólo el Text de las notificaciones).
|
||||
screen->initNotifications();
|
||||
|
||||
activeSection = ActiveSection::None;
|
||||
}
|
||||
@@ -183,6 +200,9 @@ void Director::initInput() {
|
||||
input->bindKey(input_window_dec_size, SDL_SCANCODE_F1);
|
||||
input->bindKey(input_window_inc_size, SDL_SCANCODE_F2);
|
||||
input->bindKey(input_window_fullscreen, SDL_SCANCODE_F3);
|
||||
input->bindKey(input_toggle_gpu, SDL_SCANCODE_F9);
|
||||
input->bindKey(input_toggle_shader, SDL_SCANCODE_F10);
|
||||
input->bindKey(input_toggle_shader_type, SDL_SCANCODE_F11);
|
||||
|
||||
// Mando - Movimiento del jugador
|
||||
input->bindGameControllerButton(input_up, SDL_GAMEPAD_BUTTON_DPAD_UP);
|
||||
@@ -250,6 +270,10 @@ bool Director::initSDL() {
|
||||
}
|
||||
success = false;
|
||||
} else {
|
||||
// Modo de blending por defecto (consistente con CCAE):
|
||||
// permite alpha blending para fades y notificaciones.
|
||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Activa vsync si es necesario
|
||||
if (options->vSync) {
|
||||
SDL_SetRenderVSync(renderer, 1);
|
||||
@@ -626,6 +650,12 @@ bool Director::saveConfigFile() {
|
||||
file << "borderWidth=" + std::to_string(options->borderWidth) + "\n";
|
||||
file << "borderHeight=" + std::to_string(options->borderHeight) + "\n";
|
||||
|
||||
// Opciones de GPU / shaders (post-procesado)
|
||||
file << "videoGpuAcceleration=" + boolToString(options->videoGpuAcceleration) + "\n";
|
||||
file << "videoGpuPreferredDriver=" + options->videoGpuPreferredDriver + "\n";
|
||||
file << "videoShaderEnabled=" + boolToString(options->videoShaderEnabled) + "\n";
|
||||
file << "videoShaderType=" + std::to_string(options->videoShaderType) + "\n";
|
||||
|
||||
// Otras opciones del programa
|
||||
file << "\n## OTHER OPTIONS\n";
|
||||
file << "language=" + std::to_string(options->language) + "\n";
|
||||
@@ -835,6 +865,26 @@ bool Director::setOptions(options_t *options, std::string var, std::string value
|
||||
options->borderHeight = std::stoi(value);
|
||||
}
|
||||
|
||||
// Opciones de GPU / shaders
|
||||
else if (var == "videoGpuAcceleration") {
|
||||
options->videoGpuAcceleration = stringToBool(value);
|
||||
}
|
||||
|
||||
else if (var == "videoGpuPreferredDriver") {
|
||||
options->videoGpuPreferredDriver = value;
|
||||
}
|
||||
|
||||
else if (var == "videoShaderEnabled") {
|
||||
options->videoShaderEnabled = stringToBool(value);
|
||||
}
|
||||
|
||||
else if (var == "videoShaderType") {
|
||||
options->videoShaderType = std::stoi(value);
|
||||
if (options->videoShaderType < 0 || options->videoShaderType > 1) {
|
||||
options->videoShaderType = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Opciones varias
|
||||
else if (var == "language") {
|
||||
options->language = std::stoi(value);
|
||||
|
||||
Reference in New Issue
Block a user