#pragma once #include #include #include "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) }; 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_; } // 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; 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 reinitTexturesAndBuffer() -> bool; // Recrea textura y buffer con oversample actual SDL_Window* window_ = nullptr; SDL_GPUDevice* device_ = nullptr; SDL_GPUGraphicsPipeline* pipeline_ = nullptr; SDL_GPUTexture* scene_texture_ = nullptr; SDL_GPUTransferBuffer* upload_buffer_ = nullptr; SDL_GPUSampler* sampler_ = nullptr; // NEAREST — para path sin supersampling SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR — para path con supersampling 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}; int game_width_ = 0; // Dimensiones originales del canvas (sin SS) int game_height_ = 0; int tex_width_ = 0; // Dimensiones de la textura GPU (game × oversample_) int tex_height_ = 0; int oversample_ = 1; // Factor SS actual (1 o 3) float baked_scanline_strength_ = 0.0F; // Guardado para hornear en CPU bool is_initialized_ = false; bool vsync_ = true; bool integer_scale_ = false; }; } // namespace Rendering