184 lines
9.5 KiB
C++
184 lines
9.5 KiB
C++
#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_; }
|
||
|
||
// Estirament vertical 4:3 (320x200 → 320x240 visual al viewport)
|
||
void setStretch4_3(bool enabled) override { stretch_4_3_ = enabled; }
|
||
[[nodiscard]] auto isStretch4_3() const -> bool override { return stretch_4_3_; }
|
||
|
||
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 = 200.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)
|
||
bool stretch_4_3_ = false; // Estirament vertical 4:3 al viewport
|
||
};
|
||
|
||
} // namespace Rendering
|