#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" #include #include // std::min, std::max, std::floor #include // std::floor #include // memcpy, strlen #include // 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(fw); game_height_ = static_cast(fh); uniforms_.screen_height = static_cast(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(game_width_); tex_info.height = static_cast(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(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 sampler: NEAREST (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_, 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 — crea el pipeline PostFX que va directament al swapchain // --------------------------------------------------------------------------- auto SDL3GPUShader::createPipeline() -> bool { const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_); #ifdef __APPLE__ SDL_GPUShader* postfx_frag = createShaderMSL(device_, 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). // --------------------------------------------------------------------------- auto SDL3GPUShader::createCrtPiPipeline() -> bool { const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_); #ifdef __APPLE__ SDL_GPUShader* frag = createShaderMSL(device_, 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(width) * height * 4); SDL_UnmapGPUTransferBuffer(device_, upload_buffer_); } // --------------------------------------------------------------------------- // uploadSceneTexture — copy pass: transfer buffer → scene texture // --------------------------------------------------------------------------- void SDL3GPUShader::uploadSceneTexture(SDL_GPUCommandBuffer* cmd) { SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd); if (copy == nullptr) { return; } SDL_GPUTextureTransferInfo src = {}; src.transfer_buffer = upload_buffer_; src.offset = 0; src.pixels_per_row = static_cast(game_width_); src.rows_per_layer = static_cast(game_height_); SDL_GPUTextureRegion dst = {}; dst.texture = scene_texture_; dst.w = static_cast(game_width_); dst.h = static_cast(game_height_); dst.d = 1; SDL_UploadToGPUTexture(copy, &src, &dst, false); SDL_EndGPUCopyPass(copy); } // --------------------------------------------------------------------------- // computeViewport — dimensions lògiques del canvas dins del swapchain (letterbox) // --------------------------------------------------------------------------- auto SDL3GPUShader::computeViewport(Uint32 sw, Uint32 sh) const -> Viewport { const auto SWF = static_cast(sw); const auto SHF = static_cast(sh); const float CANVAS_RATIO = static_cast(game_width_) / static_cast(game_height_); const float WINDOW_RATIO = SWF / SHF; float vw = 0.0F; float vh = 0.0F; switch (presentation_mode_) { case PresentationMode::INTEGER_SCALE: { const int SCALE = std::max(1, std::min(static_cast(sw) / game_width_, static_cast(sh) / game_height_)); vw = static_cast(game_width_ * SCALE); vh = static_cast(game_height_ * SCALE); break; } case PresentationMode::LETTERBOX: { if (WINDOW_RATIO >= CANVAS_RATIO) { vh = SHF; vw = SHF * CANVAS_RATIO; } else { vw = SWF; vh = SWF / CANVAS_RATIO; } break; } case PresentationMode::STRETCHED: { vw = SWF; vh = SHF; break; } case PresentationMode::OVERSCAN: { if (WINDOW_RATIO >= CANVAS_RATIO) { vw = SWF; vh = SWF / CANVAS_RATIO; } else { vh = SHF; vw = SHF * CANVAS_RATIO; } break; } } const float VX = std::floor((SWF - vw) * 0.5F); const float VY = std::floor((SHF - vh) * 0.5F); return {.x = VX, .y = VY, .w = vw, .h = vh}; } // --------------------------------------------------------------------------- // updateDynamicUniforms — actualitza pixel_scale i time per a aquest frame // --------------------------------------------------------------------------- void SDL3GPUShader::updateDynamicUniforms(float viewport_h) { uniforms_.pixel_scale = (game_height_ > 0) ? (viewport_h / static_cast(game_height_)) : 1.0F; uniforms_.time = static_cast(SDL_GetTicks()) / 1000.0F; } // --------------------------------------------------------------------------- // runCrtPiPass — scene_texture_ → swapchain via pipeline CrtPi (sense SS ni Lanczos) // --------------------------------------------------------------------------- void SDL3GPUShader::runCrtPiPass(SDL_GPUCommandBuffer* cmd, SDL_GPUTexture* swapchain, const Viewport& vp) { 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) { return; } SDL_BindGPUGraphicsPipeline(pass, crtpi_pipeline_); SDL_GPUViewport sdlvp = {.x = vp.x, .y = vp.y, .w = vp.w, .h = vp.h, .min_depth = 0.0F, .max_depth = 1.0F}; SDL_SetGPUViewport(pass, &sdlvp); SDL_GPUTextureSamplerBinding binding = {}; binding.texture = scene_texture_; binding.sampler = sampler_; // NEAREST: el shader CrtPi fa el seu filtrat analític SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1); crtpi_uniforms_.texture_width = static_cast(game_width_); crtpi_uniforms_.texture_height = static_cast(game_height_); SDL_PushGPUFragmentUniformData(cmd, 0, &crtpi_uniforms_, sizeof(CrtPiUniforms)); SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0); SDL_EndGPURenderPass(pass); } // --------------------------------------------------------------------------- // runDirectPostfxPass — PostFX → swapchain directament // --------------------------------------------------------------------------- void SDL3GPUShader::runDirectPostfxPass(SDL_GPUCommandBuffer* cmd, SDL_GPUTexture* swapchain, const Viewport& vp) { 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) { return; } SDL_BindGPUGraphicsPipeline(pass, pipeline_); SDL_GPUViewport sdlvp = {.x = vp.x, .y = vp.y, .w = vp.w, .h = vp.h, .min_depth = 0.0F, .max_depth = 1.0F}; SDL_SetGPUViewport(pass, &sdlvp); 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); } // --------------------------------------------------------------------------- // render — orquestra upload + path PostFX (CrtPi / direct) // --------------------------------------------------------------------------- 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; } uploadSceneTexture(cmd); 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) { // Finestra minimitzada — saltem el frame SDL_SubmitGPUCommandBuffer(cmd); return; } const Viewport VP = computeViewport(sw, sh); updateDynamicUniforms(VP.h); if (active_shader_ == ShaderType::CRTPI && crtpi_pipeline_ != nullptr) { runCrtPiPass(cmd, swapchain, VP); } else { runDirectPostfxPass(cmd, swapchain, VP); } 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(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::setPresentationMode(PresentationMode mode) { presentation_mode_ = mode; } // --------------------------------------------------------------------------- // reinitTexturesAndBuffer — recrea scene_texture_ i upload_buffer_. // 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(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(game_width_); tex_info.height = static_cast(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(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; } return true; } } // namespace Rendering