#pragma once #include #include #include #include #include "rendering/shader_backend.hpp" // PostFX uniforms pushed to fragment stage each frame. // 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement. 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; }; // CrtPi uniforms pushed to fragment stage each frame. // 16 fields = 64 bytes — 4 × 16-byte alignment. 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; }; // 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 */ 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; void destroy(); [[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; } [[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; } void setPreferredDriver(const std::string& driver) override { preferred_driver_ = driver; } void uploadPixels(const Uint32* pixels, int width, int height) override; 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 override; void setActiveShader(ShaderType type) override; void setCrtPiParams(const CrtPiParams& p) override; [[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; 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}; 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