#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" #include #include // std::min, std::max #include // std::floor #include // memcpy, strlen #include // Para 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 { if (is_initialized_) { cleanup(); } window_ = window; 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. GPU disabled by config if (preferred_driver_ == "none") { std::cout << "SDL3GPUShader: GPU disabled by config, using SDL_Renderer fallback" << '\n'; driver_name_ = ""; return false; } // 2. Create GPU device (solo si no existe ya) 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) { std::cout << "SDL3GPUShader: driver '" << preferred << "' not available, falling back to auto" << '\n'; device_ = SDL_CreateGPUDevice(PREFERRED, false, nullptr); } if (device_ == nullptr) { std::cout << "SDL3GPUShader: SDL_CreateGPUDevice failed: " << SDL_GetError() << '\n'; return false; } driver_name_ = SDL_GetGPUDeviceDriver(device_); std::cout << "SDL3GPUShader: driver = " << driver_name_ << '\n'; if (!SDL_ClaimWindowForGPUDevice(device_, window_)) { std::cout << "SDL3GPUShader: SDL_ClaimWindowForGPUDevice failed: " << SDL_GetError() << '\n'; SDL_DestroyGPUDevice(device_); device_ = nullptr; return false; } SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, bestPresentMode(vsync_)); } // 3. Create scene texture (game resolution) 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) { std::cout << "SDL3GPUShader: failed to create scene texture: " << SDL_GetError() << '\n'; cleanup(); return false; } // 4. Create upload transfer buffer (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) { std::cout << "SDL3GPUShader: failed to create upload buffer: " << SDL_GetError() << '\n'; cleanup(); return false; } // 5. Create nearest sampler 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) { std::cout << "SDL3GPUShader: failed to create sampler: " << SDL_GetError() << '\n'; cleanup(); return false; } // 6. Create pipelines if (!createPipeline()) { cleanup(); return false; } if (!createCrtPiPipeline()) { cleanup(); return false; } is_initialized_ = true; std::cout << "SDL3GPUShader: initialized OK (" << game_width_ << "x" << game_height_ << ")" << '\n'; return true; } // --------------------------------------------------------------------------- // createPipeline — PostFX → swapchain // --------------------------------------------------------------------------- auto SDL3GPUShader::createPipeline() -> bool { const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_); #ifdef __APPLE__ SDL_GPUShader* vert = createShaderMSL(device_, Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPUShader* frag = createShaderMSL(device_, Msl::kPostfxFrag, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); #else SDL_GPUShader* vert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPUShader* frag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); #endif if ((vert == nullptr) || (frag == nullptr)) { std::cout << "SDL3GPUShader: failed to compile PostFX shaders" << '\n'; if (vert != nullptr) { SDL_ReleaseGPUShader(device_, vert); } if (frag != nullptr) { SDL_ReleaseGPUShader(device_, frag); } return false; } SDL_GPUColorTargetBlendState no_blend = {}; no_blend.enable_blend = false; no_blend.enable_color_write_mask = false; SDL_GPUColorTargetDescription color_target = {}; color_target.format = SWAPCHAIN_FMT; color_target.blend_state = no_blend; SDL_GPUVertexInputState no_input = {}; SDL_GPUGraphicsPipelineCreateInfo pipe_info = {}; pipe_info.vertex_shader = vert; pipe_info.fragment_shader = frag; pipe_info.vertex_input_state = no_input; pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; pipe_info.target_info.num_color_targets = 1; pipe_info.target_info.color_target_descriptions = &color_target; pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &pipe_info); SDL_ReleaseGPUShader(device_, vert); SDL_ReleaseGPUShader(device_, frag); if (pipeline_ == nullptr) { std::cout << "SDL3GPUShader: PostFX pipeline creation failed: " << SDL_GetError() << '\n'; return false; } return true; } // --------------------------------------------------------------------------- // createCrtPiPipeline // --------------------------------------------------------------------------- auto SDL3GPUShader::createCrtPiPipeline() -> bool { const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_); #ifdef __APPLE__ SDL_GPUShader* vert = createShaderMSL(device_, Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPUShader* frag = createShaderMSL(device_, Msl::kCrtpiFrag, "crtpi_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); #else SDL_GPUShader* vert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPUShader* frag = createShaderSPIRV(device_, kcrtpi_frag_spv, kcrtpi_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); #endif if ((vert == nullptr) || (frag == nullptr)) { if (vert != nullptr) { SDL_ReleaseGPUShader(device_, vert); } if (frag != nullptr) { SDL_ReleaseGPUShader(device_, frag); } return false; } SDL_GPUColorTargetBlendState no_blend = {}; SDL_GPUColorTargetDescription ct = {}; ct.format = SWAPCHAIN_FMT; ct.blend_state = no_blend; SDL_GPUVertexInputState no_input = {}; SDL_GPUGraphicsPipelineCreateInfo pi = {}; pi.vertex_shader = vert; pi.fragment_shader = frag; pi.vertex_input_state = no_input; pi.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; pi.target_info.num_color_targets = 1; pi.target_info.color_target_descriptions = &ct; crtpi_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &pi); SDL_ReleaseGPUShader(device_, vert); SDL_ReleaseGPUShader(device_, frag); if (crtpi_pipeline_ == nullptr) { std::cout << "SDL3GPUShader: CrtPi pipeline creation failed: " << SDL_GetError() << '\n'; return false; } return true; } // --------------------------------------------------------------------------- // uploadPixels — direct memcpy // --------------------------------------------------------------------------- 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) { std::cout << "SDL3GPUShader: SDL_MapGPUTransferBuffer failed: " << SDL_GetError() << '\n'; return; } std::memcpy(mapped, pixels, static_cast(width) * height * 4); SDL_UnmapGPUTransferBuffer(device_, upload_buffer_); } // --------------------------------------------------------------------------- // render — CrtPi direct OR PostFX direct → swapchain // --------------------------------------------------------------------------- void SDL3GPUShader::render() { if (!is_initialized_) { return; } SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_); if (cmd == nullptr) { std::cout << "SDL3GPUShader: SDL_AcquireGPUCommandBuffer failed: " << SDL_GetError() << '\n'; 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(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); } // ---- Acquire swapchain texture ---- SDL_GPUTexture* swapchain = nullptr; Uint32 sw = 0; Uint32 sh = 0; if (!SDL_AcquireGPUSwapchainTexture(cmd, window_, &swapchain, &sw, &sh)) { std::cout << "SDL3GPUShader: SDL_AcquireGPUSwapchainTexture failed: " << SDL_GetError() << '\n'; SDL_SubmitGPUCommandBuffer(cmd); return; } if (swapchain == nullptr) { SDL_SubmitGPUCommandBuffer(cmd); return; } // ---- Viewport calculation ---- 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(sw) / game_width_, static_cast(sh) / game_height_)); vw = static_cast(game_width_ * SCALE); vh = static_cast(game_height_ * SCALE); } else { const float SCALE = std::min( static_cast(sw) / static_cast(game_width_), static_cast(sh) / static_cast(game_height_)); vw = static_cast(game_width_) * SCALE; vh = static_cast(game_height_) * SCALE; } vx = std::floor((static_cast(sw) - vw) * 0.5F); vy = std::floor((static_cast(sh) - vh) * 0.5F); uniforms_.pixel_scale = (game_height_ > 0) ? (vh / static_cast(game_height_)) : 1.0F; uniforms_.time = static_cast(SDL_GetTicks()) / 1000.0F; SDL_GPUColorTargetInfo ct = {}; ct.texture = swapchain; ct.load_op = SDL_GPU_LOADOP_CLEAR; ct.store_op = SDL_GPU_STOREOP_STORE; ct.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F}; SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr); if (pass != nullptr) { 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); if (active_shader_ == ShaderType::CRTPI && crtpi_pipeline_ != nullptr) { SDL_BindGPUGraphicsPipeline(pass, crtpi_pipeline_); crtpi_uniforms_.texture_width = static_cast(game_width_); crtpi_uniforms_.texture_height = static_cast(game_height_); SDL_PushGPUFragmentUniformData(cmd, 0, &crtpi_uniforms_, sizeof(CrtPiUniforms)); } else { SDL_BindGPUGraphicsPipeline(pass, pipeline_); SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms)); } SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0); SDL_EndGPURenderPass(pass); } SDL_SubmitGPUCommandBuffer(cmd); } // --------------------------------------------------------------------------- // cleanup // --------------------------------------------------------------------------- void SDL3GPUShader::cleanup() { is_initialized_ = false; if (device_ != nullptr) { SDL_WaitForGPUIdle(device_); auto release_pipeline = [this](SDL_GPUGraphicsPipeline*& p) { if (p != nullptr) { SDL_ReleaseGPUGraphicsPipeline(device_, p); p = nullptr; } }; auto release_texture = [this](SDL_GPUTexture*& t) { if (t != nullptr) { SDL_ReleaseGPUTexture(device_, t); t = nullptr; } }; release_pipeline(pipeline_); release_pipeline(crtpi_pipeline_); release_texture(scene_texture_); if (upload_buffer_ != nullptr) { SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_); upload_buffer_ = nullptr; } if (sampler_ != nullptr) { SDL_ReleaseGPUSampler(device_, sampler_); sampler_ = nullptr; } } } // --------------------------------------------------------------------------- // destroy // --------------------------------------------------------------------------- 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) { std::cout << "SDL3GPUShader: MSL shader '" << entrypoint << "' failed: " << SDL_GetError() << '\n'; } return shader; } auto SDL3GPUShader::createShaderSPIRV(SDL_GPUDevice* device, // NOLINT(readability-convert-member-functions-to-static) 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) { std::cout << "SDL3GPUShader: SPIRV shader '" << entrypoint << "' failed: " << SDL_GetError() << '\n'; } return shader; } void SDL3GPUShader::setPostFXParams(const PostFXParams& p) { uniforms_.vignette_strength = p.vignette; uniforms_.chroma_strength = p.chroma; uniforms_.scanline_strength = p.scanlines; uniforms_.mask_strength = p.mask; uniforms_.gamma_strength = p.gamma; uniforms_.curvature = p.curvature; uniforms_.bleeding = p.bleeding; uniforms_.flicker = p.flicker; 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; } void SDL3GPUShader::setActiveShader(ShaderType type) { active_shader_ = type; } auto SDL3GPUShader::bestPresentMode(bool vsync) const -> SDL_GPUPresentMode { if (vsync) { return SDL_GPU_PRESENTMODE_VSYNC; } if (SDL_WindowSupportsGPUPresentMode(device_, window_, SDL_GPU_PRESENTMODE_IMMEDIATE)) { return SDL_GPU_PRESENTMODE_IMMEDIATE; } if (SDL_WindowSupportsGPUPresentMode(device_, window_, SDL_GPU_PRESENTMODE_MAILBOX)) { return SDL_GPU_PRESENTMODE_MAILBOX; } 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 // --------------------------------------------------------------------------- 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) { std::cout << "SDL3GPUShader: reinit — failed to create scene texture: " << SDL_GetError() << '\n'; 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) { std::cout << "SDL3GPUShader: reinit — failed to create upload buffer: " << SDL_GetError() << '\n'; SDL_ReleaseGPUTexture(device_, scene_texture_); scene_texture_ = nullptr; return false; } std::cout << "SDL3GPUShader: reinit — scene " << game_width_ << "x" << game_height_ << '\n'; return true; } } // namespace Rendering