#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp"

#include <SDL3/SDL_log.h>

#include <algorithm>  // std::min, std::max, std::floor
#include <cmath>      // std::floor
#include <cstring>    // memcpy, strlen
#include <iostream>   // std::cout

#ifndef __APPLE__
#include "core/rendering/sdl3gpu/spv/crtpi_frag_spv.h"
#include "core/rendering/sdl3gpu/spv/postfx_frag_spv.h"
#include "core/rendering/sdl3gpu/spv/postfx_vert_spv.h"
#endif

#ifdef __APPLE__
#include "core/rendering/sdl3gpu/msl/crtpi_frag.msl.h"
#include "core/rendering/sdl3gpu/msl/postfx_frag.msl.h"
#include "core/rendering/sdl3gpu/msl/postfx_vert.msl.h"
#endif

namespace Rendering {

    // ---------------------------------------------------------------------------
    // Destructor
    // ---------------------------------------------------------------------------
    SDL3GPUShader::~SDL3GPUShader() {
        destroy();
    }

    // ---------------------------------------------------------------------------
    // init
    // ---------------------------------------------------------------------------
    auto SDL3GPUShader::init(SDL_Window* window,
        SDL_Texture* texture,
        const std::string& /*vertex_source*/,
        const std::string& /*fragment_source*/) -> bool {
        // Si ya estaba inicializado (p.ej. al cambiar borde), liberar recursos
        // de textura/pipeline pero mantener el device vivo para evitar conflictos
        // con SDL_Renderer en Windows/Vulkan.
        if (is_initialized_) {
            cleanup();
        }

        window_ = window;

        // Dimensions from the SDL_Texture placeholder
        float fw = 0.0F;
        float fh = 0.0F;
        SDL_GetTextureSize(texture, &fw, &fh);
        game_width_ = static_cast<int>(fw);
        game_height_ = static_cast<int>(fh);
        uniforms_.screen_height = static_cast<float>(game_height_);

        // ----------------------------------------------------------------
        // 1. Create GPU device (solo si no existe ya)
        // ----------------------------------------------------------------
        if (preferred_driver_ == "none") {
            SDL_Log("SDL3GPUShader: GPU disabled by config, using SDL_Renderer fallback");
            driver_name_ = "";  // vacío → RenderInfo mostrará "sdl"
            return false;
        }
        if (device_ == nullptr) {
#ifdef __APPLE__
            const SDL_GPUShaderFormat PREFERRED = SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB;
#else
            const SDL_GPUShaderFormat PREFERRED = SDL_GPU_SHADERFORMAT_SPIRV;
#endif
            const char* preferred = preferred_driver_.empty() ? nullptr : preferred_driver_.c_str();
            device_ = SDL_CreateGPUDevice(PREFERRED, false, preferred);
            if (device_ == nullptr && preferred != nullptr) {
                SDL_Log("SDL3GPUShader: driver '%s' not available, falling back to auto", preferred);
                device_ = SDL_CreateGPUDevice(PREFERRED, false, nullptr);
            }
            if (device_ == nullptr) {
                SDL_Log("SDL3GPUShader: SDL_CreateGPUDevice failed: %s", SDL_GetError());
                return false;
            }
            driver_name_ = SDL_GetGPUDeviceDriver(device_);
            std::cout << "GPU Driver   : " << driver_name_ << '\n';

            // ----------------------------------------------------------------
            // 2. Claim window (una sola vez — no liberar hasta destroy())
            // ----------------------------------------------------------------
            if (!SDL_ClaimWindowForGPUDevice(device_, window_)) {
                SDL_Log("SDL3GPUShader: SDL_ClaimWindowForGPUDevice failed: %s", SDL_GetError());
                SDL_DestroyGPUDevice(device_);
                device_ = nullptr;
                return false;
            }
            SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, bestPresentMode(vsync_));
        }

        // ----------------------------------------------------------------
        // 3. Create scene texture (upload target, always game resolution)
        //    Format: B8G8R8A8_UNORM matches SDL ARGB8888 byte layout on LE
        // ----------------------------------------------------------------
        SDL_GPUTextureCreateInfo tex_info = {};
        tex_info.type = SDL_GPU_TEXTURETYPE_2D;
        tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
        tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
        tex_info.width = static_cast<Uint32>(game_width_);
        tex_info.height = static_cast<Uint32>(game_height_);
        tex_info.layer_count_or_depth = 1;
        tex_info.num_levels = 1;
        scene_texture_ = SDL_CreateGPUTexture(device_, &tex_info);
        if (scene_texture_ == nullptr) {
            SDL_Log("SDL3GPUShader: failed to create scene texture: %s", SDL_GetError());
            cleanup();
            return false;
        }

        // ----------------------------------------------------------------
        // 4. Create upload transfer buffer (CPU → GPU, always game resolution)
        // ----------------------------------------------------------------
        SDL_GPUTransferBufferCreateInfo tb_info = {};
        tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
        tb_info.size = static_cast<Uint32>(game_width_ * game_height_ * 4);
        upload_buffer_ = SDL_CreateGPUTransferBuffer(device_, &tb_info);
        if (upload_buffer_ == nullptr) {
            SDL_Log("SDL3GPUShader: failed to create upload buffer: %s", SDL_GetError());
            cleanup();
            return false;
        }

        // ----------------------------------------------------------------
        // 5. Create NEAREST sampler (pixel art)
        // ----------------------------------------------------------------
        SDL_GPUSamplerCreateInfo samp_info = {};
        samp_info.min_filter = SDL_GPU_FILTER_NEAREST;
        samp_info.mag_filter = SDL_GPU_FILTER_NEAREST;
        samp_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
        samp_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
        samp_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
        samp_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
        sampler_ = SDL_CreateGPUSampler(device_, &samp_info);
        if (sampler_ == nullptr) {
            SDL_Log("SDL3GPUShader: failed to create sampler: %s", SDL_GetError());
            cleanup();
            return false;
        }

        // ----------------------------------------------------------------
        // 6. Create PostFX graphics pipeline
        // ----------------------------------------------------------------
        if (!createPipeline()) {
            cleanup();
            return false;
        }

        // ----------------------------------------------------------------
        // 7. Create CrtPi graphics pipeline
        // ----------------------------------------------------------------
        if (!createCrtPiPipeline()) {
            cleanup();
            return false;
        }

        is_initialized_ = true;
        std::cout << "GPU Shader   : initialized OK — game " << game_width_ << 'x' << game_height_ << '\n';
        return true;
    }

    // ---------------------------------------------------------------------------
    // createPostfxVertexShader — fullscreen-triangle vertex compartit per tots els pipelines
    // ---------------------------------------------------------------------------
    auto SDL3GPUShader::createPostfxVertexShader() -> SDL_GPUShader* {
#ifdef __APPLE__
        return createShaderMSL(device_, Rendering::Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
#else
        return createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
#endif
    }

    // ---------------------------------------------------------------------------
    // createPostfxLikePipeline — empaqueta vert(postfx) + frag dado + target en un pipeline.
    // Pren ownership de `frag` (el libera abans de retornar).
    // ---------------------------------------------------------------------------
    auto SDL3GPUShader::createPostfxLikePipeline(SDL_GPUShader* frag, SDL_GPUTextureFormat format, const char* debug_name) -> SDL_GPUGraphicsPipeline* {
        if (frag == nullptr) {
            SDL_Log("SDL3GPUShader: %s frag shader is null", debug_name);
            return nullptr;
        }
        SDL_GPUShader* vert = createPostfxVertexShader();
        if (vert == nullptr) {
            SDL_Log("SDL3GPUShader: %s vert shader creation failed", debug_name);
            SDL_ReleaseGPUShader(device_, frag);
            return nullptr;
        }

        SDL_GPUColorTargetBlendState no_blend = {};
        no_blend.enable_blend = false;
        no_blend.enable_color_write_mask = false;

        SDL_GPUColorTargetDescription color_target = {};
        color_target.format = format;
        color_target.blend_state = no_blend;

        SDL_GPUVertexInputState no_input = {};

        SDL_GPUGraphicsPipelineCreateInfo info = {};
        info.vertex_shader = vert;
        info.fragment_shader = frag;
        info.vertex_input_state = no_input;
        info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
        info.target_info.num_color_targets = 1;
        info.target_info.color_target_descriptions = &color_target;

        SDL_GPUGraphicsPipeline* pipeline = SDL_CreateGPUGraphicsPipeline(device_, &info);

        SDL_ReleaseGPUShader(device_, vert);
        SDL_ReleaseGPUShader(device_, frag);

        if (pipeline == nullptr) {
            SDL_Log("SDL3GPUShader: %s pipeline creation failed: %s", debug_name, SDL_GetError());
        }
        return pipeline;
    }

    // ---------------------------------------------------------------------------
    // createPipeline — pipeline únic PostFX → swapchain
    // ---------------------------------------------------------------------------
    auto SDL3GPUShader::createPipeline() -> bool {
        const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);

#ifdef __APPLE__
        SDL_GPUShader* postfx_frag = createShaderMSL(device_, Rendering::Msl::kPostfxFrag, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#else
        SDL_GPUShader* postfx_frag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#endif

        pipeline_ = createPostfxLikePipeline(postfx_frag, SWAPCHAIN_FMT, "PostFX");
        return pipeline_ != nullptr;
    }

    // ---------------------------------------------------------------------------
    // createCrtPiPipeline — pipeline dedicado para el shader CRT-Pi.
    // Usa el mismo vertex shader que postfx (fullscreen-triangle genérico).
    // El fragment shader es específico para el algoritmo CRT-Pi.
    // Sin supersampling ni Lanczos: va siempre directo al swapchain.
    // ---------------------------------------------------------------------------
    auto SDL3GPUShader::createCrtPiPipeline() -> bool {
        const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
#ifdef __APPLE__
        SDL_GPUShader* frag = createShaderMSL(device_, Rendering::Msl::kCrtpiFrag, "crtpi_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#else
        SDL_GPUShader* frag = createShaderSPIRV(device_, kcrtpi_frag_spv, kcrtpi_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#endif
        crtpi_pipeline_ = createPostfxLikePipeline(frag, SWAPCHAIN_FMT, "CrtPi");
        return crtpi_pipeline_ != nullptr;
    }

    // ---------------------------------------------------------------------------
    // uploadPixels — copies ARGB8888 CPU pixels into the GPU transfer buffer.
    // ---------------------------------------------------------------------------
    void SDL3GPUShader::uploadPixels(const Uint32* pixels, int width, int height) {
        if (!is_initialized_ || (upload_buffer_ == nullptr)) { return; }

        void* mapped = SDL_MapGPUTransferBuffer(device_, upload_buffer_, false);
        if (mapped == nullptr) {
            SDL_Log("SDL3GPUShader: SDL_MapGPUTransferBuffer failed: %s", SDL_GetError());
            return;
        }

        std::memcpy(mapped, pixels, static_cast<size_t>(width) * static_cast<size_t>(height) * 4U);

        SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
    }

    // ---------------------------------------------------------------------------
    // render — upload scene texture + PostFX pass → swapchain
    // ---------------------------------------------------------------------------
    void SDL3GPUShader::render() {
        if (!is_initialized_) { return; }

        SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_);
        if (cmd == nullptr) {
            SDL_Log("SDL3GPUShader: SDL_AcquireGPUCommandBuffer failed: %s", SDL_GetError());
            return;
        }

        // ---- Copy pass: transfer buffer → scene texture ----
        SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd);
        if (copy != nullptr) {
            SDL_GPUTextureTransferInfo src = {};
            src.transfer_buffer = upload_buffer_;
            src.offset = 0;
            src.pixels_per_row = static_cast<Uint32>(game_width_);
            src.rows_per_layer = static_cast<Uint32>(game_height_);

            SDL_GPUTextureRegion dst = {};
            dst.texture = scene_texture_;
            dst.w = static_cast<Uint32>(game_width_);
            dst.h = static_cast<Uint32>(game_height_);
            dst.d = 1;

            SDL_UploadToGPUTexture(copy, &src, &dst, false);
            SDL_EndGPUCopyPass(copy);
        }

        // ---- Acquire swapchain texture ----
        SDL_GPUTexture* swapchain = nullptr;
        Uint32 sw = 0;
        Uint32 sh = 0;
        if (!SDL_AcquireGPUSwapchainTexture(cmd, window_, &swapchain, &sw, &sh)) {
            SDL_Log("SDL3GPUShader: SDL_AcquireGPUSwapchainTexture failed: %s", SDL_GetError());
            SDL_SubmitGPUCommandBuffer(cmd);
            return;
        }
        if (swapchain == nullptr) {
            // Window minimized — skip frame
            SDL_SubmitGPUCommandBuffer(cmd);
            return;
        }

        // ---- Calcular viewport (dimensiones lógicas del canvas, no de textura GPU) ----
        float vx = 0.0F;
        float vy = 0.0F;
        float vw = 0.0F;
        float vh = 0.0F;
        if (integer_scale_) {
            const int SCALE = std::max(1, std::min(static_cast<int>(sw) / game_width_, static_cast<int>(sh) / game_height_));
            vw = static_cast<float>(game_width_ * SCALE);
            vh = static_cast<float>(game_height_ * SCALE);
        } else {
            const float SCALE = std::min(
                static_cast<float>(sw) / static_cast<float>(game_width_),
                static_cast<float>(sh) / static_cast<float>(game_height_));
            vw = static_cast<float>(game_width_) * SCALE;
            vh = static_cast<float>(game_height_) * SCALE;
        }
        vx = std::floor((static_cast<float>(sw) - vw) * 0.5F);
        vy = std::floor((static_cast<float>(sh) - vh) * 0.5F);

        // pixel_scale: subpíxels per pixel lògic (zoom de finestra).
        uniforms_.pixel_scale = (game_height_ > 0) ? (vh / static_cast<float>(game_height_)) : 1.0F;
        uniforms_.time = static_cast<float>(SDL_GetTicks()) / 1000.0F;

        // ---- Path CrtPi: directo scene_texture_ → swapchain, sin SS ni Lanczos ----
        if (active_shader_ == ShaderType::CRTPI && crtpi_pipeline_ != nullptr) {
            SDL_GPUColorTargetInfo color_target = {};
            color_target.texture = swapchain;
            color_target.load_op = SDL_GPU_LOADOP_CLEAR;
            color_target.store_op = SDL_GPU_STOREOP_STORE;
            color_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};

            SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &color_target, 1, nullptr);
            if (pass != nullptr) {
                SDL_BindGPUGraphicsPipeline(pass, crtpi_pipeline_);
                SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
                SDL_SetGPUViewport(pass, &vp);

                SDL_GPUTextureSamplerBinding binding = {};
                binding.texture = scene_texture_;
                binding.sampler = sampler_;  // NEAREST: el shader CrtPi hace su propio filtrado analítico
                SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);

                // Inyectar texture_width/height antes del push
                crtpi_uniforms_.texture_width = static_cast<float>(game_width_);
                crtpi_uniforms_.texture_height = static_cast<float>(game_height_);
                SDL_PushGPUFragmentUniformData(cmd, 0, &crtpi_uniforms_, sizeof(CrtPiUniforms));

                SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
                SDL_EndGPURenderPass(pass);
            }

            SDL_SubmitGPUCommandBuffer(cmd);
            return;
        }

        // ---- Render pass: PostFX → swapchain ----
        SDL_GPUColorTargetInfo color_target = {};
        color_target.texture = swapchain;
        color_target.load_op = SDL_GPU_LOADOP_CLEAR;
        color_target.store_op = SDL_GPU_STOREOP_STORE;
        color_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};

        SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &color_target, 1, nullptr);
        if (pass != nullptr) {
            SDL_BindGPUGraphicsPipeline(pass, pipeline_);
            SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
            SDL_SetGPUViewport(pass, &vp);

            SDL_GPUTextureSamplerBinding binding = {};
            binding.texture = scene_texture_;
            binding.sampler = sampler_;
            SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);

            SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms));

            SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
            SDL_EndGPURenderPass(pass);
        }

        SDL_SubmitGPUCommandBuffer(cmd);
    }

    // ---------------------------------------------------------------------------
    // cleanup — libera pipeline/texturas/buffer pero mantiene device + swapchain
    // ---------------------------------------------------------------------------
    void SDL3GPUShader::cleanup() {
        is_initialized_ = false;

        if (device_ != nullptr) {
            SDL_WaitForGPUIdle(device_);

            if (pipeline_ != nullptr) {
                SDL_ReleaseGPUGraphicsPipeline(device_, pipeline_);
                pipeline_ = nullptr;
            }
            if (crtpi_pipeline_ != nullptr) {
                SDL_ReleaseGPUGraphicsPipeline(device_, crtpi_pipeline_);
                crtpi_pipeline_ = nullptr;
            }
            if (scene_texture_ != nullptr) {
                SDL_ReleaseGPUTexture(device_, scene_texture_);
                scene_texture_ = nullptr;
            }
            if (upload_buffer_ != nullptr) {
                SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
                upload_buffer_ = nullptr;
            }
            if (sampler_ != nullptr) {
                SDL_ReleaseGPUSampler(device_, sampler_);
                sampler_ = nullptr;
            }
            // device_ y el claim de la ventana se mantienen vivos
        }
    }

    // ---------------------------------------------------------------------------
    // destroy — limpieza completa incluyendo device y swapchain (solo al cerrar)
    // ---------------------------------------------------------------------------
    void SDL3GPUShader::destroy() {
        cleanup();

        if (device_ != nullptr) {
            if (window_ != nullptr) {
                SDL_ReleaseWindowFromGPUDevice(device_, window_);
            }
            SDL_DestroyGPUDevice(device_);
            device_ = nullptr;
        }
        window_ = nullptr;
    }

    // ---------------------------------------------------------------------------
    // Shader creation helpers
    // ---------------------------------------------------------------------------
    auto SDL3GPUShader::createShaderMSL(SDL_GPUDevice* device,
        const char* msl_source,
        const char* entrypoint,
        SDL_GPUShaderStage stage,
        Uint32 num_samplers,
        Uint32 num_uniform_buffers) -> SDL_GPUShader* {
        SDL_GPUShaderCreateInfo info = {};
        info.code = reinterpret_cast<const Uint8*>(msl_source);
        info.code_size = std::strlen(msl_source) + 1;
        info.entrypoint = entrypoint;
        info.format = SDL_GPU_SHADERFORMAT_MSL;
        info.stage = stage;
        info.num_samplers = num_samplers;
        info.num_uniform_buffers = num_uniform_buffers;
        SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
        if (shader == nullptr) {
            SDL_Log("SDL3GPUShader: MSL shader '%s' failed: %s", entrypoint, SDL_GetError());
        }
        return shader;
    }

    auto SDL3GPUShader::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* {
        SDL_GPUShaderCreateInfo info = {};
        info.code = spv_code;
        info.code_size = spv_size;
        info.entrypoint = entrypoint;
        info.format = SDL_GPU_SHADERFORMAT_SPIRV;
        info.stage = stage;
        info.num_samplers = num_samplers;
        info.num_uniform_buffers = num_uniform_buffers;
        SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
        if (shader == nullptr) {
            SDL_Log("SDL3GPUShader: SPIRV shader '%s' failed: %s", entrypoint, SDL_GetError());
        }
        return shader;
    }

    void SDL3GPUShader::setPostFXParams(const PostFXParams& p) {
        uniforms_.vignette_strength = p.vignette;
        uniforms_.chroma_min = p.chroma_min;
        uniforms_.chroma_max = p.chroma_max;
        uniforms_.mask_strength = p.mask;
        uniforms_.gamma_strength = p.gamma;
        uniforms_.curvature = p.curvature;
        uniforms_.bleeding = p.bleeding;
        uniforms_.flicker = p.flicker;
        uniforms_.scanline_strength = p.scanlines;
        uniforms_.scan_dark_ratio = p.scan_dark_ratio;
        uniforms_.scan_dark_floor = p.scan_dark_floor;
        uniforms_.scan_edge_soft = p.scan_edge_soft;
    }

    void SDL3GPUShader::setCrtPiParams(const CrtPiParams& p) {
        crtpi_uniforms_.scanline_weight = p.scanline_weight;
        crtpi_uniforms_.scanline_gap_brightness = p.scanline_gap_brightness;
        crtpi_uniforms_.bloom_factor = p.bloom_factor;
        crtpi_uniforms_.input_gamma = p.input_gamma;
        crtpi_uniforms_.output_gamma = p.output_gamma;
        crtpi_uniforms_.mask_brightness = p.mask_brightness;
        crtpi_uniforms_.curvature_x = p.curvature_x;
        crtpi_uniforms_.curvature_y = p.curvature_y;
        crtpi_uniforms_.mask_type = p.mask_type;
        crtpi_uniforms_.enable_scanlines = p.enable_scanlines ? 1 : 0;
        crtpi_uniforms_.enable_multisample = p.enable_multisample ? 1 : 0;
        crtpi_uniforms_.enable_gamma = p.enable_gamma ? 1 : 0;
        crtpi_uniforms_.enable_curvature = p.enable_curvature ? 1 : 0;
        crtpi_uniforms_.enable_sharper = p.enable_sharper ? 1 : 0;
        // texture_width/height se inyectan en render() cada frame
    }

    void SDL3GPUShader::setActiveShader(ShaderType type) {
        active_shader_ = type;
    }

    auto SDL3GPUShader::bestPresentMode(bool vsync) const -> SDL_GPUPresentMode {
        if (vsync) {
            return SDL_GPU_PRESENTMODE_VSYNC;
        }
        // IMMEDIATE: sin sincronización — el driver puede no soportarlo en Wayland/compositing
        if (SDL_WindowSupportsGPUPresentMode(device_, window_, SDL_GPU_PRESENTMODE_IMMEDIATE)) {
            return SDL_GPU_PRESENTMODE_IMMEDIATE;
        }
        // MAILBOX: presenta en el siguiente VBlank pero sin bloquear el hilo (triple buffer)
        if (SDL_WindowSupportsGPUPresentMode(device_, window_, SDL_GPU_PRESENTMODE_MAILBOX)) {
            SDL_Log("SDL3GPUShader: IMMEDIATE no soportado, usando MAILBOX para VSync desactivado");
            return SDL_GPU_PRESENTMODE_MAILBOX;
        }
        SDL_Log("SDL3GPUShader: IMMEDIATE y MAILBOX no soportados, forzando VSYNC");
        return SDL_GPU_PRESENTMODE_VSYNC;
    }

    void SDL3GPUShader::setVSync(bool vsync) {
        vsync_ = vsync;
        if (device_ != nullptr && window_ != nullptr) {
            SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, bestPresentMode(vsync_));
        }
    }

    void SDL3GPUShader::setScaleMode(bool integer_scale) {
        integer_scale_ = integer_scale;
    }

    // ---------------------------------------------------------------------------
    // reinitTexturesAndBuffer — recrea scene_texture_ y upload_buffer_ a resolució
    // del joc (sense supersampling). No toca pipelines ni samplers.
    // ---------------------------------------------------------------------------
    auto SDL3GPUShader::reinitTexturesAndBuffer() -> bool {
        if (device_ == nullptr) { return false; }
        SDL_WaitForGPUIdle(device_);

        if (scene_texture_ != nullptr) {
            SDL_ReleaseGPUTexture(device_, scene_texture_);
            scene_texture_ = nullptr;
        }
        if (upload_buffer_ != nullptr) {
            SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
            upload_buffer_ = nullptr;
        }

        uniforms_.screen_height = static_cast<float>(game_height_);

        SDL_GPUTextureCreateInfo tex_info = {};
        tex_info.type = SDL_GPU_TEXTURETYPE_2D;
        tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
        tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
        tex_info.width = static_cast<Uint32>(game_width_);
        tex_info.height = static_cast<Uint32>(game_height_);
        tex_info.layer_count_or_depth = 1;
        tex_info.num_levels = 1;
        scene_texture_ = SDL_CreateGPUTexture(device_, &tex_info);
        if (scene_texture_ == nullptr) {
            SDL_Log("SDL3GPUShader: reinit — failed to create scene texture: %s", SDL_GetError());
            return false;
        }

        SDL_GPUTransferBufferCreateInfo tb_info = {};
        tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
        tb_info.size = static_cast<Uint32>(game_width_ * game_height_ * 4);
        upload_buffer_ = SDL_CreateGPUTransferBuffer(device_, &tb_info);
        if (upload_buffer_ == nullptr) {
            SDL_Log("SDL3GPUShader: reinit — failed to create upload buffer: %s", SDL_GetError());
            SDL_ReleaseGPUTexture(device_, scene_texture_);
            scene_texture_ = nullptr;
            return false;
        }

        SDL_Log("SDL3GPUShader: reinit — scene %dx%d", game_width_, game_height_);
        return true;
    }

}  // namespace Rendering
