#pragma once #include #include #include "core/rendering/shader_backend.hpp" // PostFX uniforms pushed to fragment stage each frame. // Must match the MSL struct and GLSL uniform block layout. // 16 floats = 64 bytes (4 × vec4) — meets Metal/Vulkan 16-byte alignment. struct PostFXUniforms { // vec4 #0 float vignette_strength; // 0 = none, ~0.8 = subtle float chroma_min; // aberració cromàtica mínima (sempre present) float scanline_strength; // 0 = off, 1 = full float screen_height; // logical height in pixels (used by bleeding effect) // vec4 #1 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 // vec4 #2 float pixel_scale; // physical pixels per logical pixel (vh / tex_height_) float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f) float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz float chroma_max; // si == chroma_min queda estàtic; si != pulsa sinusoidalment // vec4 #3 — paràmetres de forma de les scanlines (exposats per preset) float scan_dark_ratio; // fracció de subfila fosca (1/3 = 0.333 per defecte) float scan_dark_floor; // brillantor de la subfila fosca (0.42 per defecte) float scan_edge_soft; // suavitzat de la transició (0 = step dur, 1 = 1px físic) float pad3; }; // (Downscale removed — el shader PostFX nou filtra scanlines analíticament i no necessita Lanczos.) // 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) }; 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; // Estableix el mode de presentacio del canvas void setPresentationMode(PresentationMode mode) 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 createPostfxVertexShader() -> SDL_GPUShader*; // Vertex shader fullscreen-triangle compartido (MSL/SPIRV) // Empaqueta el patrón vert(postfx) + frag dado + target format en un pipeline gráfico. // Toma ownership de `frag`: lo libera tras crear el pipeline (o si vert falla). auto createPostfxLikePipeline(SDL_GPUShader* frag, SDL_GPUTextureFormat format, const char* debug_name) -> SDL_GPUGraphicsPipeline*; // Sub-passos de render() (extrets per reduir complexitat ciclomàtica) struct Viewport { float x, y, w, h; }; void uploadSceneTexture(SDL_GPUCommandBuffer* cmd); [[nodiscard]] auto computeViewport(Uint32 sw, Uint32 sh) const -> Viewport; void updateDynamicUniforms(float viewport_h); void runCrtPiPass(SDL_GPUCommandBuffer* cmd, SDL_GPUTexture* swapchain, const Viewport& vp); void runDirectPostfxPass(SDL_GPUCommandBuffer* cmd, SDL_GPUTexture* swapchain, const Viewport& vp); auto reinitTexturesAndBuffer() -> bool; // Recrea scene_texture_ y upload_buffer_ // 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 SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr; // CrtPi pass → swapchain SDL_GPUTexture* scene_texture_ = nullptr; // Canvas del juego (game_width_ × game_height_) SDL_GPUTransferBuffer* upload_buffer_ = nullptr; SDL_GPUSampler* sampler_ = nullptr; // NEAREST PostFXUniforms uniforms_{ .vignette_strength = 0.6F, .chroma_min = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .mask_strength = 0.0F, .gamma_strength = 0.0F, .curvature = 0.0F, .bleeding = 0.0F, .pixel_scale = 1.0F, .time = 0.0F, .flicker = 0.0F, .chroma_max = 0.15F, .scan_dark_ratio = 0.333F, .scan_dark_floor = 0.42F, .scan_edge_soft = 1.0F, .pad3 = 0.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, .enable_curvature = 0, .enable_sharper = 0, .texture_width = 0.0F, .texture_height = 0.0F}; ShaderType active_shader_ = ShaderType::POSTFX; // Shader de post-procesado activo int game_width_ = 0; // Dimensiones originales del canvas int game_height_ = 0; std::string driver_name_; std::string preferred_driver_; // Driver preferido; vacío = auto (SDL elige) bool is_initialized_ = false; bool vsync_ = true; PresentationMode presentation_mode_ = PresentationMode::INTEGER_SCALE; }; } // namespace Rendering